diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index ed51cf5efbfb..c33c521ee21c 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -53,6 +53,11 @@ Comment: Godot Engine logo Copyright: 2017, Andrea CalabrĂ³ License: CC-BY-4.0 +Files: core/input/input.cpp ("joy_adaptive_triggers_*" methods) +Comment: Factories for all DualSense trigger effects +Copyright: 2021-2022 John "Nielk1" Klein +License: Expat + Files: core/math/convex_hull.cpp core/math/convex_hull.h Comment: Bullet Continuous Collision Detection and Physics Library diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 89d8a08d5c52..2deb226aec68 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -527,6 +527,16 @@ void register_global_constants() { BIND_CORE_BITFIELD_CLASS_FLAG(MouseButtonMask, MOUSE_BUTTON_MASK, MB_XBUTTON1); BIND_CORE_BITFIELD_CLASS_FLAG(MouseButtonMask, MOUSE_BUTTON_MASK, MB_XBUTTON2); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, INVALID); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, LEFT_X); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, LEFT_Y); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, RIGHT_X); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, RIGHT_Y); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, TRIGGER_LEFT); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, TRIGGER_RIGHT); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, SDL_MAX); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, MAX); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, INVALID); BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, A); BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, B); @@ -552,15 +562,50 @@ void register_global_constants() { BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, SDL_MAX); BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, MAX); - BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, INVALID); - BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, LEFT_X); - BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, LEFT_Y); - BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, RIGHT_X); - BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, RIGHT_Y); - BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, TRIGGER_LEFT); - BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, TRIGGER_RIGHT); - BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, SDL_MAX); - BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, MAX); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyModel, JOY_MODEL, INVALID); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyModel, JOY_MODEL, UNKNOWN); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyModel, JOY_MODEL, STANDARD); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyModel, JOY_MODEL, XBOX360); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyModel, JOY_MODEL, XBOXONE); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyModel, JOY_MODEL, PS3); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyModel, JOY_MODEL, PS4); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyModel, JOY_MODEL, PS5); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyModel, JOY_MODEL, SWITCH_PRO); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyModel, JOY_MODEL, JOYCON_LEFT); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyModel, JOY_MODEL, JOYCON_RIGHT); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyModel, JOY_MODEL, JOYCON_PAIR); + + BIND_CORE_ENUM_CLASS_CONSTANT(JoyScheme, JOY_SCHEME, INVALID); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyScheme, JOY_SCHEME, UNKNOWN); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyScheme, JOY_SCHEME, STANDARD); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyScheme, JOY_SCHEME, XBOX); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyScheme, JOY_SCHEME, PLAYSTATION); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyScheme, JOY_SCHEME, NINTENDO); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyScheme, JOY_SCHEME, JOYCON_HORIZONTAL); + + BIND_CORE_ENUM_CLASS_CONSTANT(JoyDeviceType, JOY_DEVICE, INVALID); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyDeviceType, JOY_DEVICE, UNKNOWN); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyDeviceType, JOY_DEVICE, GAMEPAD); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyDeviceType, JOY_DEVICE, WHEEL); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyDeviceType, JOY_DEVICE, ARCADE_STICK); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyDeviceType, JOY_DEVICE, FLIGHT_STICK); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyDeviceType, JOY_DEVICE, DANCE_PAD); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyDeviceType, JOY_DEVICE, GUITAR); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyDeviceType, JOY_DEVICE, DRUM_KIT); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyDeviceType, JOY_DEVICE, ARCADE_PAD); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyDeviceType, JOY_DEVICE, THROTTLE); + + BIND_CORE_ENUM_CLASS_CONSTANT(JoyPowerState, JOY_POWER, INVALID); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyPowerState, JOY_POWER, UNKNOWN); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyPowerState, JOY_POWER, ON_BATTERY); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyPowerState, JOY_POWER, NO_BATTERY); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyPowerState, JOY_POWER, CHARGING); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyPowerState, JOY_POWER, FULL_BATTERY); + + BIND_CORE_ENUM_CLASS_CONSTANT(JoyConnectionState, JOY_CONNECTION, INVALID); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyConnectionState, JOY_CONNECTION, UNKNOWN); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyConnectionState, JOY_CONNECTION, WIRED); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyConnectionState, JOY_CONNECTION, WIRELESS); BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, NONE); BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, NOTE_OFF); diff --git a/core/input/input.cpp b/core/input/input.cpp index 50e6abaea2cd..44bddf4a1eb7 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -40,6 +40,20 @@ #include "core/os/thread.h" #endif +#include "drivers/sdl/joypad_sdl.h" + +#define JOY_CHECK_RETURN(m_value, m_default_value) \ + if (!joy_names.has(p_device)) { \ + return m_default_value; \ + } \ + return m_value; + +#ifdef SDL_ENABLED +#define SDL_ENABLED_CALL(m_code, m_default_value) (m_code) +#else +#define SDL_ENABLED_CALL(m_code, m_default_value) (m_default_value) +#endif + static const char *_joy_buttons[(size_t)JoyButton::SDL_MAX] = { "a", "b", @@ -141,14 +155,62 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("start_joy_vibration", "device", "weak_magnitude", "strong_magnitude", "duration"), &Input::start_joy_vibration, DEFVAL(0)); ClassDB::bind_method(D_METHOD("stop_joy_vibration", "device"), &Input::stop_joy_vibration); ClassDB::bind_method(D_METHOD("vibrate_handheld", "duration_ms", "amplitude"), &Input::vibrate_handheld, DEFVAL(500), DEFVAL(-1.0)); + ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_off", "device", "axis"), &Input::joy_adaptive_triggers_off); + ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_feedback", "device", "axis", "position", "strength"), &Input::joy_adaptive_triggers_feedback); + ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_weapon", "device", "axis", "start_position", "end_position", "strength"), &Input::joy_adaptive_triggers_weapon); + ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_vibration", "device", "axis", "position", "amplitude", "frequency"), &Input::joy_adaptive_triggers_vibration); + ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_multi_feedback", "device", "axis", "strengths"), &Input::joy_adaptive_triggers_multi_feedback); + ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_slope_feedback", "device", "axis", "start_position", "end_position", "start_strength", "end_strength"), &Input::joy_adaptive_triggers_slope_feedback); + ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_multi_vibration", "device", "axis", "frequency", "amplitudes"), &Input::joy_adaptive_triggers_multi_vibration); + ClassDB::bind_method(D_METHOD("start_joy_triggers_vibration", "device", "left_intensity", "right_intensity", "duration"), &Input::start_joy_triggers_vibration); + ClassDB::bind_method(D_METHOD("stop_joy_triggers_vibration", "device"), &Input::stop_joy_triggers_vibration); + ClassDB::bind_method(D_METHOD("send_joy_packet", "device", "packet"), &Input::send_joy_packet); ClassDB::bind_method(D_METHOD("get_gravity"), &Input::get_gravity); ClassDB::bind_method(D_METHOD("get_accelerometer"), &Input::get_accelerometer); ClassDB::bind_method(D_METHOD("get_magnetometer"), &Input::get_magnetometer); ClassDB::bind_method(D_METHOD("get_gyroscope"), &Input::get_gyroscope); + ClassDB::bind_method(D_METHOD("is_joy_accelerometer_enabled", "device"), &Input::is_joy_accelerometer_enabled); + ClassDB::bind_method(D_METHOD("is_joy_gyroscope_enabled", "device"), &Input::is_joy_gyroscope_enabled); + ClassDB::bind_method(D_METHOD("get_joy_accelerometer", "device"), &Input::get_joy_accelerometer); + ClassDB::bind_method(D_METHOD("get_joy_gravity", "device"), &Input::get_joy_gravity); + ClassDB::bind_method(D_METHOD("get_joy_gyroscope", "device"), &Input::get_joy_gyroscope); + ClassDB::bind_method(D_METHOD("get_joy_sensor_rate", "device"), &Input::get_joy_sensor_rate); + ClassDB::bind_method(D_METHOD("get_joy_model", "device"), &Input::get_joy_model); + ClassDB::bind_method(D_METHOD("get_joy_scheme", "device"), &Input::get_joy_scheme); + ClassDB::bind_method(D_METHOD("get_joy_device_type", "device"), &Input::get_joy_device_type); + ClassDB::bind_method(D_METHOD("get_joy_power_state", "device"), &Input::get_joy_power_state); + ClassDB::bind_method(D_METHOD("get_joy_battery_percent", "device"), &Input::get_joy_battery_percent); + ClassDB::bind_method(D_METHOD("get_joy_connection_state", "device"), &Input::get_joy_connection_state); + ClassDB::bind_method(D_METHOD("get_joy_axis_string", "device", "axis"), &Input::get_joy_axis_string); + ClassDB::bind_method(D_METHOD("get_joy_button_string", "device", "button"), &Input::get_joy_button_string); + ClassDB::bind_method(D_METHOD("get_joy_model_axis_string", "model", "axus"), &Input::get_joy_model_axis_string); + ClassDB::bind_method(D_METHOD("get_joy_model_button_string", "model", "button"), &Input::get_joy_model_button_string); + ClassDB::bind_method(D_METHOD("has_joy_axis", "device", "axis"), &Input::has_joy_axis); + ClassDB::bind_method(D_METHOD("has_joy_button", "device", "button"), &Input::has_joy_button); + ClassDB::bind_method(D_METHOD("get_joy_touchpad_finger_position", "device", "touchpad", "finger"), &Input::get_joy_touchpad_finger_position); + ClassDB::bind_method(D_METHOD("get_joy_touchpad_finger_pressure", "device", "touchpad", "finger"), &Input::get_joy_touchpad_finger_pressure); + ClassDB::bind_method(D_METHOD("get_joy_touchpad_fingers", "device", "touchpad"), &Input::get_joy_touchpad_fingers); + ClassDB::bind_method(D_METHOD("get_joy_num_buttons", "device"), &Input::get_joy_num_buttons); + ClassDB::bind_method(D_METHOD("get_joy_num_axes", "device"), &Input::get_joy_num_axes); + ClassDB::bind_method(D_METHOD("get_joy_num_touchpads", "device"), &Input::get_joy_num_touchpads); + ClassDB::bind_method(D_METHOD("start_joy_motion_calibration", "device"), &Input::start_joy_motion_calibration); + ClassDB::bind_method(D_METHOD("step_joy_motion_calibration", "device"), &Input::step_joy_motion_calibration); + ClassDB::bind_method(D_METHOD("stop_joy_motion_calibration", "device"), &Input::stop_joy_motion_calibration); + ClassDB::bind_method(D_METHOD("clear_joy_motion_calibration", "device"), &Input::clear_joy_motion_calibration); + ClassDB::bind_method(D_METHOD("get_joy_motion_calibration", "device"), &Input::get_joy_motion_calibration); + ClassDB::bind_method(D_METHOD("set_joy_motion_calibration", "device", "calibration_info"), &Input::set_joy_motion_calibration); + ClassDB::bind_method(D_METHOD("is_joy_motion_calibrated", "device"), &Input::is_joy_motion_calibrated); + ClassDB::bind_method(D_METHOD("is_joy_motion_calibrating", "device"), &Input::is_joy_motion_calibrating); ClassDB::bind_method(D_METHOD("set_gravity", "value"), &Input::set_gravity); ClassDB::bind_method(D_METHOD("set_accelerometer", "value"), &Input::set_accelerometer); ClassDB::bind_method(D_METHOD("set_magnetometer", "value"), &Input::set_magnetometer); ClassDB::bind_method(D_METHOD("set_gyroscope", "value"), &Input::set_gyroscope); + ClassDB::bind_method(D_METHOD("set_joy_light", "device", "color"), &Input::set_joy_light); + ClassDB::bind_method(D_METHOD("set_joy_accelerometer_enabled", "device", "enable"), &Input::set_joy_accelerometer_enabled); + ClassDB::bind_method(D_METHOD("set_joy_gyroscope_enabled", "device", "enable"), &Input::set_joy_gyroscope_enabled); + ClassDB::bind_method(D_METHOD("has_joy_light", "device"), &Input::has_joy_light); + ClassDB::bind_method(D_METHOD("has_joy_accelerometer", "device"), &Input::has_joy_accelerometer); + ClassDB::bind_method(D_METHOD("has_joy_gyroscope", "device"), &Input::has_joy_gyroscope); ClassDB::bind_method(D_METHOD("get_last_mouse_velocity"), &Input::get_last_mouse_velocity); ClassDB::bind_method(D_METHOD("get_last_mouse_screen_velocity"), &Input::get_last_mouse_screen_velocity); ClassDB::bind_method(D_METHOD("get_mouse_button_mask"), &Input::get_mouse_button_mask); @@ -599,7 +661,12 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_ } } } + // We don't want this to be exposed to the user, because this setting + // is not very useful outside of this method + js.info.erase("mapping_handled"); + _set_joypad_mapping(js, mapping); + SDL_ENABLED_CALL(JoypadSDL::get_singleton()->get_joypad_features(p_idx, js), (void)0); } else { js.connected = false; for (int i = 0; i < (int)JoyButton::MAX; i++) { @@ -609,6 +676,9 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_ for (int i = 0; i < (int)JoyAxis::MAX; i++) { set_joy_axis(p_idx, (JoyAxis)i, 0.0f); } + joy_vibration.erase(p_idx); + joy_motion.erase(p_idx); + joy_touch.erase(p_idx); } joy_names[p_idx] = js; @@ -664,6 +734,375 @@ Vector3 Input::get_gyroscope() const { return gyroscope; } +bool Input::is_joy_accelerometer_enabled(int p_device) const { + _THREAD_SAFE_METHOD_ + return joy_motion.has(p_device) && joy_motion[p_device].accelerometer_enabled; +} + +bool Input::is_joy_gyroscope_enabled(int p_device) const { + _THREAD_SAFE_METHOD_ + return joy_motion.has(p_device) && joy_motion[p_device].gyroscope_enabled; +} + +Vector3 Input::get_joy_accelerometer(int p_device) const { + _THREAD_SAFE_METHOD_ + if (!joy_motion.has(p_device)) { + return Vector3(); + } + + const MotionInfo &motion = joy_motion[p_device]; + Vector3 value = motion.accelerometer - motion.calibration.accelerometer_offset; + if (std::abs(value.x) < motion.calibration.accelerometer_deadzone) { + value.x = 0; + } + if (std::abs(value.y) < motion.calibration.accelerometer_deadzone) { + value.y = 0; + } + if (std::abs(value.z) < motion.calibration.accelerometer_deadzone) { + value.z = 0; + } + return value; +} + +Vector3 Input::get_joy_gravity(int p_device) const { + _THREAD_SAFE_METHOD_ + if (joy_motion.has(p_device)) { + // For some reason, the reported gravity is reversed + return -joy_motion[p_device].gravity; + } else { + return Vector3(); + } +} + +Vector3 Input::get_joy_gyroscope(int p_device) const { + _THREAD_SAFE_METHOD_ + if (!joy_motion.has(p_device)) { + return Vector3(); + } + + const MotionInfo &motion = joy_motion[p_device]; + Vector3 value = motion.gyroscope - motion.calibration.gyroscope_offset; + if (std::abs(value.x) < motion.calibration.gyroscope_deadzone) { + value.x = 0; + } + if (std::abs(value.y) < motion.calibration.gyroscope_deadzone) { + value.y = 0; + } + if (std::abs(value.z) < motion.calibration.gyroscope_deadzone) { + value.z = 0; + } + return value; +} + +float Input::get_joy_sensor_rate(int p_device) const { + _THREAD_SAFE_METHOD_ + if (joy_motion.has(p_device)) { + return joy_motion[p_device].sensor_data_rate; + } else { + return 0.0f; + } +} + +JoyModel Input::get_joy_model(int p_device) const { + _THREAD_SAFE_METHOD_ + JOY_CHECK_RETURN(joy_names[p_device].model, JoyModel::INVALID); +} + +JoyScheme Input::get_joy_scheme(int p_device) const { + _THREAD_SAFE_METHOD_ + if (!joy_names.has(p_device)) { + return JoyScheme::INVALID; + } + + JoyModel model = joy_names[p_device].model; +#ifdef SDL_ENABLED + JoyModel model_override = JoypadSDL::get_singleton()->get_scheme_override_model(p_device); + if (model_override != JoyModel::UNKNOWN) { + model = model_override; + } +#endif + + switch (model) { + case JoyModel::INVALID: // Shouldn't happen, but I'll leave it here just in case + return JoyScheme::INVALID; + + default: + case JoyModel::UNKNOWN: + return JoyScheme::UNKNOWN; + + case JoyModel::STANDARD: + return JoyScheme::STANDARD; + + case JoyModel::XBOX360: + case JoyModel::XBOXONE: + return JoyScheme::XBOX; + + case JoyModel::PS3: + case JoyModel::PS4: + case JoyModel::PS5: + return JoyScheme::PLAYSTATION; + + case JoyModel::SWITCH_PRO: + case JoyModel::JOYCON_PAIR: + return JoyScheme::NINTENDO; + + case JoyModel::JOYCON_LEFT: + case JoyModel::JOYCON_RIGHT: + return JoyScheme::JOYCON_HORIZONTAL; + } +} + +JoyDeviceType Input::get_joy_device_type(int p_device) const { + _THREAD_SAFE_METHOD_ + JOY_CHECK_RETURN(joy_names[p_device].device_type, JoyDeviceType::INVALID); +} + +JoyPowerState Input::get_joy_power_state(int p_device) const { + _THREAD_SAFE_METHOD_ + JOY_CHECK_RETURN(joy_names[p_device].power_state, JoyPowerState::INVALID); +} + +int Input::get_joy_battery_percent(int p_device) const { + _THREAD_SAFE_METHOD_ + JOY_CHECK_RETURN(joy_names[p_device].battery_percent, -1); +} + +JoyConnectionState Input::get_joy_connection_state(int p_device) const { + _THREAD_SAFE_METHOD_ + JOY_CHECK_RETURN(joy_names[p_device].connection_state, JoyConnectionState::INVALID); +} + +String Input::get_joy_axis_string(int p_device, JoyAxis p_axis) const { + _THREAD_SAFE_METHOD_ +#ifdef SDL_ENABLED + if (!joy_names.has(p_device) || !JoypadSDL::get_singleton()->has_joy_axis(p_device, p_axis)) { + return ""; + } + JoyModel model = joy_names[p_device].model; + JoyModel model_override = JoypadSDL::get_singleton()->get_scheme_override_model(p_device); + if (model_override != JoyModel::UNKNOWN) { + model = model_override; + } + return JoypadSDL::get_model_axis_string(model, p_axis); +#else + return ""; +#endif +} + +String Input::get_joy_button_string(int p_device, JoyButton p_button) const { + _THREAD_SAFE_METHOD_ +#ifdef SDL_ENABLED + if (!joy_names.has(p_device) || !JoypadSDL::get_singleton()->has_joy_button(p_device, p_button)) { + return ""; + } + JoyModel model = joy_names[p_device].model; + JoyModel model_override = JoypadSDL::get_singleton()->get_scheme_override_model(p_device); + if (model_override != JoyModel::UNKNOWN) { + model = model_override; + } + return JoypadSDL::get_model_button_string(model, p_button); +#else + return ""; +#endif +} + +String Input::get_joy_model_axis_string(JoyModel p_model, JoyAxis p_axis) const { + _THREAD_SAFE_METHOD_ + return SDL_ENABLED_CALL(JoypadSDL::get_model_axis_string(p_model, p_axis), ""); +} + +String Input::get_joy_model_button_string(JoyModel p_model, JoyButton p_button) const { + _THREAD_SAFE_METHOD_ + return SDL_ENABLED_CALL(JoypadSDL::get_model_button_string(p_model, p_button), ""); +} + +bool Input::has_joy_axis(int p_device, JoyAxis p_axis) const { + _THREAD_SAFE_METHOD_ + return SDL_ENABLED_CALL(JoypadSDL::get_singleton()->has_joy_axis(p_device, p_axis), false); +} + +bool Input::has_joy_button(int p_device, JoyButton p_button) const { + _THREAD_SAFE_METHOD_ + return SDL_ENABLED_CALL(JoypadSDL::get_singleton()->has_joy_button(p_device, p_button), false); +} + +Vector2 Input::get_joy_touchpad_finger_position(int p_device, int p_touchpad, int p_finger) const { + _THREAD_SAFE_METHOD_ + if (!joy_touch.has(p_device) || !joy_touch[p_device].touchpad_fingers.has(p_touchpad) || !joy_touch[p_device].touchpad_fingers[p_touchpad].has(p_finger)) { + return Vector2(-1.0, -1.0); + } + return joy_touch[p_device].touchpad_fingers[p_touchpad][p_finger].position; +} + +float Input::get_joy_touchpad_finger_pressure(int p_device, int p_touchpad, int p_finger) const { + _THREAD_SAFE_METHOD_ + if (!joy_touch.has(p_device) || !joy_touch[p_device].touchpad_fingers.has(p_touchpad) || !joy_touch[p_device].touchpad_fingers[p_touchpad].has(p_finger)) { + return 0.0f; + } + return joy_touch[p_device].touchpad_fingers[p_touchpad][p_finger].pressure; +} + +TypedArray Input::get_joy_touchpad_fingers(int p_device, int p_touchpad) const { + _THREAD_SAFE_METHOD_ + if (!joy_touch.has(p_device) || !joy_touch[p_device].touchpad_fingers.has(p_touchpad)) { + return TypedArray(); + } + + TypedArray result; + for (auto &i : joy_touch[p_device].touchpad_fingers[p_touchpad]) { + result.append(i.key); + } + return result; +} + +int Input::get_joy_num_buttons(int p_device) const { + _THREAD_SAFE_METHOD_ + JOY_CHECK_RETURN(joy_names[p_device].num_buttons, -1); +} + +int Input::get_joy_num_axes(int p_device) const { + _THREAD_SAFE_METHOD_ + JOY_CHECK_RETURN(joy_names[p_device].num_axes, -1); +} + +int Input::get_joy_num_touchpads(int p_device) const { + _THREAD_SAFE_METHOD_ + if (!joy_names.has(p_device)) { + return -1; + } + if (!joy_touch.has(p_device)) { + return 0; + } + return joy_touch[p_device].num_touchpads; +} + +#define CALIBRATION_SETUP \ + if (!joy_motion.has(p_device)) { \ + return; \ + } \ + auto &calibration = joy_motion[p_device].calibration; + +#define CALIBRATION_SETUP_RETURN(m_return) \ + if (!joy_motion.has(p_device)) { \ + return m_return; \ + } \ + auto &calibration = joy_motion[p_device].calibration; + +#define CALIBRATE_SENSOR(m_sensor) \ + vector_sum = Vector3(); \ + for (Vector3 step : calibration.m_sensor##_steps) { \ + vector_sum += step; \ + } \ + vector_sum /= calibration.m_sensor##_steps.size(); \ + \ + deadzone = 0.0f; \ + for (Vector3 step : calibration.m_sensor##_steps) { \ + deadzone = MAX(deadzone, (step - vector_sum).length()); \ + } \ + \ + calibration.m_sensor##_offset = vector_sum; \ + calibration.m_sensor##_deadzone = deadzone; \ + calibration.m_sensor##_steps.clear(); + +void Input::start_joy_motion_calibration(int p_device) { + _THREAD_SAFE_METHOD_ + CALIBRATION_SETUP; + + ERR_FAIL_COND_MSG(calibration.in_progress, "Calibration already in progress."); + + clear_joy_motion_calibration(p_device); + calibration.in_progress = true; +} + +void Input::step_joy_motion_calibration(int p_device) { + _THREAD_SAFE_METHOD_ + CALIBRATION_SETUP; + + ERR_FAIL_COND_MSG(!calibration.in_progress, "Calibration hasn't been started."); + + calibration.accelerometer_steps.push_back(get_joy_accelerometer(p_device)); + calibration.gyroscope_steps.push_back(get_joy_gyroscope(p_device)); +} + +void Input::stop_joy_motion_calibration(int p_device) { + _THREAD_SAFE_METHOD_ + CALIBRATION_SETUP; + + ERR_FAIL_COND_MSG(!calibration.in_progress, "Calibration hasn't been started."); + + if (calibration.accelerometer_steps.size() < 10) { + WARN_PRINT_ED("Not enough joypad motion sensors calibration steps, " + "you should call Input.step_joy_motion_calibration() at least 10 times."); + } + + Vector3 vector_sum; + float deadzone = 0.0f; + + CALIBRATE_SENSOR(accelerometer); + CALIBRATE_SENSOR(gyroscope); + + calibration.in_progress = false; + calibration.calibrated = true; +} + +void Input::clear_joy_motion_calibration(int p_device) { + _THREAD_SAFE_METHOD_ + CALIBRATION_SETUP; + + // Calibration might be in progress and the user might want to reset the progress, + // so no need to stop the calibration. + //calibration.in_progress = false; + calibration.accelerometer_steps.clear(); + calibration.gyroscope_steps.clear(); + calibration.accelerometer_offset = Vector3(); + calibration.gyroscope_offset = Vector3(); + calibration.accelerometer_deadzone = 0.0f; + calibration.gyroscope_deadzone = 0.0f; + calibration.calibrated = false; +} + +Dictionary Input::get_joy_motion_calibration(int p_device) const { + _THREAD_SAFE_METHOD_ + CALIBRATION_SETUP_RETURN({}); + + if (!calibration.calibrated) { + return {}; + } + + Dictionary result; + result["accelerometer_offset"] = calibration.accelerometer_offset; + result["accelerometer_deadzone"] = calibration.accelerometer_deadzone; + result["gyroscope_offset"] = calibration.gyroscope_offset; + result["gyroscope_deadzone"] = calibration.gyroscope_deadzone; + return result; +} + +void Input::set_joy_motion_calibration(int p_device, Dictionary p_calibration_info) { + _THREAD_SAFE_METHOD_ + CALIBRATION_SETUP; + + ERR_FAIL_COND_MSG(calibration.in_progress, "Calibration already in progress."); + + calibration.accelerometer_offset = p_calibration_info["accelerometer_offset"]; + calibration.accelerometer_deadzone = p_calibration_info["accelerometer_deadzone"]; + calibration.gyroscope_offset = p_calibration_info["gyroscope_offset"]; + calibration.gyroscope_deadzone = p_calibration_info["gyroscope_deadzone"]; + calibration.in_progress = false; + calibration.calibrated = true; +} + +bool Input::is_joy_motion_calibrated(int p_device) const { + _THREAD_SAFE_METHOD_ + CALIBRATION_SETUP_RETURN(false); + return calibration.calibrated; +} + +bool Input::is_joy_motion_calibrating(int p_device) const { + _THREAD_SAFE_METHOD_ + CALIBRATION_SETUP_RETURN(false); + return calibration.in_progress; +} + void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_emulated) { // This function does the final delivery of the input event to user land. // Regardless where the event came from originally, this has to happen on the main thread. @@ -917,6 +1356,84 @@ void Input::set_joy_axis(int p_device, JoyAxis p_axis, float p_value) { _joy_axis[c] = p_value; } +bool Input::set_joy_light(int p_device, Color p_color) { + _THREAD_SAFE_METHOD_ + return SDL_ENABLED_CALL(JoypadSDL::get_singleton()->set_light(p_device, p_color), false); +} + +bool Input::set_joy_accelerometer_enabled(int p_device, bool p_enable) { + _THREAD_SAFE_METHOD_ + bool enabled = SDL_ENABLED_CALL(JoypadSDL::get_singleton()->enable_accelerometer(p_device, p_enable), false); + joy_motion[p_device].accelerometer = Vector3(); + joy_motion[p_device].accelerometer_enabled = enabled; + return enabled; +} + +bool Input::set_joy_gyroscope_enabled(int p_device, bool p_enable) { + _THREAD_SAFE_METHOD_ + bool enabled = SDL_ENABLED_CALL(JoypadSDL::get_singleton()->enable_gyroscope(p_device, p_enable), false); + joy_motion[p_device].gyroscope = Vector3(); + joy_motion[p_device].gyroscope_enabled = enabled; + return enabled; +} + +void Input::set_joy_accelerometer(int p_device, const Vector3 &p_value) { + _THREAD_SAFE_METHOD_ + if (!joy_motion.has(p_device)) { + return; + } + + MotionInfo &motion = joy_motion[p_device]; + // Applying gravity code from https://developer.android.com/reference/android/hardware/SensorEvent#values here + motion.gravity = 0.8 * motion.gravity + 0.2 * p_value; + motion.accelerometer = p_value - motion.gravity; +} + +void Input::set_joy_gyroscope(int p_device, const Vector3 &p_value) { + _THREAD_SAFE_METHOD_ + if (!joy_motion.has(p_device)) { + return; + } + + joy_motion[p_device].gyroscope = p_value; +} + +void Input::set_joy_sensor_rate(int p_device, float p_rate) { + _THREAD_SAFE_METHOD_ + joy_motion[p_device].sensor_data_rate = p_rate; +} + +void Input::set_joy_power_info(int p_device, JoyPowerState p_power_state, int p_power_percent) { + _THREAD_SAFE_METHOD_ + joy_names[p_device].power_state = p_power_state; + joy_names[p_device].battery_percent = p_power_percent; +} + +void Input::set_joy_touchpad_finger(int p_device, int p_touchpad, int p_finger, float p_pressure, Vector2 p_value) { + _THREAD_SAFE_METHOD_ + if (p_pressure > 0.0f) { + joy_touch[p_device].touchpad_fingers[p_touchpad][p_finger] = TouchpadFingerInfo{ p_value, p_pressure }; + } else { + joy_touch[p_device].touchpad_fingers[p_touchpad].erase(p_finger); + } + // TODO: event +} + +bool Input::has_joy_light(int p_device) const { + _THREAD_SAFE_METHOD_ + return joy_names.has(p_device) && joy_names[p_device].has_light; +} + +bool Input::has_joy_accelerometer(int p_device) const { + _THREAD_SAFE_METHOD_ + return joy_motion.has(p_device) && joy_motion[p_device].has_accelerometer; +} + +bool Input::has_joy_gyroscope(int p_device) const { + _THREAD_SAFE_METHOD_ + return joy_motion.has(p_device) && joy_motion[p_device].has_gyroscope; +} + void Input::start_joy_vibration(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration) { _THREAD_SAFE_METHOD_ if (p_weak_magnitude < 0.f || p_weak_magnitude > 1.f || p_strong_magnitude < 0.f || p_strong_magnitude > 1.f) { @@ -944,6 +1461,316 @@ void Input::vibrate_handheld(int p_duration_ms, float p_amplitude) { OS::get_singleton()->vibrate_handheld(p_duration_ms, p_amplitude); } +/* + PS5 trigger effect documentation: + https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes +*/ +typedef struct +{ + uint8_t ucEnableBits1; /* 0 */ + uint8_t ucEnableBits2; /* 1 */ + uint8_t ucRumbleRight; /* 2 */ + uint8_t ucRumbleLeft; /* 3 */ + uint8_t ucHeadphoneVolume; /* 4 */ + uint8_t ucSpeakerVolume; /* 5 */ + uint8_t ucMicrophoneVolume; /* 6 */ + uint8_t ucAudioEnableBits; /* 7 */ + uint8_t ucMicLightMode; /* 8 */ + uint8_t ucAudioMuteBits; /* 9 */ + uint8_t rgucRightTriggerEffect[11]; /* 10 */ + uint8_t rgucLeftTriggerEffect[11]; /* 21 */ + uint8_t rgucUnknown1[6]; /* 32 */ + uint8_t ucLedFlags; /* 38 */ + uint8_t rgucUnknown2[2]; /* 39 */ + uint8_t ucLedAnim; /* 41 */ + uint8_t ucLedBrightness; /* 42 */ + uint8_t ucPadLights; /* 43 */ + uint8_t ucLedRed; /* 44 */ + uint8_t ucLedGreen; /* 45 */ + uint8_t ucLedBlue; /* 46 */ +} DS5EffectsState_t; + +#if defined(SDL_ENABLED) && (defined(WINDOWS_ENABLED) || defined(LINUXBSD_ENABLED) || defined(MACOS_ENABLED)) +#define HAVE_SDL_HIDAPI true +#else +#define HAVE_SDL_HIDAPI false +#endif + +#define DUALSENSE_CHECK_TRIGGER \ + ERR_FAIL_COND_MSG(!HAVE_SDL_HIDAPI, "Adaptive triggers feature is not supported on this platform."); \ + ERR_FAIL_COND_MSG(!joy_names.has(p_device), "Joypad not connected."); \ + ERR_FAIL_COND_MSG(joy_names[p_device].model != JoyModel::PS5, \ + "The requested controller is not DualSense. DualSense is the only controller that supports adaptive triggers."); \ + ERR_FAIL_COND_MSG(p_axis != JoyAxis::TRIGGER_LEFT && p_axis != JoyAxis::TRIGGER_RIGHT, \ + "Invalid trigger axis, please specify either JOY_AXIS_TRIGGER_LEFT or JOY_AXIS_TRIGGER_RIGHT."); + +#define DUALSENSE_SETUP_TRIGGER_EFFECT \ + DS5EffectsState_t state; \ + uint8_t *values = p_axis == JoyAxis::TRIGGER_LEFT ? state.rgucLeftTriggerEffect : state.rgucRightTriggerEffect; \ + memset(&state, 0, sizeof(state)); \ + state.ucEnableBits1 |= p_axis == JoyAxis::TRIGGER_LEFT ? 0x08 : 0x04; + +#define DUALSENSE_SEND_EFFECT \ + SDL_ENABLED_CALL(JoypadSDL::get_singleton()->send_effect(p_device, &state, sizeof(state)), (void)0); + +// Credit for the values-generating code: Copyright (c) 2021-2022 John "Nielk1" Klein +// https://gist.github.com/Nielk1/6d54cc2c00d2201ccb8c2720ad7538db + +void Input::joy_adaptive_triggers_off(int p_device, JoyAxis p_axis) { + _THREAD_SAFE_METHOD_ + DUALSENSE_CHECK_TRIGGER; + DUALSENSE_SETUP_TRIGGER_EFFECT; + values[0] = 0x05; + DUALSENSE_SEND_EFFECT; +} + +void Input::joy_adaptive_triggers_feedback(int p_device, JoyAxis p_axis, int p_position, int p_strength) { + if (p_strength == 0) { + joy_adaptive_triggers_off(p_device, p_axis); + return; + } + + _THREAD_SAFE_METHOD_ + DUALSENSE_CHECK_TRIGGER; + + ERR_FAIL_COND_MSG(p_position < 0 || p_position > 9, "The value of parameter \"position\" must be between 0 and 9."); + ERR_FAIL_COND_MSG(p_strength < 0 || p_strength > 8, "The value of parameter \"strength\" must be between 0 and 9."); + + DUALSENSE_SETUP_TRIGGER_EFFECT; + + uint8_t force_value = (uint8_t)((p_strength - 1) & 0x07); + uint32_t force_zones = 0; + uint16_t active_zones = 0; + for (int i = p_position; i < 10; i++) { + force_zones |= (uint32_t)(force_value << (3 * i)); + active_zones |= (uint16_t)(1 << i); + } + + values[0] = 0x21; + values[1] = (uint8_t)((active_zones >> 0) & 0xFF); + values[2] = (uint8_t)((active_zones >> 8) & 0xFF); + values[3] = (uint8_t)((force_zones >> 0) & 0xFF); + values[4] = (uint8_t)((force_zones >> 8) & 0xFF); + values[5] = (uint8_t)((force_zones >> 16) & 0xFF); + values[6] = (uint8_t)((force_zones >> 24) & 0xFF); + + DUALSENSE_SEND_EFFECT; +} + +void Input::joy_adaptive_triggers_weapon(int p_device, JoyAxis p_axis, int p_start_position, int p_end_position, int p_strength) { + if (p_strength == 0) { + joy_adaptive_triggers_off(p_device, p_axis); + return; + } + + _THREAD_SAFE_METHOD_ + DUALSENSE_CHECK_TRIGGER; + + ERR_FAIL_COND_MSG(p_start_position < 2 || p_start_position > 7, + "The value of parameter \"start_position\" must be between 2 and 7."); + ERR_FAIL_COND_MSG(p_end_position <= p_start_position || p_end_position > 8, + "The value of parameter \"end_position\" must be between p_start_position+1 and 8."); + ERR_FAIL_COND_MSG(p_strength < 0 || p_strength > 8, + "The value of parameter \"strength\" must be between 0 and 8."); + + DUALSENSE_SETUP_TRIGGER_EFFECT; + + uint16_t start_and_stop_zones = (uint16_t)((1 << p_start_position) | (1 << p_end_position)); + + values[0] = 0x25; + values[1] = (uint8_t)((start_and_stop_zones >> 0) & 0xFF); + values[2] = (uint8_t)((start_and_stop_zones >> 8) & 0xFF); + values[3] = (uint8_t)(p_strength - 1); + + DUALSENSE_SEND_EFFECT; +} + +void Input::joy_adaptive_triggers_vibration(int p_device, JoyAxis p_axis, int p_position, int p_amplitude, int p_frequency) { + if (p_amplitude == 0 || p_frequency == 0) { + joy_adaptive_triggers_off(p_device, p_axis); + return; + } + + _THREAD_SAFE_METHOD_ + DUALSENSE_CHECK_TRIGGER; + + ERR_FAIL_COND_MSG(p_position < 0 || p_position > 9, "The value of parameter \"position\" must be between 0 and 9."); + ERR_FAIL_COND_MSG(p_amplitude < 0 || p_amplitude > 8, "The value of parameter \"amplitude\" must be between 0 and 8."); + ERR_FAIL_COND_MSG(p_frequency < 0 || p_frequency > 255, "The value of parameter \"frequency\" must be between 0 and 255."); + + DUALSENSE_SETUP_TRIGGER_EFFECT; + + uint8_t strength_value = (uint8_t)((p_amplitude - 1) & 0x7); + uint32_t amplitude_zones = 0; + uint16_t active_zones = 0; + + for (int i = p_position; i < 10; i++) { + amplitude_zones |= (uint32_t)(strength_value << (3 * i)); + active_zones |= (uint16_t)(1 << i); + } + + values[0] = 0x26; + values[1] = (uint8_t)((active_zones >> 0) & 0xFF); + values[2] = (uint8_t)((active_zones >> 8) & 0xFF); + values[3] = (uint8_t)((amplitude_zones >> 0) & 0xFF); + values[4] = (uint8_t)((amplitude_zones >> 8) & 0xFF); + values[5] = (uint8_t)((amplitude_zones >> 16) & 0xFF); + values[6] = (uint8_t)((amplitude_zones >> 24) & 0xFF); + values[9] = (uint8_t)p_frequency; + + DUALSENSE_SEND_EFFECT; +} + +void Input::joy_adaptive_triggers_multi_feedback(int p_device, JoyAxis p_axis, TypedArray p_strengths) { + ERR_FAIL_COND_MSG(p_strengths.size() != 10, "The \"strengths\" parameter array must have 10 elements."); + + bool have_positive_values = false; + for (int i = 0; i < 10; i++) { + if (p_strengths[i].operator int() > 0) { + have_positive_values = true; + break; + } + } + if (!have_positive_values) { + joy_adaptive_triggers_off(p_device, p_axis); + return; + } + + _THREAD_SAFE_METHOD_ + DUALSENSE_CHECK_TRIGGER; + DUALSENSE_SETUP_TRIGGER_EFFECT; + + uint32_t force_zones = 0; + uint16_t active_zones = 0; + + for (int i = 0; i < 10; i++) { + int strength = p_strengths[i]; + ERR_FAIL_COND_MSG(strength < 0 || strength > 8, + vformat("The value of parameter strengths[%d] must be between 0 and 8.", i)); + + if (strength > 0) { + uint8_t force_value = (uint8_t)((strength - 1) & 0x07); + force_zones |= (uint32_t)(force_value << (3 * i)); + active_zones |= (uint16_t)(1 << i); + } + } + + values[0] = 0x21; + values[1] = (uint8_t)((active_zones >> 0) & 0xFF); + values[2] = (uint8_t)((active_zones >> 8) & 0xFF); + values[3] = (uint8_t)((force_zones >> 0) & 0xFF); + values[4] = (uint8_t)((force_zones >> 8) & 0xFF); + values[5] = (uint8_t)((force_zones >> 16) & 0xFF); + values[6] = (uint8_t)((force_zones >> 24) & 0xFF); + + DUALSENSE_SEND_EFFECT; +} + +void Input::joy_adaptive_triggers_slope_feedback(int p_device, JoyAxis p_axis, int p_start_position, int p_end_position, int p_start_strength, int p_end_strength) { + //_THREAD_SAFE_METHOD_ // joy_adaptive_triggers_multi_feedback already handles this + DUALSENSE_CHECK_TRIGGER; + + ERR_FAIL_COND_MSG(p_start_position < 0 || p_start_position > p_end_position, + "The value of parameter \"start_position\" must be between 0 and the value of \"end_position\" parameter."); + ERR_FAIL_COND_MSG(p_end_position <= p_start_position || p_end_position > 9, + "The value of parameter \"end_position\" must be between the value of \"start_position\" parameter + 1 and 9."); + ERR_FAIL_COND_MSG(p_start_strength < 1 || p_start_position > 8, + "The value of parameter \"start_strength\" must be between 1 and 8."); + ERR_FAIL_COND_MSG(p_end_strength < 1 || p_end_strength > 8, + "The value of parameter \"end_strength\" must be between 1 and 8."); + + TypedArray strengths; + float slope = 1.0f * (p_end_strength - p_start_strength) / (p_end_position - p_start_position); + for (int i = 0; i < 10; i++) { + if (i < p_start_position) { + strengths.append(0); + } else if (i <= p_end_position) { + strengths.append((uint8_t)round(p_start_strength + slope * (i - p_start_position))); + } else { + strengths.append(p_end_strength); + } + } + + joy_adaptive_triggers_multi_feedback(p_device, p_axis, strengths); +} + +void Input::joy_adaptive_triggers_multi_vibration(int p_device, JoyAxis p_axis, int p_frequency, TypedArray p_amplitudes) { + ERR_FAIL_COND_MSG(p_frequency < 0 || p_frequency > 255, "The value of parameter \"frequency\" must be between 0 and 255."); + ERR_FAIL_COND_MSG(p_amplitudes.size() != 10, "The \"amplitudes\" parameter array must have 10 elements."); + + bool have_positive_values = false; + for (int i = 0; i < 10; i++) { + if (p_amplitudes[i].operator int() > 0) { + have_positive_values = true; + break; + } + } + if (!have_positive_values || p_frequency == 0) { + joy_adaptive_triggers_off(p_device, p_axis); + return; + } + + _THREAD_SAFE_METHOD_ + DUALSENSE_CHECK_TRIGGER; + DUALSENSE_SETUP_TRIGGER_EFFECT; + + uint32_t strength_zones = 0; + uint16_t active_zones = 0; + + for (int i = 0; i < 10; i++) { + int amplitude = p_amplitudes[i]; + ERR_FAIL_COND_MSG(amplitude < 0 || amplitude > 8, + vformat("The value of parameter amplitude[%d] must be between 0 and 8.", i)); + if (amplitude > 0) { + uint8_t strength_value = (uint8_t)((amplitude - 1) * 0x07); + strength_zones |= (uint32_t)(strength_value << (3 * i)); + active_zones |= (uint16_t)(1 << i); + } + } + + values[0] = 0x26; + values[1] = (uint8_t)((active_zones >> 0) & 0xFF); + values[2] = (uint8_t)((active_zones >> 8) & 0xFF); + values[3] = (uint8_t)((strength_zones >> 0) & 0xFF); + values[4] = (uint8_t)((strength_zones >> 8) & 0xFF); + values[5] = (uint8_t)((strength_zones >> 16) & 0xFF); + values[6] = (uint8_t)((strength_zones >> 24) & 0xFF); + values[9] = (uint8_t)p_frequency; + + DUALSENSE_SEND_EFFECT; +} + +void Input::start_joy_triggers_vibration(int p_device, float p_left_intensity, float p_right_intensity, float p_duration) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_MSG(p_left_intensity < 0 || p_left_intensity > 1, + "The value of parameter \"left_intensity\" must be between 0.0 and 1.0."); + ERR_FAIL_COND_MSG(p_right_intensity < 0 || p_right_intensity > 1, + "The value of parameter \"right_intensity\" must be between 0.0 and 1.0."); + ERR_FAIL_COND_MSG(p_duration < 0, + "The value of parameter \"duration\" must be greater than 0."); + + SDL_ENABLED_CALL(JoypadSDL::get_singleton()->start_triggers_vibration(p_device, p_left_intensity, p_right_intensity, p_duration), (void)0); +} + +void Input::stop_joy_triggers_vibration(int p_device) { + _THREAD_SAFE_METHOD_ + SDL_ENABLED_CALL(JoypadSDL::get_singleton()->start_triggers_vibration(p_device, 0, 0, 0), (void)0); +} + +bool Input::send_joy_packet(int p_device, PackedByteArray p_packet) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V_MSG(!HAVE_SDL_HIDAPI, false, "Sending custom joypad packets is not supported on this platform."); + + if (p_packet.size() == 0) { + WARN_PRINT("Packet size is 0. Skipping sending the packet."); + return false; + } + + return SDL_ENABLED_CALL(JoypadSDL::get_singleton()->send_effect(p_device, p_packet.ptr(), p_packet.size()), false); +} + void Input::set_gravity(const Vector3 &p_gravity) { _THREAD_SAFE_METHOD_ diff --git a/core/input/input.h b/core/input/input.h index 10d357a1f92d..fa3f9e81ae0e 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -146,6 +146,42 @@ class Input : public Object { HashMap joy_vibration; + struct MotionInfo { + bool has_accelerometer = false; + bool has_gyroscope = false; + bool accelerometer_enabled = false; + bool gyroscope_enabled = false; + float sensor_data_rate = 0.0f; + Vector3 accelerometer; + Vector3 gravity; + Vector3 gyroscope; + struct { + bool in_progress = false; + bool calibrated = false; + Vector accelerometer_steps; + Vector gyroscope_steps; + Vector3 accelerometer_offset; + Vector3 gyroscope_offset; + float accelerometer_deadzone = 0.0f; + float gyroscope_deadzone = 0.0f; + } calibration; + }; + + HashMap joy_motion; + + struct TouchpadFingerInfo { + Vector2 position = Vector2(); + float pressure = 0.0f; + }; + + struct TouchpadInfo { + int num_touchpads = 0; + // The first int index refers to touchpad ID, the second one refers to finger ID on that touchpad. + HashMap> touchpad_fingers; + }; + + HashMap joy_touch; + struct VelocityTrack { uint64_t last_tick = 0; Vector2 velocity; @@ -171,6 +207,14 @@ class Input : public Object { int mapping = -1; int hat_current = 0; Dictionary info; + JoyModel model = JoyModel::INVALID; + JoyDeviceType device_type = JoyDeviceType::INVALID; + int num_buttons = -1; + int num_axes = -1; + bool has_light = false; + int battery_percent = -1; + JoyPowerState power_state = JoyPowerState::INVALID; + JoyConnectionState connection_state = JoyConnectionState::INVALID; }; VelocityTrack mouse_velocity_track; @@ -260,6 +304,7 @@ class Input : public Object { #endif friend class DisplayServer; + friend class JoypadSDL; static void (*set_mouse_mode_func)(MouseMode); static MouseMode (*get_mouse_mode_func)(); @@ -325,6 +370,49 @@ class Input : public Object { Vector3 get_magnetometer() const; Vector3 get_gyroscope() const; + bool is_joy_accelerometer_enabled(int p_device) const; + bool is_joy_gyroscope_enabled(int p_device) const; + + Vector3 get_joy_accelerometer(int p_device) const; + Vector3 get_joy_gravity(int p_device) const; + Vector3 get_joy_gyroscope(int p_device) const; + + float get_joy_sensor_rate(int p_device) const; + + JoyModel get_joy_model(int p_device) const; + JoyScheme get_joy_scheme(int p_device) const; + JoyDeviceType get_joy_device_type(int p_device) const; + + JoyPowerState get_joy_power_state(int p_device) const; + int get_joy_battery_percent(int p_device) const; + JoyConnectionState get_joy_connection_state(int p_device) const; + + String get_joy_axis_string(int p_device, JoyAxis p_axis) const; + String get_joy_button_string(int p_device, JoyButton p_button) const; + String get_joy_model_axis_string(JoyModel p_model, JoyAxis p_axis) const; + String get_joy_model_button_string(JoyModel p_model, JoyButton p_button) const; + bool has_joy_axis(int p_device, JoyAxis p_axis) const; + bool has_joy_button(int p_device, JoyButton p_button) const; + + Vector2 get_joy_touchpad_finger_position(int p_device, int p_touchpad, int p_finger) const; + float get_joy_touchpad_finger_pressure(int p_device, int p_touchpad, int p_finger) const; + TypedArray get_joy_touchpad_fingers(int p_device, int p_touchpad) const; + + int get_joy_num_buttons(int p_device) const; + int get_joy_num_axes(int p_device) const; + int get_joy_num_touchpads(int p_device) const; + + void start_joy_motion_calibration(int p_device); + void step_joy_motion_calibration(int p_device); + void stop_joy_motion_calibration(int p_device); + void clear_joy_motion_calibration(int p_device); + + Dictionary get_joy_motion_calibration(int p_device) const; + void set_joy_motion_calibration(int p_device, Dictionary p_calibration_info); + + bool is_joy_motion_calibrated(int p_device) const; + bool is_joy_motion_calibrating(int p_device) const; + Point2 get_mouse_position() const; Vector2 get_last_mouse_velocity(); Vector2 get_last_mouse_screen_velocity(); @@ -341,10 +429,40 @@ class Input : public Object { void set_gyroscope(const Vector3 &p_gyroscope); void set_joy_axis(int p_device, JoyAxis p_axis, float p_value); + bool set_joy_light(int p_device, Color p_color); + + bool set_joy_accelerometer_enabled(int p_device, bool p_enable); + bool set_joy_gyroscope_enabled(int p_device, bool p_enable); + + void set_joy_accelerometer(int p_device, const Vector3 &p_value); + void set_joy_gyroscope(int p_device, const Vector3 &p_value); + + void set_joy_sensor_rate(int p_device, float p_rate); + void set_joy_power_info(int p_device, JoyPowerState p_power_state, int p_power_percent); + + void set_joy_touchpad_finger(int p_device, int p_touchpad, int p_finger, float p_pressure, Vector2 p_value); + + bool has_joy_light(int p_device) const; + bool has_joy_accelerometer(int p_device) const; + bool has_joy_gyroscope(int p_device) const; + void start_joy_vibration(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration = 0); void stop_joy_vibration(int p_device); void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0); + void joy_adaptive_triggers_off(int p_device, JoyAxis p_axis); + void joy_adaptive_triggers_feedback(int p_device, JoyAxis p_axis, int p_position, int p_strength); + void joy_adaptive_triggers_weapon(int p_device, JoyAxis p_axis, int p_start_position, int p_end_position, int p_strength); + void joy_adaptive_triggers_vibration(int p_device, JoyAxis p_axis, int p_position, int p_amplitude, int p_frequency); + void joy_adaptive_triggers_multi_feedback(int p_device, JoyAxis p_axis, TypedArray p_strengths); + void joy_adaptive_triggers_slope_feedback(int p_device, JoyAxis p_axis, int p_start_position, int p_end_position, int p_start_strength, int p_end_strength); + void joy_adaptive_triggers_multi_vibration(int p_device, JoyAxis p_axis, int p_frequency, TypedArray p_amplitudes); + + void start_joy_triggers_vibration(int p_device, float p_left_intensity, float p_right_intensity, float p_duration); + void stop_joy_triggers_vibration(int p_device); + + bool send_joy_packet(int p_device, PackedByteArray p_packet); + void set_mouse_position(const Point2 &p_posf); void action_press(const StringName &p_action, float p_strength = 1.f); diff --git a/core/input/input_enums.h b/core/input/input_enums.h index 27508cce464a..bcd313bd87bb 100644 --- a/core/input/input_enums.h +++ b/core/input/input_enums.h @@ -104,6 +104,65 @@ enum class JoyButton { MAX = 128, // Android supports up to 36 buttons. DirectInput supports up to 128 buttons. }; +// See SDL_GamepadType +enum class JoyModel { + INVALID = -1, + UNKNOWN, + STANDARD, + XBOX360, + XBOXONE, + PS3, + PS4, + PS5, + SWITCH_PRO, + JOYCON_LEFT, + JOYCON_RIGHT, + JOYCON_PAIR, +}; + +enum class JoyScheme { + INVALID = -1, + UNKNOWN, + STANDARD, + XBOX, + PLAYSTATION, + NINTENDO, + JOYCON_HORIZONTAL, +}; + +// See SDL_JoystickType (which is not the same as SDL_GamepadType) +enum class JoyDeviceType { + INVALID = -1, + UNKNOWN, + GAMEPAD, + WHEEL, + ARCADE_STICK, + FLIGHT_STICK, + DANCE_PAD, + GUITAR, + DRUM_KIT, + ARCADE_PAD, + THROTTLE, +}; + +// See SDL_PowerState +enum class JoyPowerState { + INVALID = -1, + UNKNOWN, + ON_BATTERY, + NO_BATTERY, + CHARGING, + FULL_BATTERY, +}; + +// See SDL_JoystickConnectionState +enum class JoyConnectionState { + INVALID = -1, + UNKNOWN, + WIRED, + WIRELESS, +}; + enum class MIDIMessage { NONE = 0, NOTE_OFF = 0x8, diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h index 1c3b09569d0d..cc199c64dec9 100644 --- a/core/variant/binder_common.h +++ b/core/variant/binder_common.h @@ -104,6 +104,11 @@ VARIANT_ENUM_CAST(HatDir); VARIANT_BITFIELD_CAST(HatMask); VARIANT_ENUM_CAST(JoyAxis); VARIANT_ENUM_CAST(JoyButton); +VARIANT_ENUM_CAST(JoyModel); +VARIANT_ENUM_CAST(JoyScheme); +VARIANT_ENUM_CAST(JoyDeviceType); +VARIANT_ENUM_CAST(JoyPowerState); +VARIANT_ENUM_CAST(JoyConnectionState); VARIANT_ENUM_CAST(MIDIMessage); VARIANT_ENUM_CAST(MouseButton); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index f04f500f823c..9b9f66dba776 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2442,6 +2442,33 @@ Extra mouse button 2 mask. + + An invalid game controller axis. + + + Game controller left joystick x-axis. + + + Game controller left joystick y-axis. + + + Game controller right joystick x-axis. + + + Game controller right joystick y-axis. + + + Game controller left trigger axis. + + + Game controller right trigger axis. + + + The number of SDL game controller axes. + + + The maximum number of game controller axes: OpenVR supports up to 5 Joysticks making a total of 10 axes. + An invalid game controller button. @@ -2517,32 +2544,192 @@ - [b]Linux:[/b] Up to 80 buttons. - [b]Windows[/b] and [b]macOS:[/b] Up to 128 buttons. - - An invalid game controller axis. - - - Game controller left joystick x-axis. - - - Game controller left joystick y-axis. - - - Game controller right joystick x-axis. - - - Game controller right joystick y-axis. - - - Game controller left trigger axis. - - - Game controller right trigger axis. - - - The number of SDL game controller axes. - - - The maximum number of game controller axes: OpenVR supports up to 5 Joysticks making a total of 10 axes. + + An invalid game controller model. + + + An unknown game controller model. The game controller with this model ID is a valid controller, + but Godot doesn't have a mapping for it inside of controller database. + + + A standard game controller model with the button layout similar to an Xbox 360 controller. + A game controller with this model ID has A/B/X/Y face buttons, + a directional pad, and it might have Back/Guide/Start buttons, left/right shoulder buttons and triggers. + + + A game controller model that represents the Xbox 360 controller. + A game controller with this model ID has A/B/X/Y face buttons, + a directional pad, left and right sticks, Back/Guide/Start buttons, + LB/RB shoulder buttons and LT/RT trigger axes. + + + A game controller model that represents the Xbox One controller. + A game controller with this model ID has A/B/X/Y face buttons, + a directional pad, left and right sticks, View/Xbox/Menu buttons, + LB/RB shoulder buttons and LT/RT trigger axes. + + + A game controller model that represents the PlayStation 3 (Dualshock 3) controller. + A game controller with this model ID has Cross/Circle/Square/Triangle face buttons, + a directional pad, left and right sticks, Select/PS/Start buttons, + L1/R1 shoulder buttons, L2/R2 trigger axes, accelerometer and gyroscope sensors. + + + A game controller model that represents the PlayStation 4 (Dualshock 4) controller. + A game controller with this layout scheme has Cross/Circle/Square/Triangle face buttons, + a directional pad, left and right sticks, Share/PS/Options buttons, + L1/R1 shoulder buttons, L2/R2 trigger axes, accelerometer and gyroscope sensors and a touchpad. + + + A game controller model that represents the PlayStation 5 (Dualsense) controller. + A game controller with this layout scheme has Cross/Circle/Square/Triangle face buttons, + a directional pad, left and right sticks, Share/PS/Options buttons, + L1/R1 shoulder buttons, L2/R2 trigger axes, accelerometer and gyroscope sensors, a touchpad + and adaptive triggers. + + + A game controller model that represents the Nintendo Switch Pro Controller. + A game controller with this model ID has B/A/Y/X face buttons, + a directional pad, left and right sticks, Minus/Home/Plus buttons, + L/R shoulder buttons, ZL/ZR trigger axes (with a value of either 0 or 1) + a Capture button, accelerometer and gyroscope sensors. + + + A game controller model that represents the left Nintendo Switch Joycon that is held horizontally. + A game controller with this model ID has Face South/Face East/Face West/Face North face buttons, + a left stick, SL and SR buttons, Minus/Capture/L/ZL buttons, accelerometer and gyroscope sensors. + + + A game controller model that represents the right Nintendo Switch Joycon that is held horizontally. + A game controller with this model ID has Face South/Face East/Face West/Face North face buttons, + a left stick, SL and SR buttons, Plus/Home/R/ZR buttons, accelerometer and gyroscope sensors. + + + A game controller model that represents the pair of Nintendo Switch Joycon controllers, + with a similar layout to Nintendo Switch Pro Controller. + A game controller with this model ID has B/A/Y/X face buttons, + a directional pad, left and right sticks, Minus/Home/Plus buttons, + L/R shoulder buttons, ZL/ZR trigger axes (with a value of either 0 or 1) + a Capture button, accelerometer and gyroscope sensors (the sensors of the right Joycon controller are being used). + + + An invalid game controller layout scheme. + + + An unknown game controller layout scheme. + + + A standard game controller layout scheme similar to the Xbox controller layout. + A game controller with this layout scheme has A/B/X/Y face buttons, + a directional pad, and it might have Back/Guide/Start buttons, left/right shoulder buttons and triggers. + + + A game controller layout scheme that represents the Xbox controller layout. + A game controller with this layout scheme has A/B/X/Y face buttons, + a directional pad, left and right sticks, Back/Guide/Start (or View/Xbox/Menu) buttons, + LB/RB shoulder buttons and LT/RT trigger axes. + + + A game controller layout scheme that represents the PlayStation controller layout. + A game controller with this layout scheme has Cross/Circle/Square/Triangle face buttons, + a directional pad, left and right sticks, Select/PS/Start (or Share/PS/Options) buttons, + L1/R1 shoulder buttons, L2/R2 trigger axes and a touchpad in case of PS4 and PS5 controllers. + + + A game controller layout scheme that represents the Nintendo controller layout. + A game controller with this layout scheme has B/A/Y/X face buttons, + a directional pad, left and right sticks, Minus/Home/Plus buttons, + L/R shoulder buttons, ZL/ZR trigger axes (with a value of either 0 or 1) + and possibly a Capture button in case of Nintendo Switch Pro Controller. + + + A game controller layout scheme that represents the layout of a + Nintendo Switch Joycon controller that is being held horizontally. + A game controller with this layout scheme has Face South/Face East/Face West/Face North face buttons, + a left stick, SL and SR buttons and either Minus/Capture/L/ZL or Plus/Home/R/ZR buttons. + + + An invalid joy device type. + + + An unknown joy device type. + + + Gamepad controller. + Includes Left and Right Sticks, Left and Right Triggers, Directional Pad, + and all standard buttons (A, B, X, Y, START, BACK, LB, RB, LSB, RSB). + + + Racing wheel controller. + Left Stick X reports the wheel rotation, Right Trigger is the acceleration pedal, and Left Trigger is the brake pedal. + Includes Directional Pad and most standard buttons (A, B, X, Y, START, BACK, LB, RB). LSB and RSB are optional. + + + Arcade stick controller. + Includes a Digital Stick that reports as a DPAD (up, down, left, right), and most standard buttons (A, B, X, Y, START, BACK). + The Left and Right Triggers are implemented as digital buttons and report either 0 or 1. LB, LSB, RB, and RSB are optional. + + + Flight stick controller. + Includes a pitch and roll stick that reports as the Left Stick, a POV Hat which reports as the Right Stick, + a rudder (handle twist or rocker) that reports as Left Trigger, and a throttle control as the Right Trigger. + Includes support for a primary weapon (A), secondary weapon (B), and other standard buttons (X, Y, START, BACK). + LB, LSB, RB, and RSB are optional. + + + Dance pad controller. + Includes the Directional Pad and standard buttons (A, B, X, Y) on the pad, plus BACK and START. + + + Guitar controller. + The strum bar maps to DPAD (up and down), and the frets are assigned to A (green), B (red), Y (yellow), X (blue), and LB (orange). + Right Stick Y is associated with a vertical orientation sensor; Right Stick X is the whammy bar. + Includes support for BACK, START, DPAD (left, right). Left Trigger (pickup selector), Right Trigger, RB, LSB (fret modifier), RSB are optional. + + + Drum controller. + The drum pads are assigned to buttons: A for green (Floor Tom), B for red (Snare Drum), + X for blue (Low Tom), Y for yellow (High Tom), and LB for the pedal (Bass Drum). + Includes Directional-Pad, BACK, and START. RB, LSB, and RSB are optional. + + + Arcade pad controller. + Includes Directional Pad and most standard buttons (A, B, X, Y, START, BACK, LB, RB). + The Left and Right Triggers are implemented as digital buttons and report either 0 or 1. + Left Stick, Right Stick, LSB, and RSB are optional. + + + A throttle controller. + + + An invalid joypad power state. Most likely there was an error while determining the power status. + + + An unknown joypad power state. Godot cannot determine the power status of this joypad. + + + The joypad is not plugged in and is running on the battery. + + + The joypad is plugged in and has no battery available. + + + The joypad is plugged in and its battery is currently charging. + + + The joypad is plugged in and its battery is fully charged. + + + An invalid joypad connection state. Most likely there was an error while determining the connection state. + + + An unknown joypad connection state. + + + The joypad is currently using a wired connection to the computer. + + + The joypad is currently using a wireless connection to the computer. Does not correspond to any MIDI message. This is the default value of [member InputEventMIDI.message]. diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index 436215305b5e..3ac47439499f 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -38,6 +38,15 @@ Adds a new mapping entry (in SDL2 format) to the mapping database. Optionally update already connected devices. + + + + + Clears the calibration information about the specified joypad's motion sensors, if it has any and if they were calibrated. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + @@ -109,6 +118,24 @@ [b]Note:[/b] For Android, [member ProjectSettings.input_devices/sensors/enable_gyroscope] must be enabled. + + + + + Returns the requested joypad's accelerometer sensor readings in m/s², while trying to exclude the gravity part. + If the joypad doesn't have an accelerometer sensor or it's currently disabled, this method returns [constant Vector3.ZERO]. + For a joypad held in front of you, the returned axes are defined as follows: + -X ... +X : left ... right; + -Y ... +Y : bottom ... top; + -Z ... +Z : farther ... closer. + For the joypad to be able to send accelerometer data, the sensor must exist on the joypad and it must be enabled using [method set_joy_accelerometer_enabled]. + The gravity part alone can be retrieved using [method get_joy_gravity]. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] The gravity part may have inaccurate values if the joypad is being rotated, because Godot can't reliably retrieve it due to OS differences. + [b]Note:[/b] To get raw accelerometer readings (with the gravity part included), use [code]Input.get_joy_accelerometer(device) + Input.get_joy_gravity(device)[/code]. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + @@ -117,6 +144,68 @@ Returns the current value of the joypad axis at index [param axis]. + + + + + + Returns a joypad-specific axis name, or an empty string if the joypad doesn't have the specified axis or if Godot doesn't know anything about this joypad's axis names. + [b]Note:[/b] This method may not work exactly as expected if the joypad is not a first-party gamepad from PlayStation, Xbox or Nintendo, because Godot Engine may sometimes not be able to guess what the labels on the controller are. + [b]Note:[/b] This feature is not supported on the Web platform. + + + + + + + Returns the battery percentage of the requested joypad, if available. + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + + Returns a joypad-specific button name, or an empty string if the joypad doesn't have the specified button or if Godot doesn't know anything about this joypad's button names. + [b]Note:[/b] This method may not work exactly as expected if the joypad is not a first-party gamepad from PlayStation, Xbox or Nintendo, because Godot Engine may sometimes not be able to guess what the labels on the controller are. + [b]Note:[/b] This feature is not supported on the Web platform. + + + + + + + Returns the joypad's connection state (i.e. if it's wired or wireless). + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + Returns the joypad's device type (i.e. if it's a gamepad, a racing wheel controller, a dance pad, etc.). + [b]Note:[/b] This feature is not supported on the Web platform. + + + + + + + Returns the requested joypad's gravity value in m/s² using accelerometer readings. + If the joypad doesn't have an accelerometer sensor or it's currently disabled, this method returns [constant Vector3.ZERO]. + For a joypad held in front of you, the returned axes are defined as follows: + -X ... +X : left ... right; + -Y ... +Y : bottom ... top; + -Z ... +Z : farther ... closer. + For the joypad to be able to send accelerometer (and gravity) data, the sensor must exist on the joypad and it must be enabled using [method set_joy_accelerometer_enabled]. + The main accelerometer part alone can be retrieved using [method get_joy_accelerometer]. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] The gravity part may have inaccurate values if the joypad is being rotated, because Godot can't reliably retrieve it due to OS differences. + [b]Note:[/b] If you want to know how Godot Engine separates accelerometer movement from the gravity, Godot Engine uses a low-pass filter to isolate the force of gravity. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + @@ -125,6 +214,21 @@ On Windows, all XInput joypad GUIDs will be overridden by Godot to [code]__XINPUT_DEVICE__[/code], because their mappings are the same. + + + + + Returns the requested joypad's gyroscope sensor readings in rad/s. The rotation is positive in the clockwise direction. + If the joypad doesn't have a gyroscope sensor or it's currently disabled, this method returns [constant Vector3.ZERO]. + For a joypad held in front of you, the returned axes are defined as follows: + X : Angular speed around the x axis (pitch); + Y : Angular speed around the y axis (yaw); + Z : Angular speed around the z axis (roll). + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] For the joypad to be able to send gyroscope data, the sensor must exist on the joypad and it must be enabled using [method set_joy_gyroscope_enabled]. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + @@ -142,6 +246,48 @@ [b]Note:[/b] The returned dictionary is always empty on Web, iOS, Android, and macOS. + + + + + Returns the joypad's model type (i.e. if it's an Xbox 360 controller, a Dualshock 4, a Nintendo Switch Pro Controller, etc.). + [b]Note:[/b] This feature is not supported on the Web platform. + + + + + + + + Returns the name of the specified axis for the specified joypad model, if that model has the specified axis, otherwise returns the generic name for that axis or an empty string if the axis ID is invalid. + [b]Note:[/b] This method may not work exactly as expected if the joypad is not a first-party gamepad from PlayStation, Xbox or Nintendo, because Godot Engine may sometimes not be able to guess what the labels on the controller are. + [b]Note:[/b] This feature is not supported on the Web platform. + + + + + + + + Returns the name of the specified button for the specified joypad model, if that model has the specified button, otherwise returns the generic name for that button or an empty string if the button ID is invalid. + [b]Note:[/b] This method may not work exactly as expected if the joypad is not a first-party gamepad from PlayStation, Xbox or Nintendo, because Godot Engine may sometimes not be able to guess what the labels on the controller are. + [b]Note:[/b] This feature is not supported on the Web platform. + + + + + + + Returns the calibration information about the specified joypad's motion sensors in form of a [Dictionary], if it has any and if they have been calibrated, otherwise returns an empty [Dictionary]. + The dictionary contains the following fields: + [code]accelerometer_offset[/code]: average offset in accelerometer value readings from [constant Vector2.ZERO]. + [code]accelerometer_deadzone[/code]: the accelerometer's axes deadzone, or the average dispersion of the accelerometer readings. + [code]gyroscope_offset[/code]: average offset in gyroscope value readings from [constant Vector2.ZERO]. + [code]gyroscope_deadzone[/code]: the gyroscope's axes deadzone, or the average dispersion of the gyroscope readings. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + @@ -149,6 +295,89 @@ Returns the name of the joypad at the specified device index, e.g. [code]PS4 Controller[/code]. Godot uses the [url=https://github.com/gabomdq/SDL_GameControllerDB]SDL2 game controller database[/url] to determine gamepad names. + + + + + Returns the number of axes on the joypad. + [b]Note:[/b] This method may return inaccurate results in case if this joypad is not in Godot's controller database. + + + + + + + Returns the number of buttons on the joypad. + [b]Note:[/b] This method may return inaccurate results in case if this joypad is not in Godot's controller database. + + + + + + + Returns the number of touchpads on the joypad. + [b]Note:[/b] This method may return inaccurate results in case if this joypad is not in Godot's controller database. + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + Returns the joypad's power state (i.e. if it's currently charging, being used on battery, etc.). + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + Returns the joypad's scheme layout (i.e. if it's an Xbox controller, a PlayStation controller, a Nintendo controller, etc.). + [b]Note:[/b] This feature is not supported on the Web platform. + + + + + + + Returns the joypad's motion sensor rate in Hz, if the joypad has motion sensors. + [b]Note:[/b] For the joypad to be able to send sensor rate data, the motion sensors must exist on the joypad and they must be enabled using [method set_joy_accelerometer_enabled] or [method set_joy_gyroscope_enabled]. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + + + + + + + + Returns the position of the specified finger on the specified touchpad on the joypad. The X and Y values are in range between 0.0 and 1.0. + If the joypad doesn't have the specified touchpad or the specified finger is not currently touching the touchpad, this method returns [code]Vector2(-1.0, -1.0)[/code]. + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + + + Returns the pressure of the specified finger on the specified touchpad on the joypad. The pressure values are in range between 0.0 and 1.0. + If the joypad doesn't have the specified touchpad or the specified finger is not currently touching the touchpad, this method returns [code]0.0[/code]. + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + + Returns an array of finger IDs that are currently touching the specified touchpad on the joypad. + If the joypad doesn't have the specified touchpad or no fingers are currently touching this touchpad, this method returns an empty array. + [b]Note:[/b] To retrieve the positions of the touchpad fingers, use [method get_joy_touchpad_finger_position], and to retrieve their pressure (if supported), use [method get_joy_touchpad_finger_pressure]. + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + @@ -202,6 +431,48 @@ By default, the deadzone is automatically calculated from the average of the action deadzones. However, you can override the deadzone to be whatever you want (on the range of 0 to 1). + + + + + Returns true if the joypad has an accelerometer sensor. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + + + + + + + Returns [code]true[/code] if the specified axis has been mapped on the specified joypad, otherwise returns [code]false[/code]. + + + + + + + + Returns [code]true[/code] if the specified button has been mapped on the specified joypad, otherwise returns [code]false[/code]. + + + + + + + Returns true if the joypad has a gyroscope sensor. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + + + + + + Returns [code]true[/code] if the joypad has an LED light that can change colors and/or brightness, otherwise returns [code]false[/code]. + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + @@ -242,6 +513,16 @@ Returns [code]true[/code] if any action, key, joypad button, or mouse button is being pressed. This will also return [code]true[/code] if any action is simulated via code by calling [method action_press]. + + + + + Returns [code]true[/code] if the requested joypad has an accelerometer sensor and it is currently enabled, otherwise returns [code]false[/code]. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] Accelerometer sensor can be enabled using [method set_joy_accelerometer_enabled]. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + @@ -250,6 +531,16 @@ Returns [code]true[/code] if you are pressing the joypad button at index [param button]. + + + + + Returns [code]true[/code] if the requested joypad has a gyroscope sensor and it is currently enabled, otherwise returns [code]false[/code]. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] Gyroscope sensor can be enabled using [method set_joy_gyroscope_enabled]. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + @@ -257,6 +548,24 @@ Returns [code]true[/code] if the system knows the specified device. This means that it sets all button and axis indices. Unknown joypads are not expected to match these constants, but you can still retrieve events from them. + + + + + Returns [code]true[/code] if the joypad's motion sensors have been calibrated, otherwise [code]false[/code]. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + + + + + + Returns [code]true[/code] if the joypad's motion sensors are currently being calibrated, otherwise [code]false[/code]. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + @@ -289,6 +598,106 @@ [b]Note:[/b] Due to keyboard ghosting, [method is_physical_key_pressed] may return [code]false[/code] even if one of the action's keys is pressed. See [url=$DOCS_URL/tutorials/inputs/input_examples.html#keyboard-events]Input examples[/url] in the documentation for more information. + + + + + + + + Starts "Feedback" adaptive triggers effect on the specified joypad. This method will fail if the joypad is not DualSense. + Axis should be either [constant JOY_AXIS_TRIGGER_LEFT] for the left trigger or [constant JOY_AXIS_TRIGGER_RIGHT] for the right trigger. + [code skip-lint]position[/code] parameter can take values from 0 to 9 (inclusive). + [code skip-lint]strength[/code] parameter can take values from 0 to 8 (inclusive). + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + + + Starts "Multi Feedback" adaptive triggers effect on the specified joypad. This method will fail if the joypad is not DualSense. + Axis should be either [constant JOY_AXIS_TRIGGER_LEFT] for the left trigger or [constant JOY_AXIS_TRIGGER_RIGHT] for the right trigger. + [code skip-lint]strengths[/code] array can have at most 10 elements, each of them can take values from 0 to 8 (inclusive). + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + + + + Starts "Multi Vibration" adaptive triggers effect on the specified joypad. This method will fail if the joypad is not DualSense. + Axis should be either [constant JOY_AXIS_TRIGGER_LEFT] for the left trigger or [constant JOY_AXIS_TRIGGER_RIGHT] for the right trigger. + [code skip-lint]frequency[/code] parameter can take values from 0 to 255 (inclusive). + [code skip-lint]amplitudes[/code] array can have at most 10 elements, each of them can take values from 0 to 8 (inclusive). + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + + Stops adaptive triggers effects on the specified joypad. This method will fail if the joypad is not DualSense. + Axis should be either [constant JOY_AXIS_TRIGGER_LEFT] for the left trigger or [constant JOY_AXIS_TRIGGER_RIGHT] for the right trigger. + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + + + + + + Starts "Slope Feedback" adaptive triggers effect on the specified joypad. This method will fail if the joypad is not DualSense. + Axis should be either [constant JOY_AXIS_TRIGGER_LEFT] for the left trigger or [constant JOY_AXIS_TRIGGER_RIGHT] for the right trigger. + [code skip-lint]start_position[/code] parameter can take values from 0 to [code skip-lint]end_position[/code] (inclusive). + [code skip-lint]end_position[/code] parameter can take values from [code skip-lint]start_position+1[/code] to 9 (inclusive). + [code skip-lint]start_strength[/code] parameter can take values from 1 to 8 (inclusive). + [code skip-lint]end_strength[/code] parameter can take values from 1 to 8 (inclusive). + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + + + + + Starts "Vibration" adaptive triggers effect on the specified joypad. This method will fail if the joypad is not DualSense. + Axis should be either [constant JOY_AXIS_TRIGGER_LEFT] for the left trigger or [constant JOY_AXIS_TRIGGER_RIGHT] for the right trigger. + [code skip-lint]position[/code] parameter can take values from 0 to 9 (inclusive). + [code skip-lint]amplitude[/code] parameter can take values from 0 to 8 (inclusive). + [code skip-lint]frequency[/code] parameter can take values from 0 to 255 (inclusive). + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + + + + + Starts "Weapon" adaptive triggers effect on the specified joypad. This method will fail if the joypad is not DualSense. + Axis should be either [constant JOY_AXIS_TRIGGER_LEFT] for the left trigger or [constant JOY_AXIS_TRIGGER_RIGHT] for the right trigger. + [code skip-lint]start_position[/code] parameter can take values from 2 to 7 (inclusive). + [code skip-lint]end_position[/code] parameter can take values from [code skip-lint]start_position+1[/code] to 8 (inclusive). + [code skip-lint]strength[/code] parameter can take values from 0 to 8 (inclusive). + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + @@ -319,6 +728,15 @@ On Android, Godot will map to an internal fallback mapping. + + + + + + Send a custom controller-specific data packet to the specified joypad, if supported. Returns [code]true[/code] on success and [code]false[/code] on failure. + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + @@ -366,6 +784,45 @@ [b]Note:[/b] This value can be immediately overwritten by the hardware sensor value on Android and iOS. + + + + + + Enables or disables the accelerometer sensor, if available, on the specified joypad. If the operation was successful, returns [code]true[/code], otherwise returns [code]false[/code]. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + + + + + + + Enables or disables the gyroscope sensor, if available, on the specified joypad. If the operation was successful, returns [code]true[/code], otherwise returns [code]false[/code]. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + + + + + + + Sets the joypad's LED light, if available, to the specified color. If the operation was successful, returns [code]true[/code], otherwise returns [code]false[/code]. + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + + + + + + + Sets the specified joypad's calibration information. See also [method get_joy_motion_calibration]. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + [b]Note:[/b] Joypad motion sensors reading is only supported on Windows, Linux and macOS. + + @@ -383,6 +840,136 @@ [b]Note:[/b] Some 3rd party tools can contribute to the list of ignored devices. For example, [i]SteamInput[/i] creates virtual devices from physical devices for remapping purposes. To avoid handling the same input device twice, the original device is added to the ignore list. + + + + + Starts the process of calibrating the specified joypad's motion sensors (accelerometer and gyroscope), if it has any. + After a joypad's motion sensors are calibrated, they will show values close or equal to [constant Vector3.ZERO] when the joypad is not moved. + Here's an example code on how to use joypad joypad motion sensors and calibration in your games: + [codeblocks] + [gdscript] + func _ready(): + # If the game window is starting, the joypads might not be available in the _ready method yet, so we wait + await get_tree().create_timer(0.5).timeout + + # In this example we only use the first connected joypad (id 0) + if 0 not in Input.get_connected_joypads(): + return + + # This example makes use of the accelerometer and gyroscope + if not Input.has_joy_accelerometer(0) or not Input.has_joy_gyroscope(0): + return + + # We must enable the accelerometer and the gyroscope before using them + Input.set_joy_accelerometer_enabled(0, true) + Input.set_joy_gyroscope_enabled(0, true) + + # (Tell the users here that they need to put their joypads on a flat surface and wait for confirmation) + + # Start the calibration process + calibrate_motion() + + func _process(): + # Only move the object if the joypad motion sensors are calibrated + if Input.is_joy_motion_calibrated(0): + move_object() + + func calibrate_motion(): + Input.start_joy_motion_calibration(0) + # Send 50 calibration samples in 2.5 seconds + for i in 50: + Input.step_joy_motion_calibration(0) + await get_tree().create_timer(0.05).timeout + Input.stop_joy_motion_calibration(0) + # The joypad is now calibrated + + func move_object(): + var object: Node2D = ... # Put your object here + object.position.x += Input.get_joy_accelerometer(0).x * 5 + object.position.y += Input.get_joy_accelerometer(0).y * 5 + + object.rotation += Input.get_joy_gyroscope(0).y * 5 + [/gdscript] + [csharp] + public override void _Ready() + { + // If the game window is starting, the joypads might not be available in the _ready method yet, so we wait + await ToSignal(GetTree().CreateTimer(0.5), "timeout"); + + // In this example we only use the first connected joypad (id 0) + if (Input.GetConnectedJoypads().Has(0)) + { + return; + } + + // This example makes use of the accelerometer and gyroscope + if (!Input.HasJoyAccelerometer(0) || !Input.HasJoyGyroscope(0)) + { + return; + } + + // We must enable the accelerometer and the gyroscope before using them + Input.SetJoyAccelerometerEnabled(0, true); + Input.SetJoyGyroscopeEnabled(0, true); + + // (Tell the users here that they need to put their joypads on a flat surface and wait for confirmation) + + // Start the calibration process + CalibrateMotion(); + } + + public override void _Process() + { + // Only move the object if the joypad motion sensors are calibrated + if (Input.IsJoyMotionCalibrated(0)) + { + MoveObject(); + } + } + + private void CalibrateMotion() + { + Input.StartJoyMotionCalibration(0); + // Send 50 calibration samples in 2.5 seconds + for (int i = 0; i < 50; i++) + { + Input.StepJoyMotionCalibration(0) + await ToSignal(GetTree().CreateTimer(0.05), "timeout"); + } + Input.StopJoyMotionCalibration(0) + // The joypad is now calibrated + } + + private void MoveObject() + { + Node2D object = ... ; # Put your object here + Vector2 position = object.Position; + position.x += Input.GetJoyAccelerometer(0).X * 5; + position.y += Input.GetJoyAccelerometer(0).Y * 5; + object.Position = position; + + object.Rotation += Input.GetJoyGyroscope(0).Y * 5; + } + [/csharp] + [/codeblocks] + + + + + + + + + + Starts triggers vibration (trigger rumble) on the specified joypad, if available. Not to be confused with DualSense's adaptive triggers, which can be used with methods such as [method joy_adaptive_triggers_feedback]. + [code skip-lint]left_intensity[/code] parameter can take values from 0.0 to 1.0 (inclusive). + [code skip-lint]right_intensity[/code] parameter can take values from 0.0 to 1.0 (inclusive). + [code skip-lint]duration[/code] parameter can take values that are greater than 0.0. + [b]Note:[/b] This feature is only supported on Xbox One controllers. + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + @@ -395,6 +982,31 @@ [b]Note:[/b] For macOS, vibration is only supported in macOS 11 and later. + + + + + Saves the current state of the specified joypad's motion sensors readings for the calibration process. The values added into a special list that will be used to adjust the motion sensors readings. + For more precision, this method should be called at least 10 times in the calibration process so the engine can adjust the motion sensors readings based on at least 10 different values, otherwise a warning will be printed. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + + + + + + + Stops the calibration process of the specified joypad's motion sensors. + See [method start_joy_motion_calibration] for an example on how to use joypad motion sensors and calibration in your games. + + + + + + + Stops triggers vibration (trigger rumble) on the specified joypad, if available. See [method start_joy_triggers_vibration]. Not to be confused with DualSense's adaptive triggers, which can be used with methods such as [method joy_adaptive_triggers_feedback]. + [b]Note:[/b] This feature is only supported on Windows, Linux and macOS. + + diff --git a/drivers/sdl/SCsub b/drivers/sdl/SCsub index 4474a9806817..704b23f17aca 100644 --- a/drivers/sdl/SCsub +++ b/drivers/sdl/SCsub @@ -182,6 +182,7 @@ if env["builtin_sdl"]: "joystick/windows/SDL_xinputjoystick.c", "thread/generic/SDL_syscond.c", "thread/generic/SDL_sysrwlock.c", + "sensor/windows/SDL_windowssensor.c", "thread/windows/SDL_syscond_cv.c", "thread/windows/SDL_sysmutex.c", "thread/windows/SDL_sysrwlock_srw.c", diff --git a/drivers/sdl/SDL_build_config_private.h b/drivers/sdl/SDL_build_config_private.h index 49eca6619a60..aa010795828e 100644 --- a/drivers/sdl/SDL_build_config_private.h +++ b/drivers/sdl/SDL_build_config_private.h @@ -47,7 +47,6 @@ #define SDL_DIALOG_DISABLED 1 #define SDL_FILESYSTEM_DUMMY 1 #define SDL_FSOPS_DUMMY 1 -#define SDL_SENSOR_DISABLED 1 #define SDL_GPU_DISABLED 1 #define SDL_RENDER_DISABLED 1 #define SDL_POWER_DISABLED 1 @@ -73,6 +72,7 @@ #define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1 #define SDL_THREAD_WINDOWS 1 #define SDL_TIMER_WINDOWS 1 +#define SDL_SENSOR_WINDOWS 1 // Linux defines #elif defined(SDL_PLATFORM_LINUX) @@ -110,8 +110,12 @@ #define SDL_HAPTIC_LINUX 1 #define SDL_TIMER_UNIX 1 #define SDL_JOYSTICK_LINUX 1 +#define SDL_JOYSTICK_HIDAPI 1 #define SDL_INPUT_LINUXEV 1 #define SDL_THREAD_PTHREAD 1 +#define DEBUG_HIDAPI +#define DEBUG_INPUT_EVENTS +#define DEBUG_JOYSTICK // MacOS defines #elif defined(SDL_PLATFORM_MACOS) @@ -123,8 +127,10 @@ #define SDL_HAPTIC_IOKIT 1 #define SDL_JOYSTICK_IOKIT 1 #define SDL_JOYSTICK_MFI 1 +#define SDL_JOYSTICK_HIDAPI 1 #define SDL_TIMER_UNIX 1 #define SDL_THREAD_PTHREAD 1 +#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1 // Other platforms are not supported (for now) #else diff --git a/drivers/sdl/joypad_sdl.cpp b/drivers/sdl/joypad_sdl.cpp index 693b4a216944..f0fc8b5d0688 100644 --- a/drivers/sdl/joypad_sdl.cpp +++ b/drivers/sdl/joypad_sdl.cpp @@ -44,6 +44,7 @@ #include #include #include +#include JoypadSDL *JoypadSDL::singleton = nullptr; @@ -74,7 +75,12 @@ JoypadSDL *JoypadSDL::get_singleton() { return singleton; } +static void SDLCALL sdl_log(void *userdata, int category, SDL_LogPriority priority, const char *message) { + print_verbose(vformat("SDL Debug (priority: %d): %s", priority, message)); +} + Error JoypadSDL::initialize() { + SDL_SetLogOutputFunction(sdl_log, nullptr); SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1"); SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); ERR_FAIL_COND_V_MSG(!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD), FAILED, SDL_GetError()); @@ -213,6 +219,15 @@ void JoypadSDL::process_events() { ); break; + case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: + // Gamepads also can have battery, so no SKIP_EVENT_FOR_GAMEPAD here + + Input::get_singleton()->set_joy_power_info( + joy_id, + static_cast(sdl_event.jbattery.state), + sdl_event.jbattery.percent); + break; + case SDL_EVENT_GAMEPAD_AXIS_MOTION: { float axis_value; @@ -239,6 +254,44 @@ void JoypadSDL::process_events() { static_cast(sdl_event.gbutton.button), // Godot button constants are intentionally the same as SDL's, so we can just straight up use them sdl_event.gbutton.down); break; + + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: { + // Godot currently doesn't support anything other than the main accelerometer and the main gyroscope sensors + if (sdl_event.gsensor.sensor != SDL_SENSOR_ACCEL && sdl_event.gsensor.sensor != SDL_SENSOR_GYRO) { + continue; + } + + Vector3 value = Vector3( + sdl_event.gsensor.data[0], + sdl_event.gsensor.data[1], + sdl_event.gsensor.data[2]); + + if (sdl_event.gsensor.sensor == SDL_SENSOR_ACCEL) { + Input::get_singleton()->set_joy_accelerometer(joy_id, value); + } else if (sdl_event.gsensor.sensor == SDL_SENSOR_GYRO) { + // By default the rotation is positive in the counter-clockwise direction. + // We revert it here to be positive in the clockwise direction. + Input::get_singleton()->set_joy_gyroscope(joy_id, -value); + } + + float data_rate = SDL_GetGamepadSensorDataRate( + SDL_GetGamepadFromID(sdl_event.gsensor.which), + (SDL_SensorType)sdl_event.gsensor.sensor); + + // Data rate for all sensors should be the same + Input::get_singleton()->set_joy_sensor_rate(joy_id, data_rate); + } break; + + case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: + case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: + case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: + Input::get_singleton()->set_joy_touchpad_finger( + joy_id, + sdl_event.gtouchpad.touchpad, + sdl_event.gtouchpad.finger, + sdl_event.gtouchpad.pressure, + Vector2(sdl_event.gtouchpad.x, sdl_event.gtouchpad.y)); + break; } } } @@ -255,6 +308,403 @@ void JoypadSDL::setup_sdl_helper_window(HWND p_hwnd) { } #endif +bool JoypadSDL::enable_accelerometer(int p_pad_idx, bool p_enable) { + bool result = SDL_SetGamepadSensorEnabled(get_sdl_gamepad(p_pad_idx), SDL_SENSOR_ACCEL, p_enable); + if (!result) { + print_verbose(vformat("Error while trying to enable joypad accelerometer: %s", SDL_GetError())); + } + return result; +} + +bool JoypadSDL::enable_gyroscope(int p_pad_idx, bool p_enable) { + bool result = SDL_SetGamepadSensorEnabled(get_sdl_gamepad(p_pad_idx), SDL_SENSOR_GYRO, p_enable); + if (!result) { + print_verbose(vformat("Error while trying to enable joypad gyroscope: %s", SDL_GetError())); + } + return result; +} + +bool JoypadSDL::set_light(int p_pad_idx, Color p_color) { + Color linear = p_color.srgb_to_linear(); + return SDL_SetJoystickLED(get_sdl_joystick(p_pad_idx), linear.get_r8(), linear.get_g8(), linear.get_b8()); +} + +bool JoypadSDL::has_joy_axis(int p_pad_idx, JoyAxis p_axis) const { + SDL_Gamepad *gamepad = get_sdl_gamepad(p_pad_idx); + if (gamepad != nullptr) { + return SDL_GamepadHasAxis(gamepad, static_cast(p_axis)); + } + + SDL_Joystick *joystick = get_sdl_joystick(p_pad_idx); + if (joystick != nullptr) { + return (int)p_axis >= 0 && (int)p_axis < SDL_GetNumJoystickAxes(joystick); + } + + return false; +} + +bool JoypadSDL::has_joy_button(int p_pad_idx, JoyButton p_button) const { + SDL_Gamepad *gamepad = get_sdl_gamepad(p_pad_idx); + if (gamepad != nullptr) { + return SDL_GamepadHasButton(gamepad, static_cast(p_button)); + } + + SDL_Joystick *joystick = get_sdl_joystick(p_pad_idx); + if (joystick != nullptr) { + return (int)p_button >= 0 && (int)p_button < SDL_GetNumJoystickButtons(joystick); + } + + return false; +} + +String JoypadSDL::get_model_axis_string(JoyModel p_model, JoyAxis p_axis) { + if (p_model == JoyModel::INVALID || p_model == JoyModel::UNKNOWN) { + return ""; + } + + SDL_GamepadType gamepad_type = static_cast(p_model); + + switch (p_axis) { + case JoyAxis::LEFT_X: + return "Left Stick X"; + case JoyAxis::LEFT_Y: + return "Left Stick Y"; + case JoyAxis::RIGHT_X: + return "Right Stick X"; + case JoyAxis::RIGHT_Y: + return "Right Stick Y"; + + case JoyAxis::TRIGGER_LEFT: + case JoyAxis::TRIGGER_RIGHT: + switch (gamepad_type) { + case SDL_GAMEPAD_TYPE_XBOX360: + case SDL_GAMEPAD_TYPE_XBOXONE: + return p_axis == JoyAxis::TRIGGER_LEFT ? "LT" : "RT"; + + case SDL_GAMEPAD_TYPE_PS3: + case SDL_GAMEPAD_TYPE_PS4: + case SDL_GAMEPAD_TYPE_PS5: + return p_axis == JoyAxis::TRIGGER_LEFT ? "L2" : "R2"; + + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + //case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: // Horizontal joycons don't have "trigger" buttons + //case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + return p_axis == JoyAxis::TRIGGER_LEFT ? "ZL" : "ZR"; + + default: + return p_axis == JoyAxis::TRIGGER_LEFT ? "Left Trigger" : "Right Trigger"; + } + + default: + break; + } + + return ""; +} + +static const char *face_buttons_strings[] = { + // Xbox/Nintendo names + "A", + "B", + "X", + "Y", + // PlayStation names + "Cross", + "Circle", + "Square", + "Triangle", +}; + +static const char *horiz_joycon_face_buttons_strings[] = { + // Horizontal joycons names + "Face South", + "Face East", + "Face West", + "Face North", +}; + +static const char *xb360_buttons[] = { + "Back", + "Guide", + "Start", +}; + +static const char *xbone_buttons[] = { + "View", + "Xbox", + "Menu", +}; + +static const char *ps3_buttons[] = { + "Select", + "PS", + "Start", +}; + +static const char *ps45_buttons[] = { + "Share", + "PS", + "Options", +}; + +static const char *switch_pro_buttons[] = { + "Minus", + "Home", + "Plus", +}; + +static const char *default_paddles[] = { + "Paddle 1", + "Paddle 2", + "Paddle 3", + "Paddle 4", +}; + +static const char *dualsense_edge_paddles[] = { + "Left Function", + "Right Function", + "Left Paddle", + "Right Paddle", +}; + +static const char *joycon_paddles[] = { + "SR (R)", + "SL (L)", + "SL (R)", + "SR (L)", +}; + +static const char *joycon_horizontal_paddles[] = { + "R (R)", + "L (L)", + "ZR (R)", + "ZL (L)", +}; + +String JoypadSDL::get_model_button_string(JoyModel p_model, JoyButton p_button) { + if (p_model == JoyModel::INVALID || p_model == JoyModel::UNKNOWN) { + return ""; + } + + SDL_GamepadType gamepad_type = static_cast(p_model); + + switch (p_button) { + case JoyButton::A: + case JoyButton::B: + case JoyButton::X: + case JoyButton::Y: { + if (gamepad_type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT || gamepad_type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) { + return horiz_joycon_face_buttons_strings[(int)p_button - (int)JoyButton::A]; + } + + SDL_GamepadButtonLabel button_label = SDL_GetGamepadButtonLabelForType(gamepad_type, static_cast(p_button)); + + // button_label == SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN + // Will SDL add new values that are less than 0? Not sure, so we make a check here just in case. + if (button_label < SDL_GAMEPAD_BUTTON_LABEL_A || button_label > SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE) { + return ""; + } + return face_buttons_strings[button_label - SDL_GAMEPAD_BUTTON_LABEL_A]; + } + + case JoyButton::BACK: + case JoyButton::GUIDE: + case JoyButton::START: + switch (gamepad_type) { + default: + case SDL_GAMEPAD_TYPE_STANDARD: + case SDL_GAMEPAD_TYPE_XBOX360: + return xb360_buttons[(int)p_button - (int)JoyButton::BACK]; + + case SDL_GAMEPAD_TYPE_XBOXONE: + return xbone_buttons[(int)p_button - (int)JoyButton::BACK]; + + case SDL_GAMEPAD_TYPE_PS3: + return ps3_buttons[(int)p_button - (int)JoyButton::BACK]; + + case SDL_GAMEPAD_TYPE_PS4: + case SDL_GAMEPAD_TYPE_PS5: + return ps45_buttons[(int)p_button - (int)JoyButton::BACK]; + + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + return switch_pro_buttons[(int)p_button - (int)JoyButton::BACK]; + + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + // Plus button doesn't exist on the left joycon + return p_button == JoyButton::GUIDE ? "Capture" : "Minus"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + // Minus button doesn't exit on the right joycon + return p_button == JoyButton::GUIDE ? "Home" : "Plus"; + } + + case JoyButton::LEFT_STICK: + return "Left Stick"; + case JoyButton::RIGHT_STICK: + return "Right Stick"; + + case JoyButton::LEFT_SHOULDER: + case JoyButton::RIGHT_SHOULDER: + switch (gamepad_type) { + case SDL_GAMEPAD_TYPE_XBOX360: + case SDL_GAMEPAD_TYPE_XBOXONE: + return p_button == JoyButton::LEFT_SHOULDER ? "LB" : "RB"; + + case SDL_GAMEPAD_TYPE_PS3: + case SDL_GAMEPAD_TYPE_PS4: + case SDL_GAMEPAD_TYPE_PS5: + return p_button == JoyButton::LEFT_SHOULDER ? "L1" : "R1"; + + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + return p_button == JoyButton::LEFT_SHOULDER ? "L" : "R"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + return p_button == JoyButton::LEFT_SHOULDER ? "SL" : "SR"; + + default: + return p_button == JoyButton::LEFT_SHOULDER ? "Left Shoulder" : "Right Shoulder"; + } + + case JoyButton::DPAD_UP: + return "D-pad Up"; + case JoyButton::DPAD_DOWN: + return "D-pad Down"; + case JoyButton::DPAD_LEFT: + return "D-pad Left"; + case JoyButton::DPAD_RIGHT: + return "D-pad Right"; + + case JoyButton::MISC1: + switch (gamepad_type) { + case SDL_GAMEPAD_TYPE_PS5: + return "Mute"; + + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + //case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: // Horizontal joycons don't have the Misc1 button + //case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: // (see Back, Guide, Start handling for those above) + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + return "Capture"; + + default: + return "Misc1"; + } + + case JoyButton::PADDLE1: + case JoyButton::PADDLE2: + case JoyButton::PADDLE3: + case JoyButton::PADDLE4: + switch (gamepad_type) { + case SDL_GAMEPAD_TYPE_PS5: + return dualsense_edge_paddles[(int)p_button - (int)JoyButton::PADDLE1]; + + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + return joycon_paddles[(int)p_button - (int)JoyButton::PADDLE1]; + + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + return joycon_horizontal_paddles[(int)p_button - (int)JoyButton::PADDLE1]; + + default: + return default_paddles[(int)p_button - (int)JoyButton::PADDLE1]; + } + + case JoyButton::TOUCHPAD: + return "Touchpad"; + + default: + break; + } + return ""; +} + +// Override joypad model for controllers whose schemes can't detected by SDL +JoyModel JoypadSDL::get_scheme_override_model(int p_pad_idx) { + SDL_Gamepad *gamepad = get_sdl_gamepad(p_pad_idx); + if (gamepad == nullptr) { + return JoyModel::UNKNOWN; + } + SDL_GamepadType gamepad_type = SDL_GetGamepadType(gamepad); + // Can a gamepad have SDL_GAMEPAD_TYPE_UNKNOWN type? I'm not sure + if (gamepad_type != SDL_GAMEPAD_TYPE_STANDARD) { + return JoyModel::UNKNOWN; + } + + String joy_name = String(SDL_GetGamepadName(gamepad)).to_lower(); + + if (joy_name.contains("xbox")) { + return JoyModel::XBOX360; + } else if (joy_name.contains("playstation")) { + return JoyModel::PS3; + } + + return JoyModel::UNKNOWN; +} + +void JoypadSDL::get_joypad_features(int p_pad_idx, Input::Joypad &p_js) { + SDL_Joystick *joy = get_sdl_joystick(p_pad_idx); + // Shouldn't happen, but I'll leave it here just in case + ERR_FAIL_COND_MSG(joy == nullptr, "JoypadSDL::get_joypad_features: joy == nullptr"); + + p_js.device_type = static_cast(SDL_GetJoystickType(joy)); + + int battery_percent; + SDL_PowerState power_state = SDL_GetJoystickPowerInfo(joy, &battery_percent); + p_js.battery_percent = battery_percent; + p_js.power_state = static_cast(power_state); + p_js.connection_state = static_cast(SDL_GetJoystickConnectionState(joy)); + + SDL_PropertiesID propertiesID = SDL_GetJoystickProperties(joy); + p_js.has_light = SDL_GetBooleanProperty(propertiesID, SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, false) || SDL_GetBooleanProperty(propertiesID, SDL_PROP_JOYSTICK_CAP_MONO_LED_BOOLEAN, false); + + p_js.num_buttons = SDL_GetNumJoystickButtons(joy); + p_js.num_axes = SDL_GetNumJoystickAxes(joy); + + SDL_Gamepad *gamepad = get_sdl_gamepad(p_pad_idx); + if (gamepad != nullptr) { + if (SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL)) { + Input::get_singleton()->joy_motion[p_pad_idx].has_accelerometer = true; + } + if (SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO)) { + Input::get_singleton()->joy_motion[p_pad_idx].has_gyroscope = true; + } + + p_js.model = static_cast(SDL_GetGamepadType(gamepad)); + + // Since SDL_GetNumGamepadButtons, etc. don't exist, here's a more reliable way + // to get the number of gamepad buttons, etc. since some of them may be remapped + // to others (like a cheap controller's axes remapped to directional pad). + + int num_buttons = 0; + int num_axes = 0; + for (int i = SDL_GAMEPAD_BUTTON_SOUTH; i < SDL_GAMEPAD_BUTTON_COUNT; i++) { + if (SDL_GamepadHasButton(gamepad, (SDL_GamepadButton)i)) { + num_buttons++; + } + } + for (int i = SDL_GAMEPAD_AXIS_LEFTX; i < SDL_GAMEPAD_AXIS_COUNT; i++) { + if (SDL_GamepadHasAxis(gamepad, (SDL_GamepadAxis)i)) { + num_axes++; + } + } + p_js.num_buttons = num_buttons; + p_js.num_axes = num_axes; + + if (SDL_GetNumGamepadTouchpads(gamepad) > 0) { + Input::get_singleton()->joy_touch[p_pad_idx].num_touchpads = SDL_GetNumGamepadTouchpads(gamepad); + } + } +} + +bool JoypadSDL::send_effect(int p_pad_idx, const void *p_data, int p_size) { + return SDL_SendJoystickEffect(get_sdl_joystick(p_pad_idx), p_data, p_size); +} + +void JoypadSDL::start_triggers_vibration(int p_pad_idx, float p_left_rumble, float p_right_rumble, float p_duration) { + SDL_RumbleJoystickTriggers(get_sdl_joystick(p_pad_idx), p_left_rumble * 0xFFFF, p_right_rumble * 0xFFFF, p_duration * 1000); +} + void JoypadSDL::close_joypad(int p_pad_idx) { int sdl_instance_idx = joypads[p_pad_idx].sdl_instance_idx; @@ -270,4 +720,27 @@ void JoypadSDL::close_joypad(int p_pad_idx) { } } +SDL_Joystick *JoypadSDL::get_sdl_joystick(int p_pad_idx) const { + if (p_pad_idx < 0 || p_pad_idx >= Input::JOYPADS_MAX || !joypads[p_pad_idx].attached) { + return nullptr; + } + + SDL_JoystickID sdl_instance_idx = joypads[p_pad_idx].sdl_instance_idx; + return SDL_GetJoystickFromID(sdl_instance_idx); +} + +SDL_Gamepad *JoypadSDL::get_sdl_gamepad(int p_pad_idx) const { + if (p_pad_idx < 0 || p_pad_idx >= Input::JOYPADS_MAX || !joypads[p_pad_idx].attached) { + return nullptr; + } + + int sdl_instance_idx = joypads[p_pad_idx].sdl_instance_idx; + + if (!SDL_IsGamepad(sdl_instance_idx)) { + return nullptr; + } + + return SDL_GetGamepadFromID(sdl_instance_idx); +} + #endif // SDL_ENABLED diff --git a/drivers/sdl/joypad_sdl.h b/drivers/sdl/joypad_sdl.h index fb242a36354e..441f65c49b0f 100644 --- a/drivers/sdl/joypad_sdl.h +++ b/drivers/sdl/joypad_sdl.h @@ -35,6 +35,8 @@ typedef uint32_t SDL_JoystickID; typedef struct HWND__ *HWND; +typedef struct SDL_Joystick SDL_Joystick; +typedef struct SDL_Gamepad SDL_Gamepad; class JoypadSDL { public: @@ -49,6 +51,22 @@ class JoypadSDL { void setup_sdl_helper_window(HWND p_hwnd); #endif + bool enable_accelerometer(int p_pad_idx, bool p_enable); + bool enable_gyroscope(int p_pad_idx, bool p_enable); + + bool set_light(int p_pad_idx, Color p_color); + + bool has_joy_axis(int p_pad_idx, JoyAxis p_axis) const; + bool has_joy_button(int p_pad_idx, JoyButton p_button) const; + static String get_model_axis_string(JoyModel p_model, JoyAxis p_axis); + static String get_model_button_string(JoyModel p_model, JoyButton p_button); + JoyModel get_scheme_override_model(int p_pad_idx); + + void get_joypad_features(int p_pad_idx, Input::Joypad &p_js); + + bool send_effect(int p_pad_idx, const void *p_data, int p_size); + void start_triggers_vibration(int p_pad_idx, float p_left_rumble, float p_right_rumble, float p_duration); + private: struct Joypad { bool attached = false; @@ -66,4 +84,6 @@ class JoypadSDL { HashMap sdl_instance_id_to_joypad_id; void close_joypad(int p_pad_idx); + SDL_Joystick *get_sdl_joystick(int p_pad_idx) const; + SDL_Gamepad *get_sdl_gamepad(int p_pad_idx) const; }; diff --git a/thirdparty/sdl/SDL_error.c b/thirdparty/sdl/SDL_error.c index 3c62c8aff487..ea7b09cbbd96 100644 --- a/thirdparty/sdl/SDL_error.c +++ b/thirdparty/sdl/SDL_error.c @@ -64,9 +64,9 @@ bool SDL_SetErrorV(SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) // Enable this if you want to see all errors printed as they occur. // Note that there are many recoverable errors that may happen internally and // can be safely ignored if the public API doesn't return an error code. -#if 0 +//#if 0 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s", error->str); -#endif +//#endif } return false; diff --git a/thirdparty/sdl/sensor/windows/SDL_windowssensor.c b/thirdparty/sdl/sensor/windows/SDL_windowssensor.c new file mode 100644 index 000000000000..059747eeb420 --- /dev/null +++ b/thirdparty/sdl/sensor/windows/SDL_windowssensor.c @@ -0,0 +1,485 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_SENSOR_WINDOWS + +#include "SDL_windowssensor.h" +#include "../SDL_syssensor.h" +#include "../../core/windows/SDL_windows.h" + +#define COBJMACROS +#include +#include +#include + +DEFINE_GUID(SDL_CLSID_SensorManager, 0x77A1C827, 0xFCD2, 0x4689, 0x89, 0x15, 0x9D, 0x61, 0x3C, 0xC5, 0xFA, 0x3E); +DEFINE_GUID(SDL_IID_SensorManager, 0xBD77DB67, 0x45A8, 0x42DC, 0x8D, 0x00, 0x6D, 0xCF, 0x15, 0xF8, 0x37, 0x7A); +DEFINE_GUID(SDL_IID_SensorManagerEvents, 0x9B3B0B86, 0x266A, 0x4AAD, 0xB2, 0x1F, 0xFD, 0xE5, 0x50, 0x10, 0x01, 0xB7); +DEFINE_GUID(SDL_IID_SensorEvents, 0x5D8DCC91, 0x4641, 0x47E7, 0xB7, 0xC3, 0xB7, 0x4F, 0x48, 0xA6, 0xC3, 0x91); + +// These constants aren't available in Visual Studio 2015 or earlier Windows SDK +DEFINE_PROPERTYKEY(SDL_SENSOR_DATA_TYPE_ANGULAR_VELOCITY_X_DEGREES_PER_SECOND, 0X3F8A69A2, 0X7C5, 0X4E48, 0XA9, 0X65, 0XCD, 0X79, 0X7A, 0XAB, 0X56, 0XD5, 10); //[VT_R8] +DEFINE_PROPERTYKEY(SDL_SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Y_DEGREES_PER_SECOND, 0X3F8A69A2, 0X7C5, 0X4E48, 0XA9, 0X65, 0XCD, 0X79, 0X7A, 0XAB, 0X56, 0XD5, 11); //[VT_R8] +DEFINE_PROPERTYKEY(SDL_SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Z_DEGREES_PER_SECOND, 0X3F8A69A2, 0X7C5, 0X4E48, 0XA9, 0X65, 0XCD, 0X79, 0X7A, 0XAB, 0X56, 0XD5, 12); //[VT_R8] + +typedef struct +{ + SDL_SensorID id; + ISensor *sensor; + SENSOR_ID sensor_id; + char *name; + SDL_SensorType type; + SDL_Sensor *sensor_opened; + +} SDL_Windows_Sensor; + +static bool SDL_windowscoinit; +static ISensorManager *SDL_sensor_manager; +static int SDL_num_sensors; +static SDL_Windows_Sensor *SDL_sensors; + +static bool ConnectSensor(ISensor *sensor); +static bool DisconnectSensor(ISensor *sensor); + +static HRESULT STDMETHODCALLTYPE ISensorManagerEventsVtbl_QueryInterface(ISensorManagerEvents *This, REFIID riid, void **ppvObject) +{ + if (!ppvObject) { + return E_INVALIDARG; + } + + *ppvObject = NULL; + if (WIN_IsEqualIID(riid, &IID_IUnknown) || WIN_IsEqualIID(riid, &SDL_IID_SensorManagerEvents)) { + *ppvObject = This; + return S_OK; + } + return E_NOINTERFACE; +} + +static ULONG STDMETHODCALLTYPE ISensorManagerEventsVtbl_AddRef(ISensorManagerEvents *This) +{ + return 1; +} + +static ULONG STDMETHODCALLTYPE ISensorManagerEventsVtbl_Release(ISensorManagerEvents *This) +{ + return 1; +} + +static HRESULT STDMETHODCALLTYPE ISensorManagerEventsVtbl_OnSensorEnter(ISensorManagerEvents *This, ISensor *pSensor, SensorState state) +{ + ConnectSensor(pSensor); + return S_OK; +} + +static ISensorManagerEventsVtbl sensor_manager_events_vtbl = { + ISensorManagerEventsVtbl_QueryInterface, + ISensorManagerEventsVtbl_AddRef, + ISensorManagerEventsVtbl_Release, + ISensorManagerEventsVtbl_OnSensorEnter +}; +static ISensorManagerEvents sensor_manager_events = { + &sensor_manager_events_vtbl +}; + +static HRESULT STDMETHODCALLTYPE ISensorEventsVtbl_QueryInterface(ISensorEvents *This, REFIID riid, void **ppvObject) +{ + if (!ppvObject) { + return E_INVALIDARG; + } + + *ppvObject = NULL; + if (WIN_IsEqualIID(riid, &IID_IUnknown) || WIN_IsEqualIID(riid, &SDL_IID_SensorEvents)) { + *ppvObject = This; + return S_OK; + } + return E_NOINTERFACE; +} + +static ULONG STDMETHODCALLTYPE ISensorEventsVtbl_AddRef(ISensorEvents *This) +{ + return 1; +} + +static ULONG STDMETHODCALLTYPE ISensorEventsVtbl_Release(ISensorEvents *This) +{ + return 1; +} + +static HRESULT STDMETHODCALLTYPE ISensorEventsVtbl_OnStateChanged(ISensorEvents *This, ISensor *pSensor, SensorState state) +{ +#ifdef DEBUG_SENSORS + int i; + + SDL_LockSensors(); + for (i = 0; i < SDL_num_sensors; ++i) { + if (pSensor == SDL_sensors[i].sensor) { + SDL_Log("Sensor %s state changed to %d", SDL_sensors[i].name, state); + } + } + SDL_UnlockSensors(); +#endif + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE ISensorEventsVtbl_OnDataUpdated(ISensorEvents *This, ISensor *pSensor, ISensorDataReport *pNewData) +{ + int i; + Uint64 timestamp = SDL_GetTicksNS(); + + SDL_LockSensors(); + for (i = 0; i < SDL_num_sensors; ++i) { + if (pSensor == SDL_sensors[i].sensor) { + if (SDL_sensors[i].sensor_opened) { + HRESULT hrX, hrY, hrZ; + PROPVARIANT valueX = { 0 }, valueY = { 0 }, valueZ = { 0 }; + SYSTEMTIME sensor_systemtime; + FILETIME sensor_filetime; + Uint64 sensor_timestamp; + +#ifdef DEBUG_SENSORS + SDL_Log("Sensor %s data updated", SDL_sensors[i].name); +#endif + if (SUCCEEDED(ISensorDataReport_GetTimestamp(pNewData, &sensor_systemtime)) && + SystemTimeToFileTime(&sensor_systemtime, &sensor_filetime)) { + ULARGE_INTEGER sensor_time; + sensor_time.u.HighPart = sensor_filetime.dwHighDateTime; + sensor_time.u.LowPart = sensor_filetime.dwLowDateTime; + sensor_timestamp = sensor_time.QuadPart * 100; + } else { + sensor_timestamp = timestamp; + } + + switch (SDL_sensors[i].type) { + case SDL_SENSOR_ACCEL: + hrX = ISensorDataReport_GetSensorValue(pNewData, &SENSOR_DATA_TYPE_ACCELERATION_X_G, &valueX); + hrY = ISensorDataReport_GetSensorValue(pNewData, &SENSOR_DATA_TYPE_ACCELERATION_Y_G, &valueY); + hrZ = ISensorDataReport_GetSensorValue(pNewData, &SENSOR_DATA_TYPE_ACCELERATION_Z_G, &valueZ); + if (SUCCEEDED(hrX) && SUCCEEDED(hrY) && SUCCEEDED(hrZ) && + valueX.vt == VT_R8 && valueY.vt == VT_R8 && valueZ.vt == VT_R8) { + float values[3]; + + values[0] = (float)valueX.dblVal * SDL_STANDARD_GRAVITY; + values[1] = (float)valueY.dblVal * SDL_STANDARD_GRAVITY; + values[2] = (float)valueZ.dblVal * SDL_STANDARD_GRAVITY; + SDL_SendSensorUpdate(timestamp, SDL_sensors[i].sensor_opened, sensor_timestamp, values, 3); + } + break; + case SDL_SENSOR_GYRO: + hrX = ISensorDataReport_GetSensorValue(pNewData, &SDL_SENSOR_DATA_TYPE_ANGULAR_VELOCITY_X_DEGREES_PER_SECOND, &valueX); + hrY = ISensorDataReport_GetSensorValue(pNewData, &SDL_SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Y_DEGREES_PER_SECOND, &valueY); + hrZ = ISensorDataReport_GetSensorValue(pNewData, &SDL_SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Z_DEGREES_PER_SECOND, &valueZ); + if (SUCCEEDED(hrX) && SUCCEEDED(hrY) && SUCCEEDED(hrZ) && + valueX.vt == VT_R8 && valueY.vt == VT_R8 && valueZ.vt == VT_R8) { + const float DEGREES_TO_RADIANS = (SDL_PI_F / 180.0f); + float values[3]; + + values[0] = (float)valueX.dblVal * DEGREES_TO_RADIANS; + values[1] = (float)valueY.dblVal * DEGREES_TO_RADIANS; + values[2] = (float)valueZ.dblVal * DEGREES_TO_RADIANS; + SDL_SendSensorUpdate(timestamp, SDL_sensors[i].sensor_opened, sensor_timestamp, values, 3); + } + break; + default: + // FIXME: Need to know how to interpret the data for this sensor + break; + } + } + break; + } + } + SDL_UnlockSensors(); + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE ISensorEventsVtbl_OnEvent(ISensorEvents *This, ISensor *pSensor, REFGUID eventID, IPortableDeviceValues *pEventData) +{ +#ifdef DEBUG_SENSORS + int i; + + SDL_LockSensors(); + for (i = 0; i < SDL_num_sensors; ++i) { + if (pSensor == SDL_sensors[i].sensor) { + SDL_Log("Sensor %s event occurred", SDL_sensors[i].name); + } + } + SDL_UnlockSensors(); +#endif + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE ISensorEventsVtbl_OnLeave(ISensorEvents *This, REFSENSOR_ID ID) +{ + int i; + + SDL_LockSensors(); + for (i = 0; i < SDL_num_sensors; ++i) { + if (WIN_IsEqualIID(ID, &SDL_sensors[i].sensor_id)) { +#ifdef DEBUG_SENSORS + SDL_Log("Sensor %s disconnected", SDL_sensors[i].name); +#endif + DisconnectSensor(SDL_sensors[i].sensor); + } + } + SDL_UnlockSensors(); + + return S_OK; +} + +static ISensorEventsVtbl sensor_events_vtbl = { + ISensorEventsVtbl_QueryInterface, + ISensorEventsVtbl_AddRef, + ISensorEventsVtbl_Release, + ISensorEventsVtbl_OnStateChanged, + ISensorEventsVtbl_OnDataUpdated, + ISensorEventsVtbl_OnEvent, + ISensorEventsVtbl_OnLeave +}; +static ISensorEvents sensor_events = { + &sensor_events_vtbl +}; + +static bool ConnectSensor(ISensor *sensor) +{ + SDL_Windows_Sensor *new_sensor, *new_sensors; + HRESULT hr; + SENSOR_ID sensor_id; + SENSOR_TYPE_ID type_id; + SDL_SensorType type; + BSTR bstr_name = NULL; + char *name; + + hr = ISensor_GetID(sensor, &sensor_id); + if (FAILED(hr)) { + return WIN_SetErrorFromHRESULT("Couldn't get sensor ID", hr); + } + + hr = ISensor_GetType(sensor, &type_id); + if (FAILED(hr)) { + return WIN_SetErrorFromHRESULT("Couldn't get sensor type", hr); + } + + if (WIN_IsEqualIID(&type_id, &SENSOR_TYPE_ACCELEROMETER_3D)) { + type = SDL_SENSOR_ACCEL; + } else if (WIN_IsEqualIID(&type_id, &SENSOR_TYPE_GYROMETER_3D)) { + type = SDL_SENSOR_GYRO; + } else { + return SDL_SetError("Unknown sensor type"); + } + + hr = ISensor_GetFriendlyName(sensor, &bstr_name); + if (SUCCEEDED(hr) && bstr_name) { + name = WIN_StringToUTF8W(bstr_name); + } else { + name = SDL_strdup("Unknown Sensor"); + } + if (bstr_name != NULL) { + SysFreeString(bstr_name); + } + if (!name) { + return false; + } + + SDL_LockSensors(); + new_sensors = (SDL_Windows_Sensor *)SDL_realloc(SDL_sensors, (SDL_num_sensors + 1) * sizeof(SDL_Windows_Sensor)); + if (!new_sensors) { + SDL_UnlockSensors(); + SDL_free(name); + return false; + } + + ISensor_AddRef(sensor); + ISensor_SetEventSink(sensor, &sensor_events); + + SDL_sensors = new_sensors; + new_sensor = &SDL_sensors[SDL_num_sensors]; + ++SDL_num_sensors; + + SDL_zerop(new_sensor); + new_sensor->id = SDL_GetNextObjectID(); + new_sensor->sensor = sensor; + new_sensor->type = type; + new_sensor->name = name; + + SDL_UnlockSensors(); + + return true; +} + +static bool DisconnectSensor(ISensor *sensor) +{ + SDL_Windows_Sensor *old_sensor; + int i; + + SDL_LockSensors(); + for (i = 0; i < SDL_num_sensors; ++i) { + old_sensor = &SDL_sensors[i]; + if (sensor == old_sensor->sensor) { + /* This call hangs for some reason: + * https://github.com/libsdl-org/SDL/issues/5288 + */ + // ISensor_SetEventSink(sensor, NULL); + ISensor_Release(sensor); + SDL_free(old_sensor->name); + --SDL_num_sensors; + if (i < SDL_num_sensors) { + SDL_memmove(&SDL_sensors[i], &SDL_sensors[i + 1], (SDL_num_sensors - i) * sizeof(SDL_sensors[i])); + } + break; + } + } + SDL_UnlockSensors(); + + return true; +} + +static bool SDL_WINDOWS_SensorInit(void) +{ + HRESULT hr; + ISensorCollection *sensor_collection = NULL; + + if (WIN_CoInitialize() == S_OK) { + SDL_windowscoinit = true; + } + + hr = CoCreateInstance(&SDL_CLSID_SensorManager, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_SensorManager, (LPVOID *)&SDL_sensor_manager); + if (FAILED(hr)) { + // If we can't create a sensor manager (i.e. on Wine), we won't have any sensors, but don't fail the init + return true; // WIN_SetErrorFromHRESULT("Couldn't create the sensor manager", hr); + } + + hr = ISensorManager_SetEventSink(SDL_sensor_manager, &sensor_manager_events); + if (FAILED(hr)) { + ISensorManager_Release(SDL_sensor_manager); + SDL_sensor_manager = NULL; + return WIN_SetErrorFromHRESULT("Couldn't set the sensor manager event sink", hr); + } + + hr = ISensorManager_GetSensorsByCategory(SDL_sensor_manager, &SENSOR_CATEGORY_ALL, &sensor_collection); + if (SUCCEEDED(hr)) { + ULONG i, count; + + hr = ISensorCollection_GetCount(sensor_collection, &count); + if (SUCCEEDED(hr)) { + for (i = 0; i < count; ++i) { + ISensor *sensor; + + hr = ISensorCollection_GetAt(sensor_collection, i, &sensor); + if (SUCCEEDED(hr)) { + SensorState state; + + hr = ISensor_GetState(sensor, &state); + if (SUCCEEDED(hr)) { + ISensorManagerEventsVtbl_OnSensorEnter(&sensor_manager_events, sensor, state); + } + ISensorManager_Release(sensor); + } + } + } + ISensorCollection_Release(sensor_collection); + } + return true; +} + +static int SDL_WINDOWS_SensorGetCount(void) +{ + return SDL_num_sensors; +} + +static void SDL_WINDOWS_SensorDetect(void) +{ +} + +static const char *SDL_WINDOWS_SensorGetDeviceName(int device_index) +{ + return SDL_sensors[device_index].name; +} + +static SDL_SensorType SDL_WINDOWS_SensorGetDeviceType(int device_index) +{ + return SDL_sensors[device_index].type; +} + +static int SDL_WINDOWS_SensorGetDeviceNonPortableType(int device_index) +{ + return -1; +} + +static SDL_SensorID SDL_WINDOWS_SensorGetDeviceInstanceID(int device_index) +{ + return SDL_sensors[device_index].id; +} + +static bool SDL_WINDOWS_SensorOpen(SDL_Sensor *sensor, int device_index) +{ + SDL_sensors[device_index].sensor_opened = sensor; + return true; +} + +static void SDL_WINDOWS_SensorUpdate(SDL_Sensor *sensor) +{ +} + +static void SDL_WINDOWS_SensorClose(SDL_Sensor *sensor) +{ + int i; + + for (i = 0; i < SDL_num_sensors; ++i) { + if (sensor == SDL_sensors[i].sensor_opened) { + SDL_sensors[i].sensor_opened = NULL; + break; + } + } +} + +static void SDL_WINDOWS_SensorQuit(void) +{ + while (SDL_num_sensors > 0) { + DisconnectSensor(SDL_sensors[0].sensor); + } + + if (SDL_sensor_manager) { + ISensorManager_SetEventSink(SDL_sensor_manager, NULL); + ISensorManager_Release(SDL_sensor_manager); + SDL_sensor_manager = NULL; + } + + if (SDL_windowscoinit) { + WIN_CoUninitialize(); + } +} + +SDL_SensorDriver SDL_WINDOWS_SensorDriver = { + SDL_WINDOWS_SensorInit, + SDL_WINDOWS_SensorGetCount, + SDL_WINDOWS_SensorDetect, + SDL_WINDOWS_SensorGetDeviceName, + SDL_WINDOWS_SensorGetDeviceType, + SDL_WINDOWS_SensorGetDeviceNonPortableType, + SDL_WINDOWS_SensorGetDeviceInstanceID, + SDL_WINDOWS_SensorOpen, + SDL_WINDOWS_SensorUpdate, + SDL_WINDOWS_SensorClose, + SDL_WINDOWS_SensorQuit, +}; + +#endif // SDL_SENSOR_WINDOWS diff --git a/thirdparty/sdl/sensor/windows/SDL_windowssensor.h b/thirdparty/sdl/sensor/windows/SDL_windowssensor.h new file mode 100644 index 000000000000..4b0c6f8fcc83 --- /dev/null +++ b/thirdparty/sdl/sensor/windows/SDL_windowssensor.h @@ -0,0 +1,21 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" diff --git a/thirdparty/sdl/update-sdl.sh b/thirdparty/sdl/update-sdl.sh index 11a9d37f4848..b2510ffc45eb 100755 --- a/thirdparty/sdl/update-sdl.sh +++ b/thirdparty/sdl/update-sdl.sh @@ -60,7 +60,7 @@ mkdir $target/loadso cp -rv loadso/dlopen $target/loadso mkdir $target/sensor -cp -rv sensor/{*.{c,h},dummy} $target/sensor +cp -rv sensor/{*.{c,h},dummy,windows} $target/sensor mkdir $target/thread cp -rv thread/{*.{c,h},pthread,windows} $target/thread