From c83c672d615e04fda40e5c02835a10deba924813 Mon Sep 17 00:00:00 2001 From: Nintorch <92302738+Nintorch@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:30:18 +0500 Subject: [PATCH] Add support for joypad vibration checking --- core/input/input.cpp | 33 +++++++++++++++++++++++++++++++++ core/input/input.h | 6 ++++++ doc/classes/Input.xml | 36 ++++++++++++++++++++++++++++++++++-- drivers/sdl/joypad_sdl.cpp | 24 ++++++++++++++++++------ drivers/sdl/joypad_sdl.h | 2 ++ 5 files changed, 93 insertions(+), 8 deletions(-) diff --git a/core/input/input.cpp b/core/input/input.cpp index 4d32ddf8d7..40d5c2b507 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -146,6 +146,9 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("get_connected_joypads"), &Input::get_connected_joypads); ClassDB::bind_method(D_METHOD("get_joy_vibration_strength", "device"), &Input::get_joy_vibration_strength); ClassDB::bind_method(D_METHOD("get_joy_vibration_duration", "device"), &Input::get_joy_vibration_duration); + ClassDB::bind_method(D_METHOD("get_joy_vibration_remaining_duration", "device"), &Input::get_joy_vibration_remaining_duration); + ClassDB::bind_method(D_METHOD("is_joy_vibrating", "device"), &Input::is_joy_vibrating); + ClassDB::bind_method(D_METHOD("has_joy_vibration", "device"), &Input::has_joy_vibration); 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)); @@ -625,6 +628,33 @@ float Input::get_joy_vibration_duration(int p_device) { } } +float Input::get_joy_vibration_remaining_duration(int p_device) { + _THREAD_SAFE_METHOD_ + const Joypad *joypad = joy_names.getptr(p_device); + if (joypad == nullptr || !joypad->has_vibration) { + return 0.f; + } + const VibrationInfo *vibration = joy_vibration.getptr(p_device); + if (vibration == nullptr || (vibration->weak_magnitude == 0.f && vibration->strong_magnitude == 0.f) || vibration->duration < 0.f) { + return 0.f; + } + float vibration_duration = vibration->duration; + if (vibration_duration > 0xFFFF / 1000.f || vibration_duration == 0.f) { + vibration_duration = 0xFFFF / 1000.f; // SDL_MAX_RUMBLE_DURATION_MS / 1000.f + } + return MAX(vibration_duration - (OS::get_singleton()->get_ticks_usec() - vibration->timestamp) / 1e6, 0.f); +} + +bool Input::is_joy_vibrating(int p_device) { + return get_joy_vibration_remaining_duration(p_device) > 0.0f; +} + +bool Input::has_joy_vibration(int p_device) const { + _THREAD_SAFE_METHOD_ + const Joypad *joypad = joy_names.getptr(p_device); + return joypad != nullptr && joypad->has_vibration; +} + static String _hex_str(uint8_t p_byte) { static const char *dict = "0123456789abcdef"; char ret[3]; @@ -1738,6 +1768,9 @@ void Input::_update_joypad_features(int p_device) { if (!joypad || joypad->features == nullptr) { return; } + if (joypad->features->has_joy_vibration()) { + joypad->has_vibration = true; + } if (joypad->features->has_joy_light()) { joypad->has_light = true; } diff --git a/core/input/input.h b/core/input/input.h index 41bea926ad..4fe7662a33 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -91,6 +91,8 @@ public: public: virtual ~JoypadFeatures() {} + virtual bool has_joy_vibration() const { return false; } + virtual bool has_joy_light() const { return false; } virtual void set_joy_light(const Color &p_color) {} @@ -212,6 +214,7 @@ private: int hat_current = 0; Dictionary info; bool has_light = false; + bool has_vibration = false; Input::JoypadFeatures *features = nullptr; }; @@ -362,7 +365,10 @@ public: TypedArray get_connected_joypads(); Vector2 get_joy_vibration_strength(int p_device); float get_joy_vibration_duration(int p_device); + float get_joy_vibration_remaining_duration(int p_device); uint64_t get_joy_vibration_timestamp(int p_device); + bool is_joy_vibrating(int p_device); + bool has_joy_vibration(int p_device) const; void joy_connection_changed(int p_idx, bool p_connected, const String &p_name, const String &p_guid = "", const Dictionary &p_joypad_info = Dictionary()); Vector3 get_gravity() const; diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index 97cf266144..27837bb18d 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -221,6 +221,15 @@ Returns the duration of the current vibration effect in seconds. + [b]Note:[/b] This method returns the same value that was passed to [method start_joy_vibration], and this value does [b]not[/b] change when the joypad's vibration runs out, it only gets reset after a call to [method stop_joy_vibration]. + If you want to check if a joypad is still vibrating, use [method is_joy_vibrating] instead. + + + + + + + Returns the remaining duration of the current vibration effect in seconds. @@ -228,6 +237,8 @@ Returns the strength of the joypad vibration: x is the strength of the weak motor, and y is the strength of the strong motor. + [b]Note:[/b] This method returns the same values that were passed to [method start_joy_vibration], and these values do [b]not[/b] change when the joypad's vibration runs out, they only get reset after a call to [method stop_joy_vibration]. + If you want to check if a joypad is still vibrating, use [method is_joy_vibrating] instead. @@ -285,6 +296,14 @@ [b]Note:[/b] This feature is only supported on Windows, Linux, and macOS. + + + + + Returns [code]true[/code] if the joypad supports vibration. See also [method start_joy_vibration]. + [b]Note:[/b] For macOS, vibration is only supported in macOS 11 and later. When connected via USB, vibration is only supported for major brand controllers (except Xbox One and Xbox Series X/S controllers) due to macOS limitations. + + @@ -392,6 +411,14 @@ [b]Note:[/b] This feature is only supported on Windows, Linux, and macOS. + + + + + Returns [code]true[/code] if the joypad is still vibrating after a call to [method start_joy_vibration]. + Unlike [method get_joy_vibration_strength] and [method get_joy_vibration_duration], this method returns [code]false[/code] after the joypad's vibration runs out. + + @@ -664,8 +691,13 @@ - Starts to vibrate the joypad. Joypads usually come with two rumble motors, a strong and a weak one. [param weak_magnitude] is the strength of the weak motor (between 0 and 1) and [param strong_magnitude] is the strength of the strong motor (between 0 and 1). [param duration] is the duration of the effect in seconds (a duration of 0 will try to play the vibration indefinitely). The vibration can be stopped early by calling [method stop_joy_vibration]. - [b]Note:[/b] Not every hardware is compatible with long effect durations; it is recommended to restart an effect if it has to be played for more than a few seconds. + Starts to vibrate the joypad. See also [method has_joy_vibration] and [method is_joy_vibrating]. + Joypads usually come with two rumble motors, a strong and a weak one. + [param weak_magnitude] is the strength of the weak motor (between [code]0.0[/code] and [code]1.0[/code]). + [param strong_magnitude] is the strength of the strong motor (between [code]0.0[/code] and [code]1.0[/code]). + [param duration] is the duration of the effect in seconds (a duration of [code]0.0[/code] will try to play the vibration as long as possible, which is about 65 seconds). + The vibration can be stopped early by calling [method stop_joy_vibration]. + See also [method get_joy_vibration_strength] and [method get_joy_vibration_duration]. [b]Note:[/b] For macOS, vibration is only supported in macOS 11 and later. When connected via USB, vibration is only supported for major brand controllers (except Xbox One and Xbox Series X/S controllers) due to macOS limitations. diff --git a/drivers/sdl/joypad_sdl.cpp b/drivers/sdl/joypad_sdl.cpp index 4ecc5db2c9..04d5a3fdd3 100644 --- a/drivers/sdl/joypad_sdl.cpp +++ b/drivers/sdl/joypad_sdl.cpp @@ -95,6 +95,17 @@ void JoypadSDL::process_events() { SDL_Joystick *sdl_joy = SDL_GetJoystickFromID(joypads[i].sdl_instance_idx); Vector2 strength = Input::get_singleton()->get_joy_vibration_strength(i); + // Invalid values for strength are filtered by Input::start_joy_vibration(). + + float duration = Input::get_singleton()->get_joy_vibration_duration(i); + Uint32 duration_ms = 0; + if (duration < 0.0f) { + continue; // Invalid duration. + } else if (duration == 0.0f) { + duration_ms = 0xFFFF; // SDL_MAX_RUMBLE_DURATION_MS + } else { + duration_ms = duration * 1000; + } /* If the vibration was requested to start, SDL_RumbleJoystick will start it. @@ -103,13 +114,10 @@ void JoypadSDL::process_events() { Here strength.y goes first and then strength.x, because Input.get_joy_vibration_strength().x is vibration's weak magnitude (high frequency rumble), and .y is strong magnitude (low frequency rumble), SDL_RumbleJoystick takes low frequency rumble first and then high frequency rumble. + + Rumble strength goes from 0 to 0xFFFF. */ - SDL_RumbleJoystick( - sdl_joy, - // Rumble strength goes from 0 to 0xFFFF - strength.y * UINT16_MAX, - strength.x * UINT16_MAX, - Input::get_singleton()->get_joy_vibration_duration(i) * 1000); + SDL_RumbleJoystick(sdl_joy, strength.y * UINT16_MAX, strength.x * UINT16_MAX, duration_ms); } } } @@ -333,6 +341,10 @@ void JoypadSDL::Joypad::set_joy_motion_sensors_enabled(bool p_enable) { SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, p_enable); } +bool JoypadSDL::Joypad::has_joy_vibration() const { + return supports_force_feedback; +} + SDL_Joystick *JoypadSDL::Joypad::get_sdl_joystick() const { return SDL_GetJoystickFromID(sdl_instance_idx); } diff --git a/drivers/sdl/joypad_sdl.h b/drivers/sdl/joypad_sdl.h index 0b16900e89..718cd957f7 100644 --- a/drivers/sdl/joypad_sdl.h +++ b/drivers/sdl/joypad_sdl.h @@ -62,6 +62,8 @@ private: virtual bool has_joy_motion_sensors() const override; virtual void set_joy_motion_sensors_enabled(bool p_enable) override; + virtual bool has_joy_vibration() const override; + SDL_Joystick *get_sdl_joystick() const; SDL_Gamepad *get_sdl_gamepad() const; };