From 68335becd4436cb1e956d037fe6bc553589bd973 Mon Sep 17 00:00:00 2001 From: Nintorch <92302738+Nintorch@users.noreply.github.com> Date: Tue, 17 Mar 2026 23:59:32 +0500 Subject: [PATCH] Add support for joypad battery and connection info This PR adds the ability to get information about the joypad's battery state (no battery, charging, fully charged), battery percentage, connection state (wired, wireless). --- core/core_constants.cpp | 10 ++++++ core/input/input.cpp | 57 +++++++++++++++++++++++++++++++++++ core/input/input.h | 12 ++++++++ core/input/input_enums.h | 16 ++++++++++ core/variant/variant_caster.h | 4 +++ doc/classes/@GlobalScope.xml | 24 +++++++++++++++ doc/classes/Input.xml | 24 +++++++++++++++ drivers/sdl/joypad_sdl.cpp | 28 +++++++++++++++++ 8 files changed, 175 insertions(+) diff --git a/core/core_constants.cpp b/core/core_constants.cpp index b99da1fe2d91..8a8340298421 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -570,6 +570,16 @@ void register_global_constants() { BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, SDL_MAX); BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, MAX); + 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, 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); BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, NOTE_ON); diff --git a/core/input/input.cpp b/core/input/input.cpp index 512f402158f0..5f70b54919bd 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -183,6 +183,9 @@ void Input::_bind_methods() { 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("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("set_joy_light", "device", "color"), &Input::set_joy_light); ClassDB::bind_method(D_METHOD("has_joy_light", "device"), &Input::has_joy_light); ClassDB::bind_method(D_METHOD("get_last_mouse_velocity"), &Input::get_last_mouse_velocity); @@ -798,6 +801,33 @@ Vector3 Input::get_gyroscope() const { return gyroscope; } +JoyPowerState Input::get_joy_power_state(int p_device) const { + _THREAD_SAFE_METHOD_ + const Joypad *joypad = joy_names.getptr(p_device); + if (joypad == nullptr) { + return JoyPowerState::UNKNOWN; + } + return joypad->power_state; +} + +int Input::get_joy_battery_percent(int p_device) const { + _THREAD_SAFE_METHOD_ + const Joypad *joypad = joy_names.getptr(p_device); + if (joypad == nullptr) { + return -1; + } + return joypad->battery_percent; +} + +JoyConnectionState Input::get_joy_connection_state(int p_device) const { + _THREAD_SAFE_METHOD_ + const Joypad *joypad = joy_names.getptr(p_device); + if (joypad == nullptr) { + return JoyConnectionState::UNKNOWN; + } + return joypad->connection_state; +} + 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. @@ -1356,6 +1386,33 @@ void Input::set_gyroscope(const Vector3 &p_gyroscope) { gyroscope = p_gyroscope; } +void Input::set_joy_power_state(int p_device, JoyPowerState p_state) { + _THREAD_SAFE_METHOD_ + Joypad *joypad = joy_names.getptr(p_device); + if (joypad == nullptr) { + return; + } + joypad->power_state = p_state; +} + +void Input::set_joy_battery_percent(int p_device, int p_percent) { + _THREAD_SAFE_METHOD_ + Joypad *joypad = joy_names.getptr(p_device); + if (joypad == nullptr || p_percent < -1 || p_percent > 100) { + return; + } + joypad->battery_percent = p_percent; +} + +void Input::set_joy_connection_state(int p_device, JoyConnectionState p_state) { + _THREAD_SAFE_METHOD_ + Joypad *joypad = joy_names.getptr(p_device); + if (joypad == nullptr) { + return; + } + joypad->connection_state = p_state; +} + void Input::set_mouse_position(const Point2 &p_posf) { mouse_pos = p_posf; } diff --git a/core/input/input.h b/core/input/input.h index 6d732877efba..c07a6b7e8df2 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -217,6 +217,9 @@ class Input : public Object { HatMask last_hat = HatMask::CENTER; int mapping = -1; int hat_current = 0; + int8_t battery_percent = -1; + JoyPowerState power_state = JoyPowerState::UNKNOWN; + JoyConnectionState connection_state = JoyConnectionState::UNKNOWN; Dictionary info; bool has_light = false; bool has_vibration = false; @@ -385,6 +388,10 @@ class Input : public Object { Vector3 get_magnetometer() const; Vector3 get_gyroscope() 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; + Point2 get_mouse_position() const; Vector2 get_last_mouse_velocity(); Vector2 get_last_mouse_screen_velocity(); @@ -399,6 +406,11 @@ class Input : public Object { void set_accelerometer(const Vector3 &p_accel); void set_magnetometer(const Vector3 &p_magnetometer); void set_gyroscope(const Vector3 &p_gyroscope); + + void set_joy_power_state(int p_device, JoyPowerState p_state); + void set_joy_battery_percent(int p_device, int p_percent); + void set_joy_connection_state(int p_device, JoyConnectionState p_state); + void set_joy_axis(int p_device, JoyAxis p_axis, float p_value); void set_joy_features(int p_device, JoypadFeatures *p_features); diff --git a/core/input/input_enums.h b/core/input/input_enums.h index b0bd15400646..cf5108ebd63b 100644 --- a/core/input/input_enums.h +++ b/core/input/input_enums.h @@ -109,6 +109,22 @@ enum class JoyButton { MAX = 128, // Android supports up to 36 buttons. DirectInput supports up to 128 buttons. }; +// See SDL_PowerState. SDL_POWERSTATE_ERROR equivalent was omitted for simplicity. +enum class JoyPowerState { + UNKNOWN = 0, + ON_BATTERY = 1, + NO_BATTERY = 2, + CHARGING = 3, + FULL_BATTERY = 4, +}; + +// See SDL_JoystickConnectionState. SDL_JOYSTICK_CONNECTION_INVALID equivalent was omitted for simplicity. +enum class JoyConnectionState { + UNKNOWN = 0, + WIRED = 1, + WIRELESS = 2, +}; + enum class MIDIMessage { NONE = 0, NOTE_OFF = 0x8, diff --git a/core/variant/variant_caster.h b/core/variant/variant_caster.h index 0815a4231811..9a64194ab86a 100644 --- a/core/variant/variant_caster.h +++ b/core/variant/variant_caster.h @@ -37,6 +37,8 @@ enum class HatDir; enum class HatMask; enum class JoyAxis; enum class JoyButton; +enum class JoyPowerState; +enum class JoyConnectionState; enum class MIDIMessage; enum class MouseButton; @@ -105,6 +107,8 @@ VARIANT_ENUM_CAST(HatDir); VARIANT_BITFIELD_CAST(HatMask); VARIANT_ENUM_CAST(JoyAxis); VARIANT_ENUM_CAST(JoyButton); +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 b212f3ca91ec..2ba07963755a 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2572,6 +2572,30 @@ The maximum number of game controller axes: OpenVR supports up to 5 Joysticks making a total of 10 axes. + + An invalid or unknown joypad power state. + + + 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 or unknown joypad connection state. + + + The joypad is currently using a wired connection to the device. + + + The joypad is currently using a wireless connection to the device. + 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 ee7d3aa64089..5c48ad773aaf 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -140,6 +140,22 @@ Returns the current value of the joypad axis at index [param axis]. + + + + + Returns the battery percentage of the requested joypad, if available. + [b]Note:[/b] This feature is only supported on Windows, Linux, and macOS. + + + + + + + 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. + + @@ -217,6 +233,14 @@ 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 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. + + diff --git a/drivers/sdl/joypad_sdl.cpp b/drivers/sdl/joypad_sdl.cpp index 669d766f7be9..88973d2ab92e 100644 --- a/drivers/sdl/joypad_sdl.cpp +++ b/drivers/sdl/joypad_sdl.cpp @@ -203,12 +203,29 @@ void JoypadSDL::process_events() { joypads[joy_id].guid, joypad_info); + // Querying more information about the joypad + + int joy_battery_percent; + SDL_PowerState joy_power_state = SDL_GetJoystickPowerInfo(joy, &joy_battery_percent); + SDL_JoystickConnectionState joy_connection_state = SDL_GetJoystickConnectionState(joy); + if (joy_power_state == SDL_POWERSTATE_ERROR) { + joy_power_state = SDL_POWERSTATE_UNKNOWN; + } + if (joy_connection_state == SDL_JOYSTICK_CONNECTION_INVALID) { + joy_connection_state = SDL_JOYSTICK_CONNECTION_UNKNOWN; + } + Input::get_singleton()->set_joy_features(joy_id, &joypads[joy_id]); if (joypads[joy_id].supports_motion_sensors) { // Data rate for all sensors should be the same. Input::get_singleton()->set_joy_motion_sensors_rate(joy_id, SDL_GetGamepadSensorDataRate(gamepad, SDL_SENSOR_ACCEL)); } + + // Godot constants are intentionally the same as SDL's + Input::get_singleton()->set_joy_power_state(joy_id, static_cast(joy_power_state)); + Input::get_singleton()->set_joy_battery_percent(joy_id, joy_battery_percent); + Input::get_singleton()->set_joy_connection_state(joy_id, static_cast(joy_connection_state)); } // An event for an attached joypad } else if (sdl_event.type >= SDL_EVENT_JOYSTICK_AXIS_MOTION && sdl_event.type < SDL_EVENT_FINGER_DOWN && sdl_instance_id_to_joypad_id.has(sdl_event.jdevice.which)) { @@ -254,6 +271,17 @@ void JoypadSDL::process_events() { ); break; + case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: { + // Gamepads also can have battery, so no SKIP_EVENT_FOR_GAMEPAD here + + SDL_PowerState joy_power_state = sdl_event.jbattery.state; + if (joy_power_state == SDL_POWERSTATE_ERROR) { + joy_power_state = SDL_POWERSTATE_UNKNOWN; + } + Input::get_singleton()->set_joy_power_state(joy_id, static_cast(sdl_event.jbattery.state)); // Godot constants are intentionally the same as SDL's + Input::get_singleton()->set_joy_battery_percent(joy_id, sdl_event.jbattery.percent); + } break; + case SDL_EVENT_GAMEPAD_AXIS_MOTION: { float axis_value;