From 5a079a2d114f96d4847d1ee305d5b7c16eeec50e Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 27 Dec 2025 12:03:39 -0800 Subject: Initial commit --- contrib/SDL-3.2.8/src/joystick/SDL_gamepad.c | 3907 ++++++++++++++++++++ contrib/SDL-3.2.8/src/joystick/SDL_gamepad_c.h | 50 + contrib/SDL-3.2.8/src/joystick/SDL_gamepad_db.h | 904 +++++ contrib/SDL-3.2.8/src/joystick/SDL_joystick.c | 3675 ++++++++++++++++++ contrib/SDL-3.2.8/src/joystick/SDL_joystick_c.h | 270 ++ .../src/joystick/SDL_steam_virtual_gamepad.c | 254 ++ .../src/joystick/SDL_steam_virtual_gamepad.h | 36 + contrib/SDL-3.2.8/src/joystick/SDL_sysjoystick.h | 269 ++ .../src/joystick/android/SDL_sysjoystick.c | 681 ++++ .../src/joystick/android/SDL_sysjoystick_c.h | 57 + .../SDL-3.2.8/src/joystick/apple/SDL_mfijoystick.m | 1946 ++++++++++ .../src/joystick/apple/SDL_mfijoystick_c.h | 73 + .../SDL-3.2.8/src/joystick/bsd/SDL_bsdjoystick.c | 868 +++++ contrib/SDL-3.2.8/src/joystick/check_8bitdo.sh | 15 + contrib/SDL-3.2.8/src/joystick/controller_list.h | 609 +++ contrib/SDL-3.2.8/src/joystick/controller_type.c | 140 + contrib/SDL-3.2.8/src/joystick/controller_type.h | 78 + .../src/joystick/darwin/SDL_iokitjoystick.c | 1089 ++++++ .../src/joystick/darwin/SDL_iokitjoystick_c.h | 80 + .../SDL-3.2.8/src/joystick/dummy/SDL_sysjoystick.c | 156 + .../src/joystick/emscripten/SDL_sysjoystick.c | 445 +++ .../src/joystick/emscripten/SDL_sysjoystick_c.h | 49 + .../src/joystick/gdk/SDL_gameinputjoystick.c | 828 +++++ .../src/joystick/haiku/SDL_haikujoystick.cc | 315 ++ .../src/joystick/hidapi/SDL_hidapi_combined.c | 236 ++ .../src/joystick/hidapi/SDL_hidapi_gamecube.c | 534 +++ .../src/joystick/hidapi/SDL_hidapi_luna.c | 421 +++ .../src/joystick/hidapi/SDL_hidapi_nintendo.h | 49 + .../SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps3.c | 1446 ++++++++ .../SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps4.c | 1390 +++++++ .../SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps5.c | 1624 ++++++++ .../src/joystick/hidapi/SDL_hidapi_rumble.c | 285 ++ .../src/joystick/hidapi/SDL_hidapi_rumble.h | 42 + .../src/joystick/hidapi/SDL_hidapi_shield.c | 578 +++ .../src/joystick/hidapi/SDL_hidapi_stadia.c | 324 ++ .../src/joystick/hidapi/SDL_hidapi_steam.c | 1534 ++++++++ .../src/joystick/hidapi/SDL_hidapi_steam_hori.c | 415 +++ .../src/joystick/hidapi/SDL_hidapi_steamdeck.c | 451 +++ .../src/joystick/hidapi/SDL_hidapi_switch.c | 2859 ++++++++++++++ .../SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_wii.c | 1617 ++++++++ .../src/joystick/hidapi/SDL_hidapi_xbox360.c | 379 ++ .../src/joystick/hidapi/SDL_hidapi_xbox360w.c | 388 ++ .../src/joystick/hidapi/SDL_hidapi_xboxone.c | 1675 +++++++++ .../src/joystick/hidapi/SDL_hidapijoystick.c | 1730 +++++++++ .../src/joystick/hidapi/SDL_hidapijoystick_c.h | 195 + .../joystick/hidapi/steam/controller_constants.h | 582 +++ .../src/joystick/hidapi/steam/controller_structs.h | 463 +++ .../SDL-3.2.8/src/joystick/linux/SDL_sysjoystick.c | 2730 ++++++++++++++ .../src/joystick/linux/SDL_sysjoystick_c.h | 117 + .../SDL-3.2.8/src/joystick/n3ds/SDL_sysjoystick.c | 298 ++ .../SDL-3.2.8/src/joystick/ps2/SDL_sysjoystick.c | 366 ++ .../SDL-3.2.8/src/joystick/psp/SDL_sysjoystick.c | 277 ++ contrib/SDL-3.2.8/src/joystick/sort_controllers.py | 164 + contrib/SDL-3.2.8/src/joystick/usb_ids.h | 194 + .../src/joystick/virtual/SDL_virtualjoystick.c | 990 +++++ .../src/joystick/virtual/SDL_virtualjoystick_c.h | 84 + .../SDL-3.2.8/src/joystick/vita/SDL_sysjoystick.c | 400 ++ .../src/joystick/windows/SDL_dinputjoystick.c | 1210 ++++++ .../src/joystick/windows/SDL_dinputjoystick_c.h | 40 + .../src/joystick/windows/SDL_rawinputjoystick.c | 2238 +++++++++++ .../src/joystick/windows/SDL_rawinputjoystick_c.h | 32 + .../joystick/windows/SDL_windows_gaming_input.c | 1039 ++++++ .../src/joystick/windows/SDL_windowsjoystick.c | 693 ++++ .../src/joystick/windows/SDL_windowsjoystick_c.h | 103 + .../src/joystick/windows/SDL_xinputjoystick.c | 473 +++ .../src/joystick/windows/SDL_xinputjoystick_c.h | 44 + 66 files changed, 47503 insertions(+) create mode 100644 contrib/SDL-3.2.8/src/joystick/SDL_gamepad.c create mode 100644 contrib/SDL-3.2.8/src/joystick/SDL_gamepad_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/SDL_gamepad_db.h create mode 100644 contrib/SDL-3.2.8/src/joystick/SDL_joystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/SDL_joystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.c create mode 100644 contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.h create mode 100644 contrib/SDL-3.2.8/src/joystick/SDL_sysjoystick.h create mode 100644 contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick.m create mode 100644 contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/bsd/SDL_bsdjoystick.c create mode 100755 contrib/SDL-3.2.8/src/joystick/check_8bitdo.sh create mode 100644 contrib/SDL-3.2.8/src/joystick/controller_list.h create mode 100644 contrib/SDL-3.2.8/src/joystick/controller_type.c create mode 100644 contrib/SDL-3.2.8/src/joystick/controller_type.h create mode 100644 contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/dummy/SDL_sysjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/haiku/SDL_haikujoystick.cc create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_combined.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_gamecube.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_luna.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_nintendo.h create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps3.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps4.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps5.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.h create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_shield.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_stadia.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam_hori.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steamdeck.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_switch.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_wii.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360w.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xboxone.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_constants.h create mode 100644 contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_structs.h create mode 100644 contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/n3ds/SDL_sysjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/ps2/SDL_sysjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/psp/SDL_sysjoystick.c create mode 100755 contrib/SDL-3.2.8/src/joystick/sort_controllers.py create mode 100644 contrib/SDL-3.2.8/src/joystick/usb_ids.h create mode 100644 contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/vita/SDL_sysjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h (limited to 'contrib/SDL-3.2.8/src/joystick') diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_gamepad.c b/contrib/SDL-3.2.8/src/joystick/SDL_gamepad.c new file mode 100644 index 0000000..8486865 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/SDL_gamepad.c @@ -0,0 +1,3907 @@ +/* + 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" + +// This is the gamepad API for Simple DirectMedia Layer + +#include "SDL_sysjoystick.h" +#include "SDL_joystick_c.h" +#include "SDL_steam_virtual_gamepad.h" +#include "SDL_gamepad_c.h" +#include "SDL_gamepad_db.h" +#include "controller_type.h" +#include "usb_ids.h" +#include "hidapi/SDL_hidapi_nintendo.h" +#include "../events/SDL_events_c.h" + + +#ifdef SDL_PLATFORM_ANDROID +#endif + +// Many gamepads turn the center button into an instantaneous button press +#define SDL_MINIMUM_GUIDE_BUTTON_DELAY_MS 250 + +#define SDL_GAMEPAD_CRC_FIELD "crc:" +#define SDL_GAMEPAD_CRC_FIELD_SIZE 4 // hard-coded for speed +#define SDL_GAMEPAD_TYPE_FIELD "type:" +#define SDL_GAMEPAD_TYPE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_TYPE_FIELD) +#define SDL_GAMEPAD_FACE_FIELD "face:" +#define SDL_GAMEPAD_FACE_FIELD_SIZE 5 // hard-coded for speed +#define SDL_GAMEPAD_PLATFORM_FIELD "platform:" +#define SDL_GAMEPAD_PLATFORM_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_PLATFORM_FIELD) +#define SDL_GAMEPAD_HINT_FIELD "hint:" +#define SDL_GAMEPAD_HINT_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_HINT_FIELD) +#define SDL_GAMEPAD_SDKGE_FIELD "sdk>=:" +#define SDL_GAMEPAD_SDKGE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_SDKGE_FIELD) +#define SDL_GAMEPAD_SDKLE_FIELD "sdk<=:" +#define SDL_GAMEPAD_SDKLE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_SDKLE_FIELD) + +static bool SDL_gamepads_initialized; +static SDL_Gamepad *SDL_gamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL; + +// The face button style of a gamepad +typedef enum +{ + SDL_GAMEPAD_FACE_STYLE_UNKNOWN, + SDL_GAMEPAD_FACE_STYLE_ABXY, + SDL_GAMEPAD_FACE_STYLE_BAYX, + SDL_GAMEPAD_FACE_STYLE_SONY, +} SDL_GamepadFaceStyle; + +// our hard coded list of mapping support +typedef enum +{ + SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT, + SDL_GAMEPAD_MAPPING_PRIORITY_API, + SDL_GAMEPAD_MAPPING_PRIORITY_USER, +} SDL_GamepadMappingPriority; + +#define _guarded SDL_GUARDED_BY(SDL_joystick_lock) + +typedef struct GamepadMapping_t +{ + SDL_GUID guid _guarded; + char *name _guarded; + char *mapping _guarded; + SDL_GamepadMappingPriority priority _guarded; + struct GamepadMapping_t *next _guarded; +} GamepadMapping_t; + +typedef struct +{ + int refcount _guarded; + SDL_JoystickID *joysticks _guarded; + GamepadMapping_t **joystick_mappings _guarded; + + int num_changed_mappings _guarded; + GamepadMapping_t **changed_mappings _guarded; + +} MappingChangeTracker; + +#undef _guarded + +static SDL_GUID s_zeroGUID; +static GamepadMapping_t *s_pSupportedGamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static GamepadMapping_t *s_pDefaultMapping SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static GamepadMapping_t *s_pXInputMapping SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static MappingChangeTracker *s_mappingChangeTracker SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static SDL_HashTable *s_gamepadInstanceIDs SDL_GUARDED_BY(SDL_joystick_lock) = NULL; + +#define _guarded SDL_GUARDED_BY(SDL_joystick_lock) + +// The SDL gamepad structure +struct SDL_Gamepad +{ + SDL_Joystick *joystick _guarded; // underlying joystick device + int ref_count _guarded; + + const char *name _guarded; + SDL_GamepadType type _guarded; + SDL_GamepadFaceStyle face_style _guarded; + GamepadMapping_t *mapping _guarded; + int num_bindings _guarded; + SDL_GamepadBinding *bindings _guarded; + SDL_GamepadBinding **last_match_axis _guarded; + Uint8 *last_hat_mask _guarded; + Uint64 guide_button_down _guarded; + + struct SDL_Gamepad *next _guarded; // pointer to next gamepad we have allocated +}; + +#undef _guarded + +#define CHECK_GAMEPAD_MAGIC(gamepad, result) \ + if (!SDL_ObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD) || \ + !SDL_IsJoystickValid(gamepad->joystick)) { \ + SDL_InvalidParamError("gamepad"); \ + SDL_UnlockJoysticks(); \ + return result; \ + } + +static SDL_vidpid_list SDL_allowed_gamepads = { + SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, 0, 0, NULL, + NULL, 0, 0, NULL, + 0, NULL, + false +}; +static SDL_vidpid_list SDL_ignored_gamepads = { + SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, 0, 0, NULL, + NULL, 0, 0, NULL, + 0, NULL, + false +}; + +static GamepadMapping_t *SDL_PrivateAddMappingForGUID(SDL_GUID jGUID, const char *mappingString, bool *existing, SDL_GamepadMappingPriority priority); +static void SDL_PrivateLoadButtonMapping(SDL_Gamepad *gamepad, GamepadMapping_t *pGamepadMapping); +static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id, bool create_mapping); +static void SDL_SendGamepadAxis(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadAxis axis, Sint16 value); +static void SDL_SendGamepadButton(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadButton button, bool down); + +static bool HasSameOutput(SDL_GamepadBinding *a, SDL_GamepadBinding *b) +{ + if (a->output_type != b->output_type) { + return false; + } + + if (a->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) { + return a->output.axis.axis == b->output.axis.axis; + } else { + return a->output.button == b->output.button; + } +} + +static void ResetOutput(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadBinding *bind) +{ + if (bind->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) { + SDL_SendGamepadAxis(timestamp, gamepad, bind->output.axis.axis, 0); + } else { + SDL_SendGamepadButton(timestamp, gamepad, bind->output.button, false); + } +} + +static void HandleJoystickAxis(Uint64 timestamp, SDL_Gamepad *gamepad, int axis, int value) +{ + int i; + SDL_GamepadBinding *last_match; + SDL_GamepadBinding *match = NULL; + + SDL_AssertJoysticksLocked(); + + last_match = gamepad->last_match_axis[axis]; + for (i = 0; i < gamepad->num_bindings; ++i) { + SDL_GamepadBinding *binding = &gamepad->bindings[i]; + if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS && + axis == binding->input.axis.axis) { + if (binding->input.axis.axis_min < binding->input.axis.axis_max) { + if (value >= binding->input.axis.axis_min && + value <= binding->input.axis.axis_max) { + match = binding; + break; + } + } else { + if (value >= binding->input.axis.axis_max && + value <= binding->input.axis.axis_min) { + match = binding; + break; + } + } + } + } + + if (last_match && (!match || !HasSameOutput(last_match, match))) { + // Clear the last input that this axis generated + ResetOutput(timestamp, gamepad, last_match); + } + + if (match) { + if (match->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) { + if (match->input.axis.axis_min != match->output.axis.axis_min || match->input.axis.axis_max != match->output.axis.axis_max) { + float normalized_value = (float)(value - match->input.axis.axis_min) / (match->input.axis.axis_max - match->input.axis.axis_min); + value = match->output.axis.axis_min + (int)(normalized_value * (match->output.axis.axis_max - match->output.axis.axis_min)); + } + SDL_SendGamepadAxis(timestamp, gamepad, match->output.axis.axis, (Sint16)value); + } else { + bool down; + int threshold = match->input.axis.axis_min + (match->input.axis.axis_max - match->input.axis.axis_min) / 2; + if (match->input.axis.axis_max < match->input.axis.axis_min) { + down = (value <= threshold); + } else { + down = (value >= threshold); + } + SDL_SendGamepadButton(timestamp, gamepad, match->output.button, down); + } + } + gamepad->last_match_axis[axis] = match; +} + +static void HandleJoystickButton(Uint64 timestamp, SDL_Gamepad *gamepad, int button, bool down) +{ + int i; + + SDL_AssertJoysticksLocked(); + + for (i = 0; i < gamepad->num_bindings; ++i) { + SDL_GamepadBinding *binding = &gamepad->bindings[i]; + if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON && + button == binding->input.button) { + if (binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) { + int value = down ? binding->output.axis.axis_max : binding->output.axis.axis_min; + SDL_SendGamepadAxis(timestamp, gamepad, binding->output.axis.axis, (Sint16)value); + } else { + SDL_SendGamepadButton(timestamp, gamepad, binding->output.button, down); + } + break; + } + } +} + +static void HandleJoystickHat(Uint64 timestamp, SDL_Gamepad *gamepad, int hat, Uint8 value) +{ + int i; + Uint8 last_mask, changed_mask; + + SDL_AssertJoysticksLocked(); + + last_mask = gamepad->last_hat_mask[hat]; + changed_mask = (last_mask ^ value); + for (i = 0; i < gamepad->num_bindings; ++i) { + SDL_GamepadBinding *binding = &gamepad->bindings[i]; + if (binding->input_type == SDL_GAMEPAD_BINDTYPE_HAT && hat == binding->input.hat.hat) { + if ((changed_mask & binding->input.hat.hat_mask) != 0) { + if (value & binding->input.hat.hat_mask) { + if (binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) { + SDL_SendGamepadAxis(timestamp, gamepad, binding->output.axis.axis, (Sint16)binding->output.axis.axis_max); + } else { + SDL_SendGamepadButton(timestamp, gamepad, binding->output.button, true); + } + } else { + ResetOutput(timestamp, gamepad, binding); + } + } + } + } + gamepad->last_hat_mask[hat] = value; +} + +/* The joystick layer will _also_ send events to recenter before disconnect, + but it has to make (sometimes incorrect) guesses at what being "centered" + is. The gamepad layer, however, can set a definite logical idle + position, so set them all here. If we happened to already be at the + center thanks to the joystick layer or idle hands, this won't generate + duplicate events. */ +static void RecenterGamepad(SDL_Gamepad *gamepad) +{ + int i; + Uint64 timestamp = SDL_GetTicksNS(); + + for (i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) { + SDL_GamepadButton button = (SDL_GamepadButton)i; + if (SDL_GetGamepadButton(gamepad, button)) { + SDL_SendGamepadButton(timestamp, gamepad, button, false); + } + } + + for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) { + SDL_GamepadAxis axis = (SDL_GamepadAxis)i; + if (SDL_GetGamepadAxis(gamepad, axis) != 0) { + SDL_SendGamepadAxis(timestamp, gamepad, axis, 0); + } + } +} + +void SDL_PrivateGamepadAdded(SDL_JoystickID instance_id) +{ + SDL_Event event; + + if (!SDL_gamepads_initialized) { + return; + } + + event.type = SDL_EVENT_GAMEPAD_ADDED; + event.common.timestamp = 0; + event.gdevice.which = instance_id; + SDL_PushEvent(&event); +} + +void SDL_PrivateGamepadRemoved(SDL_JoystickID instance_id) +{ + SDL_Event event; + SDL_Gamepad *gamepad; + + SDL_AssertJoysticksLocked(); + + if (!SDL_gamepads_initialized) { + return; + } + + for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) { + if (gamepad->joystick->instance_id == instance_id) { + RecenterGamepad(gamepad); + break; + } + } + + event.type = SDL_EVENT_GAMEPAD_REMOVED; + event.common.timestamp = 0; + event.gdevice.which = instance_id; + SDL_PushEvent(&event); +} + +static void SDL_PrivateGamepadRemapped(SDL_JoystickID instance_id) +{ + SDL_Event event; + + if (!SDL_gamepads_initialized || SDL_IsJoystickBeingAdded()) { + return; + } + + event.type = SDL_EVENT_GAMEPAD_REMAPPED; + event.common.timestamp = 0; + event.gdevice.which = instance_id; + SDL_PushEvent(&event); +} + +/* + * Event filter to fire gamepad events from joystick ones + */ +static bool SDLCALL SDL_GamepadEventWatcher(void *userdata, SDL_Event *event) +{ + SDL_Gamepad *gamepad; + + switch (event->type) { + case SDL_EVENT_JOYSTICK_AXIS_MOTION: + { + SDL_AssertJoysticksLocked(); + + for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) { + if (gamepad->joystick->instance_id == event->jaxis.which) { + HandleJoystickAxis(event->common.timestamp, gamepad, event->jaxis.axis, event->jaxis.value); + break; + } + } + } break; + case SDL_EVENT_JOYSTICK_BUTTON_DOWN: + case SDL_EVENT_JOYSTICK_BUTTON_UP: + { + SDL_AssertJoysticksLocked(); + + for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) { + if (gamepad->joystick->instance_id == event->jbutton.which) { + HandleJoystickButton(event->common.timestamp, gamepad, event->jbutton.button, event->jbutton.down); + break; + } + } + } break; + case SDL_EVENT_JOYSTICK_HAT_MOTION: + { + SDL_AssertJoysticksLocked(); + + for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) { + if (gamepad->joystick->instance_id == event->jhat.which) { + HandleJoystickHat(event->common.timestamp, gamepad, event->jhat.hat, event->jhat.value); + break; + } + } + } break; + case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE: + { + SDL_AssertJoysticksLocked(); + + for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) { + if (gamepad->joystick->instance_id == event->jdevice.which) { + SDL_Event deviceevent; + + deviceevent.type = SDL_EVENT_GAMEPAD_UPDATE_COMPLETE; + deviceevent.common.timestamp = event->jdevice.timestamp; + deviceevent.gdevice.which = event->jdevice.which; + SDL_PushEvent(&deviceevent); + break; + } + } + } break; + default: + break; + } + + return true; +} + +/* SDL defines sensor orientation relative to the device natural + orientation, so when it's changed orientation to be used as a + gamepad, change the sensor orientation to match. + */ +static void AdjustSensorOrientation(const SDL_Joystick *joystick, const float *src, float *dst) +{ + unsigned int i, j; + + SDL_AssertJoysticksLocked(); + + for (i = 0; i < 3; ++i) { + dst[i] = 0.0f; + for (j = 0; j < 3; ++j) { + dst[i] += joystick->sensor_transform[i][j] * src[j]; + } + } +} + +/* + * Event filter to fire gamepad sensor events from system sensor events + * + * We don't use SDL_GamepadEventWatcher() for this because we want to + * deliver gamepad sensor events when system sensor events are disabled, + * and we also need to avoid a potential deadlock where joystick event + * delivery locks the joysticks and then the event queue, but sensor + * event delivery would lock the event queue and then from within the + * event watcher function lock the joysticks. + */ +void SDL_GamepadSensorWatcher(Uint64 timestamp, SDL_SensorID sensor, Uint64 sensor_timestamp, float *data, int num_values) +{ + SDL_Gamepad *gamepad; + + SDL_LockJoysticks(); + for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) { + if (gamepad->joystick->accel && gamepad->joystick->accel_sensor == sensor) { + float gamepad_data[3]; + AdjustSensorOrientation(gamepad->joystick, data, gamepad_data); + SDL_SendJoystickSensor(timestamp, gamepad->joystick, SDL_SENSOR_ACCEL, sensor_timestamp, gamepad_data, SDL_arraysize(gamepad_data)); + } + if (gamepad->joystick->gyro && gamepad->joystick->gyro_sensor == sensor) { + float gamepad_data[3]; + AdjustSensorOrientation(gamepad->joystick, data, gamepad_data); + SDL_SendJoystickSensor(timestamp, gamepad->joystick, SDL_SENSOR_GYRO, sensor_timestamp, gamepad_data, SDL_arraysize(gamepad_data)); + } + } + SDL_UnlockJoysticks(); +} + +static void PushMappingChangeTracking(void) +{ + MappingChangeTracker *tracker; + int i, num_joysticks; + + SDL_AssertJoysticksLocked(); + + if (s_mappingChangeTracker) { + ++s_mappingChangeTracker->refcount; + return; + } + s_mappingChangeTracker = (MappingChangeTracker *)SDL_calloc(1, sizeof(*tracker)); + s_mappingChangeTracker->refcount = 1; + + // Save the list of joysticks and associated mappings + tracker = s_mappingChangeTracker; + tracker->joysticks = SDL_GetJoysticks(&num_joysticks); + if (!tracker->joysticks) { + return; + } + if (num_joysticks == 0) { + return; + } + tracker->joystick_mappings = (GamepadMapping_t **)SDL_malloc(num_joysticks * sizeof(*tracker->joystick_mappings)); + if (!tracker->joystick_mappings) { + return; + } + for (i = 0; i < num_joysticks; ++i) { + tracker->joystick_mappings[i] = SDL_PrivateGetGamepadMapping(tracker->joysticks[i], false); + } +} + +static void AddMappingChangeTracking(GamepadMapping_t *mapping) +{ + MappingChangeTracker *tracker; + int num_mappings; + GamepadMapping_t **new_mappings; + + SDL_AssertJoysticksLocked(); + + SDL_assert(s_mappingChangeTracker != NULL); + tracker = s_mappingChangeTracker; + num_mappings = tracker->num_changed_mappings; + new_mappings = (GamepadMapping_t **)SDL_realloc(tracker->changed_mappings, (num_mappings + 1) * sizeof(*new_mappings)); + if (new_mappings) { + tracker->changed_mappings = new_mappings; + tracker->changed_mappings[num_mappings] = mapping; + tracker->num_changed_mappings = (num_mappings + 1); + } +} + +static bool HasMappingChangeTracking(MappingChangeTracker *tracker, GamepadMapping_t *mapping) +{ + int i; + + SDL_AssertJoysticksLocked(); + + for (i = 0; i < tracker->num_changed_mappings; ++i) { + if (tracker->changed_mappings[i] == mapping) { + return true; + } + } + return false; +} + +static void PopMappingChangeTracking(void) +{ + int i; + MappingChangeTracker *tracker; + + SDL_AssertJoysticksLocked(); + + SDL_assert(s_mappingChangeTracker != NULL); + tracker = s_mappingChangeTracker; + --tracker->refcount; + if (tracker->refcount > 0) { + return; + } + s_mappingChangeTracker = NULL; + + // Now check to see what gamepads changed because of the mapping changes + if (tracker->joysticks && tracker->joystick_mappings) { + for (i = 0; tracker->joysticks[i]; ++i) { + // Looking up the new mapping might create one and associate it with the gamepad (and generate events) + SDL_JoystickID joystick = tracker->joysticks[i]; + SDL_Gamepad *gamepad = SDL_GetGamepadFromID(joystick); + GamepadMapping_t *new_mapping = SDL_PrivateGetGamepadMapping(joystick, false); + GamepadMapping_t *old_mapping = gamepad ? gamepad->mapping : tracker->joystick_mappings[i]; + + if (new_mapping && !old_mapping) { + SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)true, true); + SDL_PrivateGamepadAdded(joystick); + } else if (old_mapping && !new_mapping) { + SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)false, true); + SDL_PrivateGamepadRemoved(joystick); + } else if (old_mapping != new_mapping || HasMappingChangeTracking(tracker, new_mapping)) { + if (gamepad) { + SDL_PrivateLoadButtonMapping(gamepad, new_mapping); + } + SDL_PrivateGamepadRemapped(joystick); + } + } + } + + SDL_free(tracker->joysticks); + SDL_free(tracker->joystick_mappings); + SDL_free(tracker->changed_mappings); + SDL_free(tracker); +} + +#ifdef SDL_PLATFORM_ANDROID +/* + * Helper function to guess at a mapping based on the elements reported for this gamepad + */ +static GamepadMapping_t *SDL_CreateMappingForAndroidGamepad(SDL_GUID guid) +{ + const int face_button_mask = ((1 << SDL_GAMEPAD_BUTTON_SOUTH) | + (1 << SDL_GAMEPAD_BUTTON_EAST) | + (1 << SDL_GAMEPAD_BUTTON_WEST) | + (1 << SDL_GAMEPAD_BUTTON_NORTH)); + bool existing; + char mapping_string[1024]; + int button_mask; + int axis_mask; + + button_mask = SDL_Swap16LE(*(Uint16 *)(&guid.data[sizeof(guid.data) - 4])); + axis_mask = SDL_Swap16LE(*(Uint16 *)(&guid.data[sizeof(guid.data) - 2])); + if (!button_mask && !axis_mask) { + // Accelerometer, shouldn't have a gamepad mapping + return NULL; + } + if (!(button_mask & face_button_mask)) { + // We don't know what buttons or axes are supported, don't make up a mapping + return NULL; + } + + SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string)); + + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_SOUTH)) { + SDL_strlcat(mapping_string, "a:b0,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_EAST)) { + SDL_strlcat(mapping_string, "b:b1,", sizeof(mapping_string)); + } else if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) { + // Use the back button as "B" for easy UI navigation with TV remotes + SDL_strlcat(mapping_string, "b:b4,", sizeof(mapping_string)); + button_mask &= ~(1 << SDL_GAMEPAD_BUTTON_BACK); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_WEST)) { + SDL_strlcat(mapping_string, "x:b2,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_NORTH)) { + SDL_strlcat(mapping_string, "y:b3,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) { + SDL_strlcat(mapping_string, "back:b4,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_GUIDE)) { + // The guide button generally isn't functional (or acts as a home button) on most Android gamepads before Android 11 + if (SDL_GetAndroidSDKVersion() >= 30 /* Android 11 */) { + SDL_strlcat(mapping_string, "guide:b5,", sizeof(mapping_string)); + } + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) { + SDL_strlcat(mapping_string, "start:b6,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) { + SDL_strlcat(mapping_string, "leftstick:b7,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) { + SDL_strlcat(mapping_string, "rightstick:b8,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) { + SDL_strlcat(mapping_string, "leftshoulder:b9,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) { + SDL_strlcat(mapping_string, "rightshoulder:b10,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_UP)) { + SDL_strlcat(mapping_string, "dpup:b11,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN)) { + SDL_strlcat(mapping_string, "dpdown:b12,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT)) { + SDL_strlcat(mapping_string, "dpleft:b13,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) { + SDL_strlcat(mapping_string, "dpright:b14,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTX)) { + SDL_strlcat(mapping_string, "leftx:a0,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTY)) { + SDL_strlcat(mapping_string, "lefty:a1,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTX)) { + SDL_strlcat(mapping_string, "rightx:a2,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTY)) { + SDL_strlcat(mapping_string, "righty:a3,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER)) { + SDL_strlcat(mapping_string, "lefttrigger:a4,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) { + SDL_strlcat(mapping_string, "righttrigger:a5,", sizeof(mapping_string)); + } + + return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT); +} +#endif // SDL_PLATFORM_ANDROID + +/* + * Helper function to guess at a mapping for HIDAPI gamepads + */ +static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) +{ + bool existing; + char mapping_string[1024]; + Uint16 vendor; + Uint16 product; + + SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string)); + + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + + if ((vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) || + (vendor == USB_VENDOR_DRAGONRISE && + (product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 || + product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2))) { + // GameCube driver has 12 buttons and 6 axes + SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b2,y:b3,", sizeof(mapping_string)); + } else if (vendor == USB_VENDOR_NINTENDO && + (guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft || + guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCRight || + guid.data[15] == k_eSwitchDeviceInfoControllerType_NESLeft || + guid.data[15] == k_eSwitchDeviceInfoControllerType_NESRight || + guid.data[15] == k_eSwitchDeviceInfoControllerType_SNES || + guid.data[15] == k_eSwitchDeviceInfoControllerType_N64 || + guid.data[15] == k_eSwitchDeviceInfoControllerType_SEGA_Genesis || + guid.data[15] == k_eWiiExtensionControllerType_None || + guid.data[15] == k_eWiiExtensionControllerType_Nunchuk || + guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConLeft || + guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConRight)) { + switch (guid.data[15]) { + case k_eSwitchDeviceInfoControllerType_HVCLeft: + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string)); + break; + case k_eSwitchDeviceInfoControllerType_HVCRight: + SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,", sizeof(mapping_string)); + break; + case k_eSwitchDeviceInfoControllerType_NESLeft: + case k_eSwitchDeviceInfoControllerType_NESRight: + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string)); + break; + case k_eSwitchDeviceInfoControllerType_SNES: + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,", sizeof(mapping_string)); + break; + case k_eSwitchDeviceInfoControllerType_N64: + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b11,", sizeof(mapping_string)); + break; + case k_eSwitchDeviceInfoControllerType_SEGA_Genesis: + SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b11,", sizeof(mapping_string)); + break; + case k_eWiiExtensionControllerType_None: + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,start:b6,x:b2,y:b3,", sizeof(mapping_string)); + break; + case k_eWiiExtensionControllerType_Nunchuk: + { + // FIXME: Should we map this to the left or right side? + const bool map_nunchuck_left_side = true; + + if (map_nunchuck_left_side) { + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,start:b6,x:b2,y:b3,", sizeof(mapping_string)); + } else { + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,rightshoulder:b9,righttrigger:a4,rightx:a0,righty:a1,start:b6,x:b2,y:b3,", sizeof(mapping_string)); + } + } break; + default: + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false)) { + // Vertical mode + if (guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConLeft) { + SDL_strlcat(mapping_string, "back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b11,paddle2:b13,paddle4:b15,", sizeof(mapping_string)); + } else { + SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,paddle1:b12,paddle3:b14,", sizeof(mapping_string)); + } + } else { + // Mini gamepad mode + if (guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConLeft) { + SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,paddle2:b13,paddle4:b15,", sizeof(mapping_string)); + } else { + SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,paddle1:b12,paddle3:b14,", sizeof(mapping_string)); + } + } + break; + } + } else { + // All other gamepads have the standard set of 19 buttons and 6 axes + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", sizeof(mapping_string)); + + if (SDL_IsJoystickXboxSeriesX(vendor, product)) { + // XBox Series X Controllers have a share button under the guide button + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); + } else if (SDL_IsJoystickXboxOneElite(vendor, product)) { + // XBox One Elite Controllers have 4 back paddle buttons + SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,", sizeof(mapping_string)); + } else if (SDL_IsJoystickSteamController(vendor, product)) { + // Steam controllers have 2 back paddle buttons + SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,", sizeof(mapping_string)); + } else if (SDL_IsJoystickNintendoSwitchPro(vendor, product) || + SDL_IsJoystickNintendoSwitchProInputOnly(vendor, product)) { + // Nintendo Switch Pro controllers have a screenshot button + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); + } else if (SDL_IsJoystickNintendoSwitchJoyConPair(vendor, product)) { + // The Nintendo Switch Joy-Con combined controllers has a share button and paddles + SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,", sizeof(mapping_string)); + } else if (SDL_IsJoystickAmazonLunaController(vendor, product)) { + // Amazon Luna Controller has a mic button under the guide button + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); + } else if (SDL_IsJoystickGoogleStadiaController(vendor, product)) { + // The Google Stadia controller has a share button and a Google Assistant button + SDL_strlcat(mapping_string, "misc1:b11,misc2:b12", sizeof(mapping_string)); + } else if (SDL_IsJoystickNVIDIASHIELDController(vendor, product)) { + // The NVIDIA SHIELD controller has a share button between back and start buttons + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); + + if (product == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) { + // The original SHIELD controller has a touchpad and plus/minus buttons as well + SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14", sizeof(mapping_string)); + } + } else if (SDL_IsJoystickHoriSteamController(vendor, product)) { + /* The Wireless HORIPad for Steam has QAM, Steam, Capsense L/R Sticks, 2 rear buttons, and 2 misc buttons */ + SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17", sizeof(mapping_string)); + } else { + switch (SDL_GetGamepadTypeFromGUID(guid, NULL)) { + case SDL_GAMEPAD_TYPE_PS4: + // PS4 controllers have an additional touchpad button + SDL_strlcat(mapping_string, "touchpad:b11,", sizeof(mapping_string)); + break; + case SDL_GAMEPAD_TYPE_PS5: + // PS5 controllers have a microphone button and an additional touchpad button + SDL_strlcat(mapping_string, "touchpad:b11,misc1:b12,", sizeof(mapping_string)); + // DualSense Edge controllers have paddles + if (SDL_IsJoystickDualSenseEdge(vendor, product)) { + SDL_strlcat(mapping_string, "paddle1:b16,paddle2:b15,paddle3:b14,paddle4:b13,", sizeof(mapping_string)); + } + break; + default: + if (vendor == 0 && product == 0) { + // This is a Bluetooth Nintendo Switch Pro controller + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); + } + break; + } + } + } + + return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT); +} + +/* + * Helper function to guess at a mapping for RAWINPUT gamepads + */ +static GamepadMapping_t *SDL_CreateMappingForRAWINPUTGamepad(SDL_GUID guid) +{ + bool existing; + char mapping_string[1024]; + + SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b1,x:b2,y:b3,back:b6,guide:b10,start:b7,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,", sizeof(mapping_string)); + + return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT); +} + +/* + * Helper function to guess at a mapping for WGI gamepads + */ +static GamepadMapping_t *SDL_CreateMappingForWGIGamepad(SDL_GUID guid) +{ + bool existing; + char mapping_string[1024]; + + if (guid.data[15] != SDL_JOYSTICK_TYPE_GAMEPAD) { + return NULL; + } + + SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:b10,dpdown:b12,dpleft:b13,dpright:b11,leftx:a1,lefty:a0~,rightx:a3,righty:a2~,lefttrigger:a4,righttrigger:a5,", sizeof(mapping_string)); + + return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT); +} + +/* + * Helper function to scan the mappings database for a gamepad with the specified GUID + */ +static GamepadMapping_t *SDL_PrivateMatchGamepadMappingForGUID(SDL_GUID guid, bool match_version, bool exact_match_crc) +{ + GamepadMapping_t *mapping, *best_match = NULL; + Uint16 crc = 0; + + SDL_AssertJoysticksLocked(); + + SDL_GetJoystickGUIDInfo(guid, NULL, NULL, NULL, &crc); + + // Clear the CRC from the GUID for matching, the mappings never include it in the GUID + SDL_SetJoystickGUIDCRC(&guid, 0); + + if (!match_version) { + SDL_SetJoystickGUIDVersion(&guid, 0); + } + + for (mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) { + SDL_GUID mapping_guid; + + if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) { + continue; + } + + SDL_memcpy(&mapping_guid, &mapping->guid, sizeof(mapping_guid)); + if (!match_version) { + SDL_SetJoystickGUIDVersion(&mapping_guid, 0); + } + + if (SDL_memcmp(&guid, &mapping_guid, sizeof(guid)) == 0) { + const char *crc_string = SDL_strstr(mapping->mapping, SDL_GAMEPAD_CRC_FIELD); + if (crc_string) { + Uint16 mapping_crc = (Uint16)SDL_strtol(crc_string + SDL_GAMEPAD_CRC_FIELD_SIZE, NULL, 16); + if (mapping_crc != crc) { + // This mapping specified a CRC and they don't match + continue; + } + + // An exact match, including CRC + return mapping; + } else if (crc && exact_match_crc) { + return NULL; + } + + if (!best_match) { + best_match = mapping; + } + } + } + return best_match; +} + +/* + * Helper function to scan the mappings database for a gamepad with the specified GUID + */ +static GamepadMapping_t *SDL_PrivateGetGamepadMappingForGUID(SDL_GUID guid, bool adding_mapping) +{ + GamepadMapping_t *mapping; + + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, true, adding_mapping); + if (mapping) { + return mapping; + } + + if (adding_mapping) { + // We didn't find an existing mapping + return NULL; + } + + // Try harder to get the best match, or create a mapping + + if (SDL_JoystickGUIDUsesVersion(guid)) { + // Try again, ignoring the version + mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, false, false); + if (mapping) { + return mapping; + } + } + +#ifdef SDL_JOYSTICK_XINPUT + if (SDL_IsJoystickXInput(guid)) { + // This is an XInput device + return s_pXInputMapping; + } +#endif + if (SDL_IsJoystickHIDAPI(guid)) { + mapping = SDL_CreateMappingForHIDAPIGamepad(guid); + } else if (SDL_IsJoystickRAWINPUT(guid)) { + mapping = SDL_CreateMappingForRAWINPUTGamepad(guid); + } else if (SDL_IsJoystickWGI(guid)) { + mapping = SDL_CreateMappingForWGIGamepad(guid); + } else if (SDL_IsJoystickVIRTUAL(guid)) { + // We'll pick up a robust mapping in VIRTUAL_JoystickGetGamepadMapping +#ifdef SDL_PLATFORM_ANDROID + } else { + mapping = SDL_CreateMappingForAndroidGamepad(guid); +#endif + } + return mapping; +} + +static const char *map_StringForGamepadType[] = { + "unknown", + "standard", + "xbox360", + "xboxone", + "ps3", + "ps4", + "ps5", + "switchpro", + "joyconleft", + "joyconright", + "joyconpair" +}; +SDL_COMPILE_TIME_ASSERT(map_StringForGamepadType, SDL_arraysize(map_StringForGamepadType) == SDL_GAMEPAD_TYPE_COUNT); + +/* + * convert a string to its enum equivalent + */ +SDL_GamepadType SDL_GetGamepadTypeFromString(const char *str) +{ + int i; + + if (!str || str[0] == '\0') { + return SDL_GAMEPAD_TYPE_UNKNOWN; + } + + if (*str == '+' || *str == '-') { + ++str; + } + + for (i = 0; i < SDL_arraysize(map_StringForGamepadType); ++i) { + if (SDL_strcasecmp(str, map_StringForGamepadType[i]) == 0) { + return (SDL_GamepadType)i; + } + } + return SDL_GAMEPAD_TYPE_UNKNOWN; +} + +/* + * convert an enum to its string equivalent + */ +const char *SDL_GetGamepadStringForType(SDL_GamepadType type) +{ + if (type >= SDL_GAMEPAD_TYPE_STANDARD && type < SDL_GAMEPAD_TYPE_COUNT) { + return map_StringForGamepadType[type]; + } + return NULL; +} + +static const char *map_StringForGamepadAxis[] = { + "leftx", + "lefty", + "rightx", + "righty", + "lefttrigger", + "righttrigger" +}; +SDL_COMPILE_TIME_ASSERT(map_StringForGamepadAxis, SDL_arraysize(map_StringForGamepadAxis) == SDL_GAMEPAD_AXIS_COUNT); + +/* + * convert a string to its enum equivalent + */ +SDL_GamepadAxis SDL_GetGamepadAxisFromString(const char *str) +{ + int i; + + if (!str || str[0] == '\0') { + return SDL_GAMEPAD_AXIS_INVALID; + } + + if (*str == '+' || *str == '-') { + ++str; + } + + for (i = 0; i < SDL_arraysize(map_StringForGamepadAxis); ++i) { + if (SDL_strcasecmp(str, map_StringForGamepadAxis[i]) == 0) { + return (SDL_GamepadAxis)i; + } + } + return SDL_GAMEPAD_AXIS_INVALID; +} + +/* + * convert an enum to its string equivalent + */ +const char *SDL_GetGamepadStringForAxis(SDL_GamepadAxis axis) +{ + if (axis > SDL_GAMEPAD_AXIS_INVALID && axis < SDL_GAMEPAD_AXIS_COUNT) { + return map_StringForGamepadAxis[axis]; + } + return NULL; +} + +static const char *map_StringForGamepadButton[] = { + "a", + "b", + "x", + "y", + "back", + "guide", + "start", + "leftstick", + "rightstick", + "leftshoulder", + "rightshoulder", + "dpup", + "dpdown", + "dpleft", + "dpright", + "misc1", + "paddle1", + "paddle2", + "paddle3", + "paddle4", + "touchpad", + "misc2", + "misc3", + "misc4", + "misc5", + "misc6" +}; +SDL_COMPILE_TIME_ASSERT(map_StringForGamepadButton, SDL_arraysize(map_StringForGamepadButton) == SDL_GAMEPAD_BUTTON_COUNT); + +/* + * convert a string to its enum equivalent + */ +static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, bool baxy) +{ + int i; + + if (!str || str[0] == '\0') { + return SDL_GAMEPAD_BUTTON_INVALID; + } + + for (i = 0; i < SDL_arraysize(map_StringForGamepadButton); ++i) { + if (SDL_strcasecmp(str, map_StringForGamepadButton[i]) == 0) { + if (baxy) { + // Need to swap face buttons + switch (i) { + case SDL_GAMEPAD_BUTTON_SOUTH: + return SDL_GAMEPAD_BUTTON_EAST; + case SDL_GAMEPAD_BUTTON_EAST: + return SDL_GAMEPAD_BUTTON_SOUTH; + case SDL_GAMEPAD_BUTTON_WEST: + return SDL_GAMEPAD_BUTTON_NORTH; + case SDL_GAMEPAD_BUTTON_NORTH: + return SDL_GAMEPAD_BUTTON_WEST; + default: + break; + } + } + return (SDL_GamepadButton)i; + } + } + return SDL_GAMEPAD_BUTTON_INVALID; +} +SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str) +{ + return SDL_PrivateGetGamepadButtonFromString(str, false); +} + +/* + * convert an enum to its string equivalent + */ +const char *SDL_GetGamepadStringForButton(SDL_GamepadButton button) +{ + if (button > SDL_GAMEPAD_BUTTON_INVALID && button < SDL_GAMEPAD_BUTTON_COUNT) { + return map_StringForGamepadButton[button]; + } + return NULL; +} + +/* + * given a gamepad button name and a joystick name update our mapping structure with it + */ +static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGameButton, const char *szJoystickButton) +{ + SDL_GamepadBinding bind; + SDL_GamepadButton button; + SDL_GamepadAxis axis; + bool invert_input = false; + char half_axis_input = 0; + char half_axis_output = 0; + int i; + SDL_GamepadBinding *new_bindings; + bool baxy_mapping = false; + + SDL_AssertJoysticksLocked(); + + SDL_zero(bind); + + if (*szGameButton == '+' || *szGameButton == '-') { + half_axis_output = *szGameButton++; + } + + if (SDL_strstr(gamepad->mapping->mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { + baxy_mapping = true; + } + + axis = SDL_GetGamepadAxisFromString(szGameButton); + button = SDL_PrivateGetGamepadButtonFromString(szGameButton, baxy_mapping); + if (axis != SDL_GAMEPAD_AXIS_INVALID) { + bind.output_type = SDL_GAMEPAD_BINDTYPE_AXIS; + bind.output.axis.axis = axis; + if (axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { + bind.output.axis.axis_min = 0; + bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MAX; + } else { + if (half_axis_output == '+') { + bind.output.axis.axis_min = 0; + bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MAX; + } else if (half_axis_output == '-') { + bind.output.axis.axis_min = 0; + bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MIN; + } else { + bind.output.axis.axis_min = SDL_JOYSTICK_AXIS_MIN; + bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MAX; + } + } + } else if (button != SDL_GAMEPAD_BUTTON_INVALID) { + bind.output_type = SDL_GAMEPAD_BINDTYPE_BUTTON; + bind.output.button = button; + } else { + return false; + } + + if (*szJoystickButton == '+' || *szJoystickButton == '-') { + half_axis_input = *szJoystickButton++; + } + if (szJoystickButton[SDL_strlen(szJoystickButton) - 1] == '~') { + invert_input = true; + } + + if (szJoystickButton[0] == 'a' && SDL_isdigit((unsigned char)szJoystickButton[1])) { + bind.input_type = SDL_GAMEPAD_BINDTYPE_AXIS; + bind.input.axis.axis = SDL_atoi(&szJoystickButton[1]); + if (half_axis_input == '+') { + bind.input.axis.axis_min = 0; + bind.input.axis.axis_max = SDL_JOYSTICK_AXIS_MAX; + } else if (half_axis_input == '-') { + bind.input.axis.axis_min = 0; + bind.input.axis.axis_max = SDL_JOYSTICK_AXIS_MIN; + } else { + bind.input.axis.axis_min = SDL_JOYSTICK_AXIS_MIN; + bind.input.axis.axis_max = SDL_JOYSTICK_AXIS_MAX; + } + if (invert_input) { + int tmp = bind.input.axis.axis_min; + bind.input.axis.axis_min = bind.input.axis.axis_max; + bind.input.axis.axis_max = tmp; + } + } else if (szJoystickButton[0] == 'b' && SDL_isdigit((unsigned char)szJoystickButton[1])) { + bind.input_type = SDL_GAMEPAD_BINDTYPE_BUTTON; + bind.input.button = SDL_atoi(&szJoystickButton[1]); + } else if (szJoystickButton[0] == 'h' && SDL_isdigit((unsigned char)szJoystickButton[1]) && + szJoystickButton[2] == '.' && SDL_isdigit((unsigned char)szJoystickButton[3])) { + int hat = SDL_atoi(&szJoystickButton[1]); + int mask = SDL_atoi(&szJoystickButton[3]); + bind.input_type = SDL_GAMEPAD_BINDTYPE_HAT; + bind.input.hat.hat = hat; + bind.input.hat.hat_mask = mask; + } else { + return false; + } + + for (i = 0; i < gamepad->num_bindings; ++i) { + if (SDL_memcmp(&gamepad->bindings[i], &bind, sizeof(bind)) == 0) { + // We already have this binding, could be different face button names? + return true; + } + } + + ++gamepad->num_bindings; + new_bindings = (SDL_GamepadBinding *)SDL_realloc(gamepad->bindings, gamepad->num_bindings * sizeof(*gamepad->bindings)); + if (!new_bindings) { + SDL_free(gamepad->bindings); + gamepad->num_bindings = 0; + gamepad->bindings = NULL; + return false; + } + gamepad->bindings = new_bindings; + gamepad->bindings[gamepad->num_bindings - 1] = bind; + return true; +} + +/* + * given a gamepad mapping string update our mapping object + */ +static bool SDL_PrivateParseGamepadConfigString(SDL_Gamepad *gamepad, const char *pchString) +{ + char szGameButton[20]; + char szJoystickButton[20]; + bool bGameButton = true; + int i = 0; + const char *pchPos = pchString; + + SDL_zeroa(szGameButton); + SDL_zeroa(szJoystickButton); + + while (pchPos && *pchPos) { + if (*pchPos == ':') { + i = 0; + bGameButton = false; + } else if (*pchPos == ' ') { + + } else if (*pchPos == ',') { + i = 0; + bGameButton = true; + SDL_PrivateParseGamepadElement(gamepad, szGameButton, szJoystickButton); + SDL_zeroa(szGameButton); + SDL_zeroa(szJoystickButton); + + } else if (bGameButton) { + if (i >= sizeof(szGameButton)) { + szGameButton[sizeof(szGameButton) - 1] = '\0'; + return SDL_SetError("Button name too large: %s", szGameButton); + } + szGameButton[i] = *pchPos; + i++; + } else { + if (i >= sizeof(szJoystickButton)) { + szJoystickButton[sizeof(szJoystickButton) - 1] = '\0'; + return SDL_SetError("Joystick button name too large: %s", szJoystickButton); + } + szJoystickButton[i] = *pchPos; + i++; + } + pchPos++; + } + + // No more values if the string was terminated by a comma. Don't report an error. + if (szGameButton[0] != '\0' || szJoystickButton[0] != '\0') { + SDL_PrivateParseGamepadElement(gamepad, szGameButton, szJoystickButton); + } + return true; +} + +static void SDL_UpdateGamepadType(SDL_Gamepad *gamepad) +{ + char *type_string, *comma; + + SDL_AssertJoysticksLocked(); + + gamepad->type = SDL_GAMEPAD_TYPE_UNKNOWN; + + type_string = SDL_strstr(gamepad->mapping->mapping, SDL_GAMEPAD_TYPE_FIELD); + if (type_string) { + type_string += SDL_GAMEPAD_TYPE_FIELD_SIZE; + comma = SDL_strchr(type_string, ','); + if (comma) { + *comma = '\0'; + gamepad->type = SDL_GetGamepadTypeFromString(type_string); + *comma = ','; + } else { + gamepad->type = SDL_GetGamepadTypeFromString(type_string); + } + } + if (gamepad->type == SDL_GAMEPAD_TYPE_UNKNOWN) { + gamepad->type = SDL_GetRealGamepadTypeForID(gamepad->joystick->instance_id); + } +} + +static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleFromString(const char *string) +{ + if (SDL_strcmp(string, "abxy") == 0) { + return SDL_GAMEPAD_FACE_STYLE_ABXY; + } else if (SDL_strcmp(string, "bayx") == 0) { + return SDL_GAMEPAD_FACE_STYLE_BAYX; + } else if (SDL_strcmp(string, "sony") == 0) { + return SDL_GAMEPAD_FACE_STYLE_SONY; + } else { + return SDL_GAMEPAD_FACE_STYLE_UNKNOWN; + } +} + +static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleForGamepadType(SDL_GamepadType type) +{ + switch (type) { + case SDL_GAMEPAD_TYPE_PS3: + case SDL_GAMEPAD_TYPE_PS4: + case SDL_GAMEPAD_TYPE_PS5: + return SDL_GAMEPAD_FACE_STYLE_SONY; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + return SDL_GAMEPAD_FACE_STYLE_BAYX; + default: + return SDL_GAMEPAD_FACE_STYLE_ABXY; + } +} + +static void SDL_UpdateGamepadFaceStyle(SDL_Gamepad *gamepad) +{ + char *face_string, *comma; + + SDL_AssertJoysticksLocked(); + + gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_UNKNOWN; + + face_string = SDL_strstr(gamepad->mapping->mapping, SDL_GAMEPAD_FACE_FIELD); + if (face_string) { + face_string += SDL_GAMEPAD_TYPE_FIELD_SIZE; + comma = SDL_strchr(face_string, ','); + if (comma) { + *comma = '\0'; + gamepad->face_style = SDL_GetGamepadFaceStyleFromString(face_string); + *comma = ','; + } else { + gamepad->face_style = SDL_GetGamepadFaceStyleFromString(face_string); + } + } + + if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN && + SDL_strstr(gamepad->mapping->mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") != NULL) { + // This controller uses Nintendo button style + gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_BAYX; + } + if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN) { + gamepad->face_style = SDL_GetGamepadFaceStyleForGamepadType(gamepad->type); + } +} + +static void SDL_FixupHIDAPIMapping(SDL_Gamepad *gamepad) +{ + // Check to see if we need fixup + bool need_fixup = false; + for (int i = 0; i < gamepad->num_bindings; ++i) { + SDL_GamepadBinding *binding = &gamepad->bindings[i]; + if (binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON && + binding->output.button >= SDL_GAMEPAD_BUTTON_DPAD_UP) { + if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON && + binding->input.button == binding->output.button) { + // Old style binding + need_fixup = true; + } + break; + } + } + if (!need_fixup) { + return; + } + + for (int i = 0; i < gamepad->num_bindings; ++i) { + SDL_GamepadBinding *binding = &gamepad->bindings[i]; + if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON && + binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON) { + switch (binding->output.button) { + case SDL_GAMEPAD_BUTTON_DPAD_UP: + binding->input_type = SDL_GAMEPAD_BINDTYPE_HAT; + binding->input.hat.hat = 0; + binding->input.hat.hat_mask = SDL_HAT_UP; + break; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + binding->input_type = SDL_GAMEPAD_BINDTYPE_HAT; + binding->input.hat.hat = 0; + binding->input.hat.hat_mask = SDL_HAT_DOWN; + break; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + binding->input_type = SDL_GAMEPAD_BINDTYPE_HAT; + binding->input.hat.hat = 0; + binding->input.hat.hat_mask = SDL_HAT_LEFT; + break; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + binding->input_type = SDL_GAMEPAD_BINDTYPE_HAT; + binding->input.hat.hat = 0; + binding->input.hat.hat_mask = SDL_HAT_RIGHT; + break; + default: + if (binding->output.button > SDL_GAMEPAD_BUTTON_DPAD_RIGHT) { + binding->input.button -= 4; + } + break; + } + } + } +} + +/* + * Make a new button mapping struct + */ +static void SDL_PrivateLoadButtonMapping(SDL_Gamepad *gamepad, GamepadMapping_t *pGamepadMapping) +{ + int i; + + SDL_AssertJoysticksLocked(); + + gamepad->name = pGamepadMapping->name; + gamepad->num_bindings = 0; + gamepad->mapping = pGamepadMapping; + if (gamepad->joystick->naxes != 0 && gamepad->last_match_axis) { + SDL_memset(gamepad->last_match_axis, 0, gamepad->joystick->naxes * sizeof(*gamepad->last_match_axis)); + } + + SDL_UpdateGamepadType(gamepad); + SDL_UpdateGamepadFaceStyle(gamepad); + + SDL_PrivateParseGamepadConfigString(gamepad, pGamepadMapping->mapping); + + if (SDL_IsJoystickHIDAPI(pGamepadMapping->guid)) { + SDL_FixupHIDAPIMapping(gamepad); + } + + // Set the zero point for triggers + for (i = 0; i < gamepad->num_bindings; ++i) { + SDL_GamepadBinding *binding = &gamepad->bindings[i]; + if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS && + binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS && + (binding->output.axis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || + binding->output.axis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) { + if (binding->input.axis.axis < gamepad->joystick->naxes) { + gamepad->joystick->axes[binding->input.axis.axis].value = + gamepad->joystick->axes[binding->input.axis.axis].zero = (Sint16)binding->input.axis.axis_min; + } + } + } +} + +/* + * grab the guid string from a mapping string + */ +static char *SDL_PrivateGetGamepadGUIDFromMappingString(const char *pMapping) +{ + const char *pFirstComma = SDL_strchr(pMapping, ','); + if (pFirstComma) { + char *pchGUID = (char *)SDL_malloc(pFirstComma - pMapping + 1); + if (!pchGUID) { + return NULL; + } + SDL_memcpy(pchGUID, pMapping, pFirstComma - pMapping); + pchGUID[pFirstComma - pMapping] = '\0'; + + // Convert old style GUIDs to the new style in 2.0.5 +#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) + if (SDL_strlen(pchGUID) == 32 && + SDL_memcmp(&pchGUID[20], "504944564944", 12) == 0) { + SDL_memcpy(&pchGUID[20], "000000000000", 12); + SDL_memcpy(&pchGUID[16], &pchGUID[4], 4); + SDL_memcpy(&pchGUID[8], &pchGUID[0], 4); + SDL_memcpy(&pchGUID[0], "03000000", 8); + } +#elif defined(SDL_PLATFORM_MACOS) + if (SDL_strlen(pchGUID) == 32 && + SDL_memcmp(&pchGUID[4], "000000000000", 12) == 0 && + SDL_memcmp(&pchGUID[20], "000000000000", 12) == 0) { + SDL_memcpy(&pchGUID[20], "000000000000", 12); + SDL_memcpy(&pchGUID[8], &pchGUID[0], 4); + SDL_memcpy(&pchGUID[0], "03000000", 8); + } +#endif + return pchGUID; + } + return NULL; +} + +/* + * grab the name string from a mapping string + */ +static char *SDL_PrivateGetGamepadNameFromMappingString(const char *pMapping) +{ + const char *pFirstComma, *pSecondComma; + char *pchName; + + pFirstComma = SDL_strchr(pMapping, ','); + if (!pFirstComma) { + return NULL; + } + + pSecondComma = SDL_strchr(pFirstComma + 1, ','); + if (!pSecondComma) { + return NULL; + } + + pchName = (char *)SDL_malloc(pSecondComma - pFirstComma); + if (!pchName) { + return NULL; + } + SDL_memcpy(pchName, pFirstComma + 1, pSecondComma - pFirstComma); + pchName[pSecondComma - pFirstComma - 1] = 0; + return pchName; +} + +/* + * grab the button mapping string from a mapping string + */ +static char *SDL_PrivateGetGamepadMappingFromMappingString(const char *pMapping) +{ + const char *pFirstComma, *pSecondComma; + char *result; + size_t length; + + pFirstComma = SDL_strchr(pMapping, ','); + if (!pFirstComma) { + return NULL; + } + + pSecondComma = SDL_strchr(pFirstComma + 1, ','); + if (!pSecondComma) { + return NULL; + } + + // Skip whitespace + while (SDL_isspace(pSecondComma[1])) { + ++pSecondComma; + } + + result = SDL_strdup(pSecondComma + 1); // mapping is everything after the 3rd comma + + // Trim whitespace + length = SDL_strlen(result); + while (length > 0 && SDL_isspace(result[length - 1])) { + --length; + } + result[length] = '\0'; + + return result; +} + +/* + * Helper function to add a mapping for a guid + */ +static GamepadMapping_t *SDL_PrivateAddMappingForGUID(SDL_GUID jGUID, const char *mappingString, bool *existing, SDL_GamepadMappingPriority priority) +{ + char *pchName; + char *pchMapping; + GamepadMapping_t *pGamepadMapping; + Uint16 crc; + + SDL_AssertJoysticksLocked(); + + pchName = SDL_PrivateGetGamepadNameFromMappingString(mappingString); + if (!pchName) { + SDL_SetError("Couldn't parse name from %s", mappingString); + return NULL; + } + + pchMapping = SDL_PrivateGetGamepadMappingFromMappingString(mappingString); + if (!pchMapping) { + SDL_free(pchName); + SDL_SetError("Couldn't parse %s", mappingString); + return NULL; + } + + // Fix up the GUID and the mapping with the CRC, if needed + SDL_GetJoystickGUIDInfo(jGUID, NULL, NULL, NULL, &crc); + if (crc) { + // Make sure the mapping has the CRC + char *new_mapping; + const char *optional_comma; + size_t mapping_length; + char *crc_end = ""; + char *crc_string = SDL_strstr(pchMapping, SDL_GAMEPAD_CRC_FIELD); + if (crc_string) { + crc_end = SDL_strchr(crc_string, ','); + if (crc_end) { + ++crc_end; + } else { + crc_end = ""; + } + *crc_string = '\0'; + } + + // Make sure there's a comma before the CRC + mapping_length = SDL_strlen(pchMapping); + if (mapping_length == 0 || pchMapping[mapping_length - 1] == ',') { + optional_comma = ""; + } else { + optional_comma = ","; + } + + if (SDL_asprintf(&new_mapping, "%s%s%s%.4x,%s", pchMapping, optional_comma, SDL_GAMEPAD_CRC_FIELD, crc, crc_end) >= 0) { + SDL_free(pchMapping); + pchMapping = new_mapping; + } + } else { + // Make sure the GUID has the CRC, for matching purposes + char *crc_string = SDL_strstr(pchMapping, SDL_GAMEPAD_CRC_FIELD); + if (crc_string) { + crc = (Uint16)SDL_strtol(crc_string + SDL_GAMEPAD_CRC_FIELD_SIZE, NULL, 16); + if (crc) { + SDL_SetJoystickGUIDCRC(&jGUID, crc); + } + } + } + + PushMappingChangeTracking(); + + pGamepadMapping = SDL_PrivateGetGamepadMappingForGUID(jGUID, true); + if (pGamepadMapping) { + // Only overwrite the mapping if the priority is the same or higher. + if (pGamepadMapping->priority <= priority) { + // Update existing mapping + SDL_free(pGamepadMapping->name); + pGamepadMapping->name = pchName; + SDL_free(pGamepadMapping->mapping); + pGamepadMapping->mapping = pchMapping; + pGamepadMapping->priority = priority; + } else { + SDL_free(pchName); + SDL_free(pchMapping); + } + if (existing) { + *existing = true; + } + AddMappingChangeTracking(pGamepadMapping); + } else { + pGamepadMapping = (GamepadMapping_t *)SDL_malloc(sizeof(*pGamepadMapping)); + if (!pGamepadMapping) { + PopMappingChangeTracking(); + SDL_free(pchName); + SDL_free(pchMapping); + return NULL; + } + // Clear the CRC, we've already added it to the mapping + if (crc) { + SDL_SetJoystickGUIDCRC(&jGUID, 0); + } + pGamepadMapping->guid = jGUID; + pGamepadMapping->name = pchName; + pGamepadMapping->mapping = pchMapping; + pGamepadMapping->next = NULL; + pGamepadMapping->priority = priority; + + if (s_pSupportedGamepads) { + // Add the mapping to the end of the list + GamepadMapping_t *pCurrMapping, *pPrevMapping; + + for (pPrevMapping = s_pSupportedGamepads, pCurrMapping = pPrevMapping->next; + pCurrMapping; + pPrevMapping = pCurrMapping, pCurrMapping = pCurrMapping->next) { + // continue; + } + pPrevMapping->next = pGamepadMapping; + } else { + s_pSupportedGamepads = pGamepadMapping; + } + if (existing) { + *existing = false; + } + } + + PopMappingChangeTracking(); + + return pGamepadMapping; +} + +/* + * Helper function to determine pre-calculated offset to certain joystick mappings + */ +static GamepadMapping_t *SDL_PrivateGetGamepadMappingForNameAndGUID(const char *name, SDL_GUID guid) +{ + GamepadMapping_t *mapping; + + SDL_AssertJoysticksLocked(); + + mapping = SDL_PrivateGetGamepadMappingForGUID(guid, false); +#ifdef SDL_PLATFORM_LINUX + if (!mapping && name) { + if (SDL_strstr(name, "Xbox 360 Wireless Receiver")) { + // The Linux driver xpad.c maps the wireless dpad to buttons + bool existing; + mapping = SDL_PrivateAddMappingForGUID(guid, + "none,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT); + } + } +#endif // SDL_PLATFORM_LINUX + + return mapping; +} + +static void SDL_PrivateAppendToMappingString(char *mapping_string, + size_t mapping_string_len, + const char *input_name, + SDL_InputMapping *mapping) +{ + char buffer[16]; + if (mapping->kind == EMappingKind_None) { + return; + } + + SDL_strlcat(mapping_string, input_name, mapping_string_len); + SDL_strlcat(mapping_string, ":", mapping_string_len); + switch (mapping->kind) { + case EMappingKind_Button: + (void)SDL_snprintf(buffer, sizeof(buffer), "b%u", mapping->target); + break; + case EMappingKind_Axis: + (void)SDL_snprintf(buffer, sizeof(buffer), "%sa%u%s", + mapping->half_axis_positive ? "+" : + mapping->half_axis_negative ? "-" : "", + mapping->target, + mapping->axis_reversed ? "~" : ""); + break; + case EMappingKind_Hat: + (void)SDL_snprintf(buffer, sizeof(buffer), "h%i.%i", mapping->target >> 4, mapping->target & 0x0F); + break; + default: + SDL_assert(false); + } + + SDL_strlcat(mapping_string, buffer, mapping_string_len); + SDL_strlcat(mapping_string, ",", mapping_string_len); +} + +static GamepadMapping_t *SDL_PrivateGenerateAutomaticGamepadMapping(const char *name, + SDL_GUID guid, + SDL_GamepadMapping *raw_map) +{ + bool existing; + char name_string[128]; + char mapping[1024]; + + // Remove any commas in the name + SDL_strlcpy(name_string, name, sizeof(name_string)); + { + char *spot; + for (spot = name_string; *spot; ++spot) { + if (*spot == ',') { + *spot = ' '; + } + } + } + (void)SDL_snprintf(mapping, sizeof(mapping), "none,%s,", name_string); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "a", &raw_map->a); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "b", &raw_map->b); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "x", &raw_map->x); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "y", &raw_map->y); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "back", &raw_map->back); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "guide", &raw_map->guide); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "start", &raw_map->start); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftstick", &raw_map->leftstick); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightstick", &raw_map->rightstick); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftshoulder", &raw_map->leftshoulder); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightshoulder", &raw_map->rightshoulder); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpup", &raw_map->dpup); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpdown", &raw_map->dpdown); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpleft", &raw_map->dpleft); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpright", &raw_map->dpright); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc1", &raw_map->misc1); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc2", &raw_map->misc2); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc3", &raw_map->misc3); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc4", &raw_map->misc4); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc5", &raw_map->misc5); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc6", &raw_map->misc6); + /* Keep using paddle1-4 in the generated mapping so that it can be + * reused with SDL2 */ + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle1", &raw_map->right_paddle1); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle2", &raw_map->left_paddle1); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle3", &raw_map->right_paddle2); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle4", &raw_map->left_paddle2); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftx", &raw_map->leftx); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "lefty", &raw_map->lefty); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightx", &raw_map->rightx); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "righty", &raw_map->righty); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "lefttrigger", &raw_map->lefttrigger); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "righttrigger", &raw_map->righttrigger); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "touchpad", &raw_map->touchpad); + + return SDL_PrivateAddMappingForGUID(guid, mapping, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT); +} + +static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id, bool create_mapping) +{ + const char *name; + SDL_GUID guid; + GamepadMapping_t *mapping; + + SDL_AssertJoysticksLocked(); + + name = SDL_GetJoystickNameForID(instance_id); + guid = SDL_GetJoystickGUIDForID(instance_id); + mapping = SDL_PrivateGetGamepadMappingForNameAndGUID(name, guid); + if (!mapping && create_mapping) { + SDL_GamepadMapping raw_map; + + SDL_zero(raw_map); + if (SDL_PrivateJoystickGetAutoGamepadMapping(instance_id, &raw_map)) { + mapping = SDL_PrivateGenerateAutomaticGamepadMapping(name, guid, &raw_map); + } + } + + if (!mapping) { + mapping = s_pDefaultMapping; + } + return mapping; +} + +/* + * Add or update an entry into the Mappings Database + */ +int SDL_AddGamepadMappingsFromIO(SDL_IOStream *src, bool closeio) +{ + const char *platform = SDL_GetPlatform(); + int gamepads = 0; + char *buf, *line, *line_end, *tmp, *comma, line_platform[64]; + size_t db_size; + size_t platform_len; + + buf = (char *)SDL_LoadFile_IO(src, &db_size, closeio); + if (!buf) { + SDL_SetError("Could not allocate space to read DB into memory"); + return -1; + } + line = buf; + + SDL_LockJoysticks(); + + PushMappingChangeTracking(); + + while (line < buf + db_size) { + line_end = SDL_strchr(line, '\n'); + if (line_end) { + *line_end = '\0'; + } else { + line_end = buf + db_size; + } + + // Extract and verify the platform + tmp = SDL_strstr(line, SDL_GAMEPAD_PLATFORM_FIELD); + if (tmp) { + tmp += SDL_GAMEPAD_PLATFORM_FIELD_SIZE; + comma = SDL_strchr(tmp, ','); + if (comma) { + platform_len = comma - tmp + 1; + if (platform_len + 1 < SDL_arraysize(line_platform)) { + SDL_strlcpy(line_platform, tmp, platform_len); + if (SDL_strncasecmp(line_platform, platform, platform_len) == 0 && + SDL_AddGamepadMapping(line) > 0) { + gamepads++; + } + } + } + } + + line = line_end + 1; + } + + PopMappingChangeTracking(); + + SDL_UnlockJoysticks(); + + SDL_free(buf); + return gamepads; +} + +int SDL_AddGamepadMappingsFromFile(const char *file) +{ + return SDL_AddGamepadMappingsFromIO(SDL_IOFromFile(file, "rb"), true); +} + +bool SDL_ReloadGamepadMappings(void) +{ + SDL_Gamepad *gamepad; + + SDL_LockJoysticks(); + + PushMappingChangeTracking(); + + for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) { + AddMappingChangeTracking(gamepad->mapping); + } + + SDL_QuitGamepadMappings(); + SDL_InitGamepadMappings(); + + PopMappingChangeTracking(); + + SDL_UnlockJoysticks(); + + return true; +} + +static char *SDL_ConvertMappingToPositional(const char *mapping) +{ + // Add space for '!' and null terminator + size_t length = SDL_strlen(mapping) + 1 + 1; + char *remapped = (char *)SDL_malloc(length); + if (remapped) { + char *button_A; + char *button_B; + char *button_X; + char *button_Y; + char *hint; + + SDL_strlcpy(remapped, mapping, length); + button_A = SDL_strstr(remapped, "a:"); + button_B = SDL_strstr(remapped, "b:"); + button_X = SDL_strstr(remapped, "x:"); + button_Y = SDL_strstr(remapped, "y:"); + hint = SDL_strstr(remapped, "hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS"); + + if (button_A) { + *button_A = 'b'; + } + if (button_B) { + *button_B = 'a'; + } + if (button_X) { + *button_X = 'y'; + } + if (button_Y) { + *button_Y = 'x'; + } + if (hint) { + hint += 5; + SDL_memmove(hint + 1, hint, SDL_strlen(hint) + 1); + *hint = '!'; + } + } + return remapped; +} + +/* + * Add or update an entry into the Mappings Database with a priority + */ +static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMappingPriority priority) +{ + char *remapped = NULL; + char *pchGUID; + SDL_GUID jGUID; + bool is_default_mapping = false; + bool is_xinput_mapping = false; + bool existing = false; + GamepadMapping_t *pGamepadMapping; + int result = -1; + + SDL_AssertJoysticksLocked(); + + if (!mappingString) { + SDL_InvalidParamError("mappingString"); + return -1; + } + + { // Extract and verify the hint field + const char *tmp; + + tmp = SDL_strstr(mappingString, SDL_GAMEPAD_HINT_FIELD); + if (tmp) { + bool default_value, value, negate; + int len; + char hint[128]; + + tmp += SDL_GAMEPAD_HINT_FIELD_SIZE; + + if (*tmp == '!') { + negate = true; + ++tmp; + } else { + negate = false; + } + + len = 0; + while (*tmp && *tmp != ',' && *tmp != ':' && len < (sizeof(hint) - 1)) { + hint[len++] = *tmp++; + } + hint[len] = '\0'; + + if (tmp[0] == ':' && tmp[1] == '=') { + tmp += 2; + default_value = SDL_atoi(tmp); + } else { + default_value = false; + } + + if (SDL_strcmp(hint, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") == 0) { + // This hint is used to signal whether the mapping uses positional buttons or not + if (negate) { + // This mapping uses positional buttons, we can use it as-is + } else { + // This mapping uses labeled buttons, we need to swap them to positional + remapped = SDL_ConvertMappingToPositional(mappingString); + if (!remapped) { + goto done; + } + mappingString = remapped; + } + } else { + value = SDL_GetHintBoolean(hint, default_value); + if (negate) { + value = !value; + } + if (!value) { + result = 0; + goto done; + } + } + } + } + +#ifdef ANDROID + { // Extract and verify the SDK version + const char *tmp; + + tmp = SDL_strstr(mappingString, SDL_GAMEPAD_SDKGE_FIELD); + if (tmp) { + tmp += SDL_GAMEPAD_SDKGE_FIELD_SIZE; + if (!(SDL_GetAndroidSDKVersion() >= SDL_atoi(tmp))) { + SDL_SetError("SDK version %d < minimum version %d", SDL_GetAndroidSDKVersion(), SDL_atoi(tmp)); + goto done; + } + } + tmp = SDL_strstr(mappingString, SDL_GAMEPAD_SDKLE_FIELD); + if (tmp) { + tmp += SDL_GAMEPAD_SDKLE_FIELD_SIZE; + if (!(SDL_GetAndroidSDKVersion() <= SDL_atoi(tmp))) { + SDL_SetError("SDK version %d > maximum version %d", SDL_GetAndroidSDKVersion(), SDL_atoi(tmp)); + goto done; + } + } + } +#endif + + pchGUID = SDL_PrivateGetGamepadGUIDFromMappingString(mappingString); + if (!pchGUID) { + SDL_SetError("Couldn't parse GUID from %s", mappingString); + goto done; + } + if (!SDL_strcasecmp(pchGUID, "default")) { + is_default_mapping = true; + } else if (!SDL_strcasecmp(pchGUID, "xinput")) { + is_xinput_mapping = true; + } + jGUID = SDL_StringToGUID(pchGUID); + SDL_free(pchGUID); + + pGamepadMapping = SDL_PrivateAddMappingForGUID(jGUID, mappingString, &existing, priority); + if (!pGamepadMapping) { + goto done; + } + + if (existing) { + result = 0; + } else { + if (is_default_mapping) { + s_pDefaultMapping = pGamepadMapping; + } else if (is_xinput_mapping) { + s_pXInputMapping = pGamepadMapping; + } + result = 1; + } +done: + if (remapped) { + SDL_free(remapped); + } + return result; +} + +/* + * Add or update an entry into the Mappings Database + */ +int SDL_AddGamepadMapping(const char *mapping) +{ + int result; + + SDL_LockJoysticks(); + { + result = SDL_PrivateAddGamepadMapping(mapping, SDL_GAMEPAD_MAPPING_PRIORITY_API); + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Create a mapping string for a mapping + */ +static char *CreateMappingString(GamepadMapping_t *mapping, SDL_GUID guid) +{ + char *pMappingString, *pPlatformString; + char pchGUID[33]; + size_t needed; + bool need_platform = false; + const char *platform = NULL; + + SDL_AssertJoysticksLocked(); + + SDL_GUIDToString(guid, pchGUID, sizeof(pchGUID)); + + // allocate enough memory for GUID + ',' + name + ',' + mapping + \0 + needed = SDL_strlen(pchGUID) + 1 + SDL_strlen(mapping->name) + 1 + SDL_strlen(mapping->mapping) + 1; + + if (!SDL_strstr(mapping->mapping, SDL_GAMEPAD_PLATFORM_FIELD)) { + // add memory for ',' + platform:PLATFORM + need_platform = true; + if (mapping->mapping[SDL_strlen(mapping->mapping) - 1] != ',') { + needed += 1; + } + platform = SDL_GetPlatform(); + needed += SDL_GAMEPAD_PLATFORM_FIELD_SIZE + SDL_strlen(platform) + 1; + } + + pMappingString = (char *)SDL_malloc(needed); + if (!pMappingString) { + return NULL; + } + + (void)SDL_snprintf(pMappingString, needed, "%s,%s,%s", pchGUID, mapping->name, mapping->mapping); + + if (need_platform) { + if (mapping->mapping[SDL_strlen(mapping->mapping) - 1] != ',') { + SDL_strlcat(pMappingString, ",", needed); + } + SDL_strlcat(pMappingString, SDL_GAMEPAD_PLATFORM_FIELD, needed); + SDL_strlcat(pMappingString, platform, needed); + SDL_strlcat(pMappingString, ",", needed); + } + + // Make sure multiple platform strings haven't made their way into the mapping + pPlatformString = SDL_strstr(pMappingString, SDL_GAMEPAD_PLATFORM_FIELD); + if (pPlatformString) { + pPlatformString = SDL_strstr(pPlatformString + 1, SDL_GAMEPAD_PLATFORM_FIELD); + if (pPlatformString) { + *pPlatformString = '\0'; + } + } + return pMappingString; +} + +char **SDL_GetGamepadMappings(int *count) +{ + int num_mappings = 0; + char **result = NULL; + char **mappings = NULL; + + if (count) { + *count = 0; + } + + SDL_LockJoysticks(); + + for (GamepadMapping_t *mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) { + if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) { + continue; + } + num_mappings++; + } + + size_t final_allocation = sizeof (char *); // for the NULL terminator element. + bool failed = false; + mappings = (char **) SDL_calloc(num_mappings + 1, sizeof (char *)); + if (!mappings) { + failed = true; + } else { + int i = 0; + for (GamepadMapping_t *mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) { + if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) { + continue; + } + + char *mappingstr = CreateMappingString(mapping, mapping->guid); + if (!mappingstr) { + failed = true; + break; // error string is already set. + } + + SDL_assert(i < num_mappings); + mappings[i++] = mappingstr; + + final_allocation += SDL_strlen(mappingstr) + 1 + sizeof (char *); + } + } + + SDL_UnlockJoysticks(); + + if (!failed) { + result = (char **) SDL_malloc(final_allocation); + if (result) { + final_allocation -= (sizeof (char *) * num_mappings + 1); + char *strptr = (char *) (result + (num_mappings + 1)); + for (int i = 0; i < num_mappings; i++) { + result[i] = strptr; + const size_t slen = SDL_strlcpy(strptr, mappings[i], final_allocation) + 1; + SDL_assert(final_allocation >= slen); + final_allocation -= slen; + strptr += slen; + } + result[num_mappings] = NULL; + + if (count) { + *count = num_mappings; + } + } + } + + if (mappings) { + for (int i = 0; i < num_mappings; i++) { + SDL_free(mappings[i]); + } + SDL_free(mappings); + } + + return result; +} + +/* + * Get the mapping string for this GUID + */ +char *SDL_GetGamepadMappingForGUID(SDL_GUID guid) +{ + char *result; + + SDL_LockJoysticks(); + { + GamepadMapping_t *mapping = SDL_PrivateGetGamepadMappingForGUID(guid, false); + if (mapping) { + result = CreateMappingString(mapping, guid); + } else { + SDL_SetError("Mapping not available"); + result = NULL; + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the mapping string for this device + */ +char *SDL_GetGamepadMapping(SDL_Gamepad *gamepad) +{ + char *result; + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, NULL); + + result = CreateMappingString(gamepad->mapping, gamepad->joystick->guid); + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Set the mapping string for this device + */ +bool SDL_SetGamepadMapping(SDL_JoystickID instance_id, const char *mapping) +{ + SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id); + bool result = false; + + if (SDL_memcmp(&guid, &s_zeroGUID, sizeof(guid)) == 0) { + return SDL_InvalidParamError("instance_id"); + } + + if (!mapping) { + mapping = "*,*,"; + } + + SDL_LockJoysticks(); + { + if (SDL_PrivateAddMappingForGUID(guid, mapping, NULL, SDL_GAMEPAD_MAPPING_PRIORITY_API)) { + result = true; + } + } + SDL_UnlockJoysticks(); + + return result; +} + +static void SDL_LoadGamepadHints(void) +{ + const char *hint = SDL_GetHint(SDL_HINT_GAMECONTROLLERCONFIG); + if (hint && hint[0]) { + char *pTempMappings = SDL_strdup(hint); + char *pUserMappings = pTempMappings; + + PushMappingChangeTracking(); + + while (pUserMappings) { + char *pchNewLine = NULL; + + pchNewLine = SDL_strchr(pUserMappings, '\n'); + if (pchNewLine) { + *pchNewLine = '\0'; + } + + SDL_PrivateAddGamepadMapping(pUserMappings, SDL_GAMEPAD_MAPPING_PRIORITY_USER); + + if (pchNewLine) { + pUserMappings = pchNewLine + 1; + } else { + pUserMappings = NULL; + } + } + + PopMappingChangeTracking(); + + SDL_free(pTempMappings); + } +} + +/* + * Fill the given buffer with the expected gamepad mapping filepath. + * Usually this will just be SDL_HINT_GAMECONTROLLERCONFIG_FILE, but for + * Android, we want to get the internal storage path. + */ +static bool SDL_GetGamepadMappingFilePath(char *path, size_t size) +{ + const char *hint = SDL_GetHint(SDL_HINT_GAMECONTROLLERCONFIG_FILE); + if (hint && *hint) { + return SDL_strlcpy(path, hint, size) < size; + } + +#ifdef SDL_PLATFORM_ANDROID + return SDL_snprintf(path, size, "%s/gamepad_map.txt", SDL_GetAndroidInternalStoragePath()) < size; +#else + return false; +#endif +} + +/* + * Initialize the gamepad system, mostly load our DB of gamepad config mappings + */ +bool SDL_InitGamepadMappings(void) +{ + char szGamepadMapPath[1024]; + int i = 0; + const char *pMappingString = NULL; + + SDL_AssertJoysticksLocked(); + + PushMappingChangeTracking(); + + pMappingString = s_GamepadMappings[i]; + while (pMappingString) { + SDL_PrivateAddGamepadMapping(pMappingString, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT); + + i++; + pMappingString = s_GamepadMappings[i]; + } + + if (SDL_GetGamepadMappingFilePath(szGamepadMapPath, sizeof(szGamepadMapPath))) { + SDL_AddGamepadMappingsFromFile(szGamepadMapPath); + } + + // load in any user supplied config + SDL_LoadGamepadHints(); + + SDL_LoadVIDPIDList(&SDL_allowed_gamepads); + SDL_LoadVIDPIDList(&SDL_ignored_gamepads); + + PopMappingChangeTracking(); + + return true; +} + +bool SDL_InitGamepads(void) +{ + int i; + SDL_JoystickID *joysticks; + + SDL_gamepads_initialized = true; + + // Watch for joystick events and fire gamepad ones if needed + SDL_AddEventWatch(SDL_GamepadEventWatcher, NULL); + + // Send added events for gamepads currently attached + joysticks = SDL_GetJoysticks(NULL); + if (joysticks) { + for (i = 0; joysticks[i]; ++i) { + if (SDL_IsGamepad(joysticks[i])) { + SDL_PrivateGamepadAdded(joysticks[i]); + } + } + SDL_free(joysticks); + } + + return true; +} + +bool SDL_HasGamepad(void) +{ + int num_joysticks = 0; + int num_gamepads = 0; + SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks); + if (joysticks) { + int i; + for (i = num_joysticks - 1; i >= 0 && num_gamepads == 0; --i) { + if (SDL_IsGamepad(joysticks[i])) { + ++num_gamepads; + } + } + SDL_free(joysticks); + } + if (num_gamepads > 0) { + return true; + } + return false; +} + +SDL_JoystickID *SDL_GetGamepads(int *count) +{ + int num_joysticks = 0; + int num_gamepads = 0; + SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks); + if (joysticks) { + int i; + for (i = num_joysticks - 1; i >= 0; --i) { + if (SDL_IsGamepad(joysticks[i])) { + ++num_gamepads; + } else { + SDL_memmove(&joysticks[i], &joysticks[i+1], (num_gamepads + 1) * sizeof(joysticks[i])); + } + } + } + if (count) { + *count = num_gamepads; + } + return joysticks; +} + +const char *SDL_GetGamepadNameForID(SDL_JoystickID instance_id) +{ + const char *result = NULL; + + SDL_LockJoysticks(); + { + GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true); + if (mapping) { + if (SDL_strcmp(mapping->name, "*") == 0) { + result = SDL_GetJoystickNameForID(instance_id); + } else { + result = SDL_GetPersistentString(mapping->name); + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +const char *SDL_GetGamepadPathForID(SDL_JoystickID instance_id) +{ + return SDL_GetJoystickPathForID(instance_id); +} + +int SDL_GetGamepadPlayerIndexForID(SDL_JoystickID instance_id) +{ + return SDL_GetJoystickPlayerIndexForID(instance_id); +} + +SDL_GUID SDL_GetGamepadGUIDForID(SDL_JoystickID instance_id) +{ + return SDL_GetJoystickGUIDForID(instance_id); +} + +Uint16 SDL_GetGamepadVendorForID(SDL_JoystickID instance_id) +{ + return SDL_GetJoystickVendorForID(instance_id); +} + +Uint16 SDL_GetGamepadProductForID(SDL_JoystickID instance_id) +{ + return SDL_GetJoystickProductForID(instance_id); +} + +Uint16 SDL_GetGamepadProductVersionForID(SDL_JoystickID instance_id) +{ + return SDL_GetJoystickProductVersionForID(instance_id); +} + +SDL_GamepadType SDL_GetGamepadTypeForID(SDL_JoystickID instance_id) +{ + SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN; + + SDL_LockJoysticks(); + { + GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true); + if (mapping) { + char *type_string, *comma; + + type_string = SDL_strstr(mapping->mapping, SDL_GAMEPAD_TYPE_FIELD); + if (type_string) { + type_string += SDL_GAMEPAD_TYPE_FIELD_SIZE; + comma = SDL_strchr(type_string, ','); + if (comma) { + *comma = '\0'; + type = SDL_GetGamepadTypeFromString(type_string); + *comma = ','; + } + } + } + } + SDL_UnlockJoysticks(); + + if (type != SDL_GAMEPAD_TYPE_UNKNOWN) { + return type; + } + return SDL_GetRealGamepadTypeForID(instance_id); +} + +SDL_GamepadType SDL_GetRealGamepadTypeForID(SDL_JoystickID instance_id) +{ + SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN; + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + { + info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id); + if (info) { + type = info->type; + } else { + type = SDL_GetGamepadTypeFromGUID(SDL_GetJoystickGUIDForID(instance_id), SDL_GetJoystickNameForID(instance_id)); + } + } + SDL_UnlockJoysticks(); + + return type; +} + +char *SDL_GetGamepadMappingForID(SDL_JoystickID instance_id) +{ + char *result = NULL; + + SDL_LockJoysticks(); + { + GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true); + if (mapping) { + char pchGUID[33]; + SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id); + SDL_GUIDToString(guid, pchGUID, sizeof(pchGUID)); + SDL_asprintf(&result, "%s,%s,%s", pchGUID, mapping->name, mapping->mapping); + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Return 1 if the joystick with this name and GUID is a supported gamepad + */ +bool SDL_IsGamepadNameAndGUID(const char *name, SDL_GUID guid) +{ + bool result; + + SDL_LockJoysticks(); + { + if (s_pDefaultMapping || SDL_PrivateGetGamepadMappingForNameAndGUID(name, guid) != NULL) { + result = true; + } else { + result = false; + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Return 1 if the joystick at this device index is a supported gamepad + */ +bool SDL_IsGamepad(SDL_JoystickID instance_id) +{ + bool result; + + SDL_LockJoysticks(); + { + const void *value; + if (SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) { + result = (bool)(uintptr_t)value; + } else { + if (SDL_PrivateGetGamepadMapping(instance_id, true) != NULL) { + result = true; + } else { + result = false; + } + + if (!s_gamepadInstanceIDs) { + s_gamepadInstanceIDs = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL); + } + SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, (void *)(uintptr_t)result, true); + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Return 1 if the gamepad should be ignored by SDL + */ +bool SDL_ShouldIgnoreGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ +#ifdef SDL_PLATFORM_LINUX + if (SDL_endswith(name, " Motion Sensors")) { + // Don't treat the PS3 and PS4 motion controls as a separate gamepad + return true; + } + if (SDL_strncmp(name, "Nintendo ", 9) == 0 && SDL_strstr(name, " IMU") != NULL) { + // Don't treat the Nintendo IMU as a separate gamepad + return true; + } + if (SDL_endswith(name, " Accelerometer") || + SDL_endswith(name, " IR") || + SDL_endswith(name, " Motion Plus") || + SDL_endswith(name, " Nunchuk")) { + // Don't treat the Wii extension controls as a separate gamepad + return true; + } +#endif + + if (name && SDL_strcmp(name, "uinput-fpc") == 0) { + // The Google Pixel fingerprint sensor reports itself as a joystick + return true; + } + +#ifdef SDL_PLATFORM_WIN32 + if (SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", false) && + SDL_GetHintBoolean("STEAM_COMPAT_PROTON", false)) { + // We are launched by Steam and running under Proton + // We can't tell whether this controller is a Steam Virtual Gamepad, + // so assume that Proton is doing the appropriate filtering of controllers + // and anything we see here is fine to use. + return false; + } +#endif // SDL_PLATFORM_WIN32 + + if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) { + return !SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", false); + } + + if (SDL_allowed_gamepads.num_included_entries > 0) { + if (SDL_VIDPIDInList(vendor_id, product_id, &SDL_allowed_gamepads)) { + return false; + } + return true; + } else { + if (SDL_VIDPIDInList(vendor_id, product_id, &SDL_ignored_gamepads)) { + return true; + } + return false; + } +} + +/* + * Open a gamepad for use + * + * This function returns a gamepad identifier, or NULL if an error occurred. + */ +SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) +{ + SDL_Gamepad *gamepad; + SDL_Gamepad *gamepadlist; + GamepadMapping_t *pSupportedGamepad = NULL; + + SDL_LockJoysticks(); + + gamepadlist = SDL_gamepads; + // If the gamepad is already open, return it + while (gamepadlist) { + if (instance_id == gamepadlist->joystick->instance_id) { + gamepad = gamepadlist; + ++gamepad->ref_count; + SDL_UnlockJoysticks(); + return gamepad; + } + gamepadlist = gamepadlist->next; + } + + // Find a gamepad mapping + pSupportedGamepad = SDL_PrivateGetGamepadMapping(instance_id, true); + if (!pSupportedGamepad) { + SDL_SetError("Couldn't find mapping for device (%" SDL_PRIu32 ")", instance_id); + SDL_UnlockJoysticks(); + return NULL; + } + + // Create and initialize the gamepad + gamepad = (SDL_Gamepad *)SDL_calloc(1, sizeof(*gamepad)); + if (!gamepad) { + SDL_UnlockJoysticks(); + return NULL; + } + SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, true); + + gamepad->joystick = SDL_OpenJoystick(instance_id); + if (!gamepad->joystick) { + SDL_free(gamepad); + SDL_UnlockJoysticks(); + return NULL; + } + + if (gamepad->joystick->naxes) { + gamepad->last_match_axis = (SDL_GamepadBinding **)SDL_calloc(gamepad->joystick->naxes, sizeof(*gamepad->last_match_axis)); + if (!gamepad->last_match_axis) { + SDL_CloseJoystick(gamepad->joystick); + SDL_free(gamepad); + SDL_UnlockJoysticks(); + return NULL; + } + } + if (gamepad->joystick->nhats) { + gamepad->last_hat_mask = (Uint8 *)SDL_calloc(gamepad->joystick->nhats, sizeof(*gamepad->last_hat_mask)); + if (!gamepad->last_hat_mask) { + SDL_CloseJoystick(gamepad->joystick); + SDL_free(gamepad->last_match_axis); + SDL_free(gamepad); + SDL_UnlockJoysticks(); + return NULL; + } + } + + SDL_PrivateLoadButtonMapping(gamepad, pSupportedGamepad); + + // Add the gamepad to list + ++gamepad->ref_count; + // Link the gamepad in the list + gamepad->next = SDL_gamepads; + SDL_gamepads = gamepad; + + SDL_UnlockJoysticks(); + + return gamepad; +} + +/* + * Manually pump for gamepad updates. + */ +void SDL_UpdateGamepads(void) +{ + // Just for API completeness; the joystick API does all the work. + SDL_UpdateJoysticks(); +} + +/** + * Return whether a gamepad has a given axis + */ +bool SDL_GamepadHasAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis) +{ + bool result = false; + + SDL_LockJoysticks(); + { + int i; + + CHECK_GAMEPAD_MAGIC(gamepad, false); + + for (i = 0; i < gamepad->num_bindings; ++i) { + const SDL_GamepadBinding *binding = &gamepad->bindings[i]; + if (binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS && binding->output.axis.axis == axis) { + result = true; + break; + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the current state of an axis control on a gamepad + */ +Sint16 SDL_GetGamepadAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis) +{ + Sint16 result = 0; + + SDL_LockJoysticks(); + { + int i; + + CHECK_GAMEPAD_MAGIC(gamepad, 0); + + for (i = 0; i < gamepad->num_bindings; ++i) { + const SDL_GamepadBinding *binding = &gamepad->bindings[i]; + if (binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS && binding->output.axis.axis == axis) { + int value = 0; + bool valid_input_range; + bool valid_output_range; + + if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS) { + value = SDL_GetJoystickAxis(gamepad->joystick, binding->input.axis.axis); + if (binding->input.axis.axis_min < binding->input.axis.axis_max) { + valid_input_range = (value >= binding->input.axis.axis_min && value <= binding->input.axis.axis_max); + } else { + valid_input_range = (value >= binding->input.axis.axis_max && value <= binding->input.axis.axis_min); + } + if (valid_input_range) { + if (binding->input.axis.axis_min != binding->output.axis.axis_min || binding->input.axis.axis_max != binding->output.axis.axis_max) { + float normalized_value = (float)(value - binding->input.axis.axis_min) / (binding->input.axis.axis_max - binding->input.axis.axis_min); + value = binding->output.axis.axis_min + (int)(normalized_value * (binding->output.axis.axis_max - binding->output.axis.axis_min)); + } + } else { + value = 0; + } + } else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON) { + if (SDL_GetJoystickButton(gamepad->joystick, binding->input.button)) { + value = binding->output.axis.axis_max; + } + } else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_HAT) { + int hat_mask = SDL_GetJoystickHat(gamepad->joystick, binding->input.hat.hat); + if (hat_mask & binding->input.hat.hat_mask) { + value = binding->output.axis.axis_max; + } + } + + if (binding->output.axis.axis_min < binding->output.axis.axis_max) { + valid_output_range = (value >= binding->output.axis.axis_min && value <= binding->output.axis.axis_max); + } else { + valid_output_range = (value >= binding->output.axis.axis_max && value <= binding->output.axis.axis_min); + } + // If the value is zero, there might be another binding that makes it non-zero + if (value != 0 && valid_output_range) { + result = (Sint16)value; + break; + } + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/** + * Return whether a gamepad has a given button + */ +bool SDL_GamepadHasButton(SDL_Gamepad *gamepad, SDL_GamepadButton button) +{ + bool result = false; + + SDL_LockJoysticks(); + { + int i; + + CHECK_GAMEPAD_MAGIC(gamepad, false); + + for (i = 0; i < gamepad->num_bindings; ++i) { + const SDL_GamepadBinding *binding = &gamepad->bindings[i]; + if (binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON && binding->output.button == button) { + result = true; + break; + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the current state of a button on a gamepad + */ +bool SDL_GetGamepadButton(SDL_Gamepad *gamepad, SDL_GamepadButton button) +{ + bool result = false; + + SDL_LockJoysticks(); + { + int i; + + CHECK_GAMEPAD_MAGIC(gamepad, false); + + for (i = 0; i < gamepad->num_bindings; ++i) { + const SDL_GamepadBinding *binding = &gamepad->bindings[i]; + if (binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON && binding->output.button == button) { + if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS) { + bool valid_input_range; + + int value = SDL_GetJoystickAxis(gamepad->joystick, binding->input.axis.axis); + int threshold = binding->input.axis.axis_min + (binding->input.axis.axis_max - binding->input.axis.axis_min) / 2; + if (binding->input.axis.axis_min < binding->input.axis.axis_max) { + valid_input_range = (value >= binding->input.axis.axis_min && value <= binding->input.axis.axis_max); + if (valid_input_range) { + result |= (value >= threshold); + } + } else { + valid_input_range = (value >= binding->input.axis.axis_max && value <= binding->input.axis.axis_min); + if (valid_input_range) { + result |= (value <= threshold); + } + } + } else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON) { + result |= SDL_GetJoystickButton(gamepad->joystick, binding->input.button); + } else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_HAT) { + int hat_mask = SDL_GetJoystickHat(gamepad->joystick, binding->input.hat.hat); + result |= ((hat_mask & binding->input.hat.hat_mask) != 0); + } + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/** + * Get the label of a button on a gamepad. + */ +static SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForFaceStyle(SDL_GamepadFaceStyle face_style, SDL_GamepadButton button) +{ + SDL_GamepadButtonLabel label = SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN; + + switch (face_style) { + case SDL_GAMEPAD_FACE_STYLE_ABXY: + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + label = SDL_GAMEPAD_BUTTON_LABEL_A; + break; + case SDL_GAMEPAD_BUTTON_EAST: + label = SDL_GAMEPAD_BUTTON_LABEL_B; + break; + case SDL_GAMEPAD_BUTTON_WEST: + label = SDL_GAMEPAD_BUTTON_LABEL_X; + break; + case SDL_GAMEPAD_BUTTON_NORTH: + label = SDL_GAMEPAD_BUTTON_LABEL_Y; + break; + default: + break; + } + break; + case SDL_GAMEPAD_FACE_STYLE_BAYX: + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + label = SDL_GAMEPAD_BUTTON_LABEL_B; + break; + case SDL_GAMEPAD_BUTTON_EAST: + label = SDL_GAMEPAD_BUTTON_LABEL_A; + break; + case SDL_GAMEPAD_BUTTON_WEST: + label = SDL_GAMEPAD_BUTTON_LABEL_Y; + break; + case SDL_GAMEPAD_BUTTON_NORTH: + label = SDL_GAMEPAD_BUTTON_LABEL_X; + break; + default: + break; + } + break; + case SDL_GAMEPAD_FACE_STYLE_SONY: + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + label = SDL_GAMEPAD_BUTTON_LABEL_CROSS; + break; + case SDL_GAMEPAD_BUTTON_EAST: + label = SDL_GAMEPAD_BUTTON_LABEL_CIRCLE; + break; + case SDL_GAMEPAD_BUTTON_WEST: + label = SDL_GAMEPAD_BUTTON_LABEL_SQUARE; + break; + case SDL_GAMEPAD_BUTTON_NORTH: + label = SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE; + break; + default: + break; + } + break; + default: + break; + } + return label; +} + +/** + * Get the label of a button on a gamepad. + */ +SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForType(SDL_GamepadType type, SDL_GamepadButton button) +{ + return SDL_GetGamepadButtonLabelForFaceStyle(SDL_GetGamepadFaceStyleForGamepadType(type), button); +} + +/** + * Get the label of a button on a gamepad. + */ +SDL_GamepadButtonLabel SDL_GetGamepadButtonLabel(SDL_Gamepad *gamepad, SDL_GamepadButton button) +{ + SDL_GamepadFaceStyle face_style; + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN); + + face_style = gamepad->face_style; + } + SDL_UnlockJoysticks(); + + return SDL_GetGamepadButtonLabelForFaceStyle(face_style, button); +} + +/** + * Get the number of touchpads on a gamepad. + */ +int SDL_GetNumGamepadTouchpads(SDL_Gamepad *gamepad) +{ + int result = 0; + + SDL_LockJoysticks(); + { + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + if (joystick) { + result = joystick->ntouchpads; + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/** + * Get the number of supported simultaneous fingers on a touchpad on a gamepad. + */ +int SDL_GetNumGamepadTouchpadFingers(SDL_Gamepad *gamepad, int touchpad) +{ + int result = 0; + + SDL_LockJoysticks(); + { + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + if (joystick) { + if (touchpad >= 0 && touchpad < joystick->ntouchpads) { + result = joystick->touchpads[touchpad].nfingers; + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/** + * Get the current state of a finger on a touchpad on a gamepad. + */ +bool SDL_GetGamepadTouchpadFinger(SDL_Gamepad *gamepad, int touchpad, int finger, bool *down, float *x, float *y, float *pressure) +{ + bool result = false; + + SDL_LockJoysticks(); + { + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + if (joystick) { + if (touchpad >= 0 && touchpad < joystick->ntouchpads) { + SDL_JoystickTouchpadInfo *touchpad_info = &joystick->touchpads[touchpad]; + if (finger >= 0 && finger < touchpad_info->nfingers) { + SDL_JoystickTouchpadFingerInfo *info = &touchpad_info->fingers[finger]; + + if (down) { + *down = info->down; + } + if (x) { + *x = info->x; + } + if (y) { + *y = info->y; + } + if (pressure) { + *pressure = info->pressure; + } + result = true; + } else { + result = SDL_InvalidParamError("finger"); + } + } else { + result = SDL_InvalidParamError("touchpad"); + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/** + * Return whether a gamepad has a particular sensor. + */ +bool SDL_GamepadHasSensor(SDL_Gamepad *gamepad, SDL_SensorType type) +{ + bool result = false; + + SDL_LockJoysticks(); + { + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + if (joystick) { + int i; + for (i = 0; i < joystick->nsensors; ++i) { + if (joystick->sensors[i].type == type) { + result = true; + break; + } + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Set whether data reporting for a gamepad sensor is enabled + */ +bool SDL_SetGamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type, bool enabled) +{ + SDL_LockJoysticks(); + { + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + if (joystick) { + int i; + for (i = 0; i < joystick->nsensors; ++i) { + SDL_JoystickSensorInfo *sensor = &joystick->sensors[i]; + + if (sensor->type == type) { + if (sensor->enabled == (enabled != false)) { + SDL_UnlockJoysticks(); + return true; + } + + if (type == SDL_SENSOR_ACCEL && joystick->accel_sensor) { + if (enabled) { + joystick->accel = SDL_OpenSensor(joystick->accel_sensor); + if (!joystick->accel) { + SDL_UnlockJoysticks(); + return false; + } + } else { + if (joystick->accel) { + SDL_CloseSensor(joystick->accel); + joystick->accel = NULL; + } + } + } else if (type == SDL_SENSOR_GYRO && joystick->gyro_sensor) { + if (enabled) { + joystick->gyro = SDL_OpenSensor(joystick->gyro_sensor); + if (!joystick->gyro) { + SDL_UnlockJoysticks(); + return false; + } + } else { + if (joystick->gyro) { + SDL_CloseSensor(joystick->gyro); + joystick->gyro = NULL; + } + } + } else { + if (enabled) { + if (joystick->nsensors_enabled == 0) { + if (!joystick->driver->SetSensorsEnabled(joystick, true)) { + SDL_UnlockJoysticks(); + return false; + } + } + ++joystick->nsensors_enabled; + } else { + if (joystick->nsensors_enabled == 1) { + if (!joystick->driver->SetSensorsEnabled(joystick, false)) { + SDL_UnlockJoysticks(); + return false; + } + } + --joystick->nsensors_enabled; + } + } + + sensor->enabled = enabled; + SDL_UnlockJoysticks(); + return true; + } + } + } + } + SDL_UnlockJoysticks(); + + return SDL_Unsupported(); +} + +/* + * Query whether sensor data reporting is enabled for a gamepad + */ +bool SDL_GamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type) +{ + bool result = false; + + SDL_LockJoysticks(); + { + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + if (joystick) { + int i; + for (i = 0; i < joystick->nsensors; ++i) { + if (joystick->sensors[i].type == type) { + result = joystick->sensors[i].enabled; + break; + } + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the data rate of a gamepad sensor. + */ +float SDL_GetGamepadSensorDataRate(SDL_Gamepad *gamepad, SDL_SensorType type) +{ + float result = 0.0f; + + SDL_LockJoysticks(); + { + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + if (joystick) { + int i; + for (i = 0; i < joystick->nsensors; ++i) { + SDL_JoystickSensorInfo *sensor = &joystick->sensors[i]; + + if (sensor->type == type) { + result = sensor->rate; + break; + } + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the current state of a gamepad sensor. + */ +bool SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, SDL_SensorType type, float *data, int num_values) +{ + SDL_LockJoysticks(); + { + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + if (joystick) { + int i; + for (i = 0; i < joystick->nsensors; ++i) { + SDL_JoystickSensorInfo *sensor = &joystick->sensors[i]; + + if (sensor->type == type) { + num_values = SDL_min(num_values, SDL_arraysize(sensor->data)); + SDL_memcpy(data, sensor->data, num_values * sizeof(*data)); + SDL_UnlockJoysticks(); + return true; + } + } + } + } + SDL_UnlockJoysticks(); + + return SDL_Unsupported(); +} + +SDL_JoystickID SDL_GetGamepadID(SDL_Gamepad *gamepad) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return 0; + } + return SDL_GetJoystickID(joystick); +} + +SDL_PropertiesID SDL_GetGamepadProperties(SDL_Gamepad *gamepad) +{ + SDL_PropertiesID result = 0; + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, 0); + + result = SDL_GetJoystickProperties(gamepad->joystick); + } + SDL_UnlockJoysticks(); + + return result; +} + +const char *SDL_GetGamepadName(SDL_Gamepad *gamepad) +{ + const char *result = NULL; + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, NULL); + + if (SDL_strcmp(gamepad->name, "*") == 0 || + gamepad->joystick->steam_handle != 0) { + result = SDL_GetJoystickName(gamepad->joystick); + } else { + result = SDL_GetPersistentString(gamepad->name); + } + } + SDL_UnlockJoysticks(); + + return result; +} + +const char *SDL_GetGamepadPath(SDL_Gamepad *gamepad) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return NULL; + } + return SDL_GetJoystickPath(joystick); +} + +SDL_GamepadType SDL_GetGamepadType(SDL_Gamepad *gamepad) +{ + SDL_GamepadType type; + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, SDL_GAMEPAD_TYPE_UNKNOWN); + + info = SDL_GetJoystickVirtualGamepadInfoForID(gamepad->joystick->instance_id); + if (info) { + type = info->type; + } else { + type = gamepad->type; + } + } + SDL_UnlockJoysticks(); + + return type; +} + +SDL_GamepadType SDL_GetRealGamepadType(SDL_Gamepad *gamepad) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return SDL_GAMEPAD_TYPE_UNKNOWN; + } + return SDL_GetGamepadTypeFromGUID(SDL_GetJoystickGUID(joystick), SDL_GetJoystickName(joystick)); +} + +int SDL_GetGamepadPlayerIndex(SDL_Gamepad *gamepad) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return -1; + } + return SDL_GetJoystickPlayerIndex(joystick); +} + +/** + * Set the player index of an opened gamepad + */ +bool SDL_SetGamepadPlayerIndex(SDL_Gamepad *gamepad, int player_index) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + // SDL_SetError() will have been called already by SDL_GetGamepadJoystick() + return false; + } + return SDL_SetJoystickPlayerIndex(joystick, player_index); +} + +Uint16 SDL_GetGamepadVendor(SDL_Gamepad *gamepad) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return 0; + } + return SDL_GetJoystickVendor(joystick); +} + +Uint16 SDL_GetGamepadProduct(SDL_Gamepad *gamepad) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return 0; + } + return SDL_GetJoystickProduct(joystick); +} + +Uint16 SDL_GetGamepadProductVersion(SDL_Gamepad *gamepad) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return 0; + } + return SDL_GetJoystickProductVersion(joystick); +} + +Uint16 SDL_GetGamepadFirmwareVersion(SDL_Gamepad *gamepad) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return 0; + } + return SDL_GetJoystickFirmwareVersion(joystick); +} + +const char * SDL_GetGamepadSerial(SDL_Gamepad *gamepad) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return NULL; + } + return SDL_GetJoystickSerial(joystick); + +} + +Uint64 SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepad) +{ + Uint64 handle = 0; + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, 0); + + handle = gamepad->joystick->steam_handle; + } + SDL_UnlockJoysticks(); + + return handle; +} + +SDL_JoystickConnectionState SDL_GetGamepadConnectionState(SDL_Gamepad *gamepad) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return SDL_JOYSTICK_CONNECTION_INVALID; + } + return SDL_GetJoystickConnectionState(joystick); +} + +SDL_PowerState SDL_GetGamepadPowerInfo(SDL_Gamepad *gamepad, int *percent) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (percent) { + *percent = -1; + } + if (!joystick) { + return SDL_POWERSTATE_ERROR; + } + return SDL_GetJoystickPowerInfo(joystick, percent); +} + +/* + * Return if the gamepad in question is currently attached to the system, + * \return 0 if not plugged in, 1 if still present. + */ +bool SDL_GamepadConnected(SDL_Gamepad *gamepad) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return false; + } + return SDL_JoystickConnected(joystick); +} + +/* + * Get the joystick for this gamepad + */ +SDL_Joystick *SDL_GetGamepadJoystick(SDL_Gamepad *gamepad) +{ + SDL_Joystick *joystick; + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, NULL); + + joystick = gamepad->joystick; + } + SDL_UnlockJoysticks(); + + return joystick; +} + +/* + * Return the SDL_Gamepad associated with an instance id. + */ +SDL_Gamepad *SDL_GetGamepadFromID(SDL_JoystickID joyid) +{ + SDL_Gamepad *gamepad; + + SDL_LockJoysticks(); + gamepad = SDL_gamepads; + while (gamepad) { + if (gamepad->joystick->instance_id == joyid) { + SDL_UnlockJoysticks(); + return gamepad; + } + gamepad = gamepad->next; + } + SDL_UnlockJoysticks(); + return NULL; +} + +/** + * Return the SDL_Gamepad associated with a player index. + */ +SDL_Gamepad *SDL_GetGamepadFromPlayerIndex(int player_index) +{ + SDL_Gamepad *result = NULL; + + SDL_LockJoysticks(); + { + SDL_Joystick *joystick = SDL_GetJoystickFromPlayerIndex(player_index); + if (joystick) { + result = SDL_GetGamepadFromID(joystick->instance_id); + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the SDL joystick layer bindings for this gamepad + */ +SDL_GamepadBinding **SDL_GetGamepadBindings(SDL_Gamepad *gamepad, int *count) +{ + SDL_GamepadBinding **bindings = NULL; + + if (count) { + *count = 0; + } + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, NULL); + + size_t pointers_size = ((gamepad->num_bindings + 1) * sizeof(SDL_GamepadBinding *)); + size_t elements_size = (gamepad->num_bindings * sizeof(SDL_GamepadBinding)); + bindings = (SDL_GamepadBinding **)SDL_malloc(pointers_size + elements_size); + if (bindings) { + SDL_GamepadBinding *binding = (SDL_GamepadBinding *)((Uint8 *)bindings + pointers_size); + int i; + for (i = 0; i < gamepad->num_bindings; ++i, ++binding) { + bindings[i] = binding; + SDL_copyp(binding, &gamepad->bindings[i]); + } + bindings[i] = NULL; + + if (count) { + *count = gamepad->num_bindings; + } + } + } + SDL_UnlockJoysticks(); + + return bindings; +} + +bool SDL_RumbleGamepad(SDL_Gamepad *gamepad, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return false; + } + return SDL_RumbleJoystick(joystick, low_frequency_rumble, high_frequency_rumble, duration_ms); +} + +bool SDL_RumbleGamepadTriggers(SDL_Gamepad *gamepad, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return false; + } + return SDL_RumbleJoystickTriggers(joystick, left_rumble, right_rumble, duration_ms); +} + +bool SDL_SetGamepadLED(SDL_Gamepad *gamepad, Uint8 red, Uint8 green, Uint8 blue) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return false; + } + return SDL_SetJoystickLED(joystick, red, green, blue); +} + +bool SDL_SendGamepadEffect(SDL_Gamepad *gamepad, const void *data, int size) +{ + SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad); + + if (!joystick) { + return false; + } + return SDL_SendJoystickEffect(joystick, data, size); +} + +void SDL_CloseGamepad(SDL_Gamepad *gamepad) +{ + SDL_Gamepad *gamepadlist, *gamepadlistprev; + + SDL_LockJoysticks(); + + if (!SDL_ObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD)) { + SDL_UnlockJoysticks(); + return; + } + + // First decrement ref count + if (--gamepad->ref_count > 0) { + SDL_UnlockJoysticks(); + return; + } + + SDL_CloseJoystick(gamepad->joystick); + + gamepadlist = SDL_gamepads; + gamepadlistprev = NULL; + while (gamepadlist) { + if (gamepad == gamepadlist) { + if (gamepadlistprev) { + // unlink this entry + gamepadlistprev->next = gamepadlist->next; + } else { + SDL_gamepads = gamepad->next; + } + break; + } + gamepadlistprev = gamepadlist; + gamepadlist = gamepadlist->next; + } + + SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false); + SDL_free(gamepad->bindings); + SDL_free(gamepad->last_match_axis); + SDL_free(gamepad->last_hat_mask); + SDL_free(gamepad); + + SDL_UnlockJoysticks(); +} + +/* + * Quit the gamepad subsystem + */ +void SDL_QuitGamepads(void) +{ + SDL_Gamepad *gamepad; + + SDL_LockJoysticks(); + + for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) { + SDL_PrivateGamepadRemoved(gamepad->joystick->instance_id); + } + + SDL_gamepads_initialized = false; + + SDL_RemoveEventWatch(SDL_GamepadEventWatcher, NULL); + + while (SDL_gamepads) { + SDL_gamepads->ref_count = 1; + SDL_CloseGamepad(SDL_gamepads); + } + + SDL_UnlockJoysticks(); +} + +void SDL_QuitGamepadMappings(void) +{ + GamepadMapping_t *pGamepadMap; + + SDL_AssertJoysticksLocked(); + + while (s_pSupportedGamepads) { + pGamepadMap = s_pSupportedGamepads; + s_pSupportedGamepads = s_pSupportedGamepads->next; + SDL_free(pGamepadMap->name); + SDL_free(pGamepadMap->mapping); + SDL_free(pGamepadMap); + } + + SDL_FreeVIDPIDList(&SDL_allowed_gamepads); + SDL_FreeVIDPIDList(&SDL_ignored_gamepads); + + if (s_gamepadInstanceIDs) { + SDL_DestroyHashTable(s_gamepadInstanceIDs); + s_gamepadInstanceIDs = NULL; + } +} + +/* + * Event filter to transform joystick events into appropriate gamepad ones + */ +static void SDL_SendGamepadAxis(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadAxis axis, Sint16 value) +{ + SDL_AssertJoysticksLocked(); + + // translate the event, if desired + if (SDL_EventEnabled(SDL_EVENT_GAMEPAD_AXIS_MOTION)) { + SDL_Event event; + event.type = SDL_EVENT_GAMEPAD_AXIS_MOTION; + event.common.timestamp = timestamp; + event.gaxis.which = gamepad->joystick->instance_id; + event.gaxis.axis = axis; + event.gaxis.value = value; + SDL_PushEvent(&event); + } +} + +static void SDL_SendGamepadButton(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadButton button, bool down) +{ + SDL_Event event; + + SDL_AssertJoysticksLocked(); + + if (button == SDL_GAMEPAD_BUTTON_INVALID) { + return; + } + + if (down) { + event.type = SDL_EVENT_GAMEPAD_BUTTON_DOWN; + } else { + event.type = SDL_EVENT_GAMEPAD_BUTTON_UP; + } + + if (button == SDL_GAMEPAD_BUTTON_GUIDE) { + Uint64 now = SDL_GetTicks(); + if (down) { + gamepad->guide_button_down = now; + + if (gamepad->joystick->delayed_guide_button) { + // Skip duplicate press + return; + } + } else { + if (now < (gamepad->guide_button_down + SDL_MINIMUM_GUIDE_BUTTON_DELAY_MS)) { + gamepad->joystick->delayed_guide_button = true; + return; + } + gamepad->joystick->delayed_guide_button = false; + } + } + + // translate the event, if desired + if (SDL_EventEnabled(event.type)) { + event.common.timestamp = timestamp; + event.gbutton.which = gamepad->joystick->instance_id; + event.gbutton.button = button; + event.gbutton.down = down; + SDL_PushEvent(&event); + } +} + +static const Uint32 SDL_gamepad_event_list[] = { + SDL_EVENT_GAMEPAD_AXIS_MOTION, + SDL_EVENT_GAMEPAD_BUTTON_DOWN, + SDL_EVENT_GAMEPAD_BUTTON_UP, + SDL_EVENT_GAMEPAD_ADDED, + SDL_EVENT_GAMEPAD_REMOVED, + SDL_EVENT_GAMEPAD_REMAPPED, + SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN, + SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION, + SDL_EVENT_GAMEPAD_TOUCHPAD_UP, + SDL_EVENT_GAMEPAD_SENSOR_UPDATE, +}; + +void SDL_SetGamepadEventsEnabled(bool enabled) +{ + unsigned int i; + + for (i = 0; i < SDL_arraysize(SDL_gamepad_event_list); ++i) { + SDL_SetEventEnabled(SDL_gamepad_event_list[i], enabled); + } +} + +bool SDL_GamepadEventsEnabled(void) +{ + bool enabled = false; + unsigned int i; + + for (i = 0; i < SDL_arraysize(SDL_gamepad_event_list); ++i) { + enabled = SDL_EventEnabled(SDL_gamepad_event_list[i]); + if (enabled) { + break; + } + } + return enabled; +} + +void SDL_GamepadHandleDelayedGuideButton(SDL_Joystick *joystick) +{ + SDL_Gamepad *gamepad; + + SDL_AssertJoysticksLocked(); + + for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) { + if (gamepad->joystick == joystick) { + SDL_SendGamepadButton(0, gamepad, SDL_GAMEPAD_BUTTON_GUIDE, false); + + // Make sure we send an update complete event for this change + if (!gamepad->joystick->update_complete) { + gamepad->joystick->update_complete = SDL_GetTicksNS(); + } + break; + } + } +} + +const char *SDL_GetGamepadAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button) +{ + const char *result = NULL; +#ifdef SDL_JOYSTICK_MFI + const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button); + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, NULL); + + result = IOS_GetAppleSFSymbolsNameForButton(gamepad, button); + } + SDL_UnlockJoysticks(); +#endif + return result; +} + +const char *SDL_GetGamepadAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis) +{ + const char *result = NULL; +#ifdef SDL_JOYSTICK_MFI + const char *IOS_GetAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis); + + SDL_LockJoysticks(); + { + CHECK_GAMEPAD_MAGIC(gamepad, NULL); + + result = IOS_GetAppleSFSymbolsNameForAxis(gamepad, axis); + } + SDL_UnlockJoysticks(); +#endif + return result; +} diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_gamepad_c.h b/contrib/SDL-3.2.8/src/joystick/SDL_gamepad_c.h new file mode 100644 index 0000000..f1b1d10 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/SDL_gamepad_c.h @@ -0,0 +1,50 @@ +/* + 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. +*/ + +#ifndef SDL_gamepad_c_h_ +#define SDL_gamepad_c_h_ + +#include "SDL_internal.h" + +// Useful functions and variables from SDL_gamepad.c + +// Initialization and shutdown functions +extern bool SDL_InitGamepadMappings(void); +extern void SDL_QuitGamepadMappings(void); +extern bool SDL_InitGamepads(void); +extern void SDL_QuitGamepads(void); + +extern void SDL_PrivateGamepadAdded(SDL_JoystickID instance_id); +extern void SDL_PrivateGamepadRemoved(SDL_JoystickID instance_id); + +// Function to return whether a joystick name and GUID is a gamepad +extern bool SDL_IsGamepadNameAndGUID(const char *name, SDL_GUID guid); + +// Function to return whether a gamepad should be ignored +extern bool SDL_ShouldIgnoreGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name); + +// Handle delayed guide button on a gamepad +extern void SDL_GamepadHandleDelayedGuideButton(SDL_Joystick *joystick); + +// Handle system sensor data +extern void SDL_GamepadSensorWatcher(Uint64 timestamp, SDL_SensorID sensor, Uint64 sensor_timestamp, float *data, int num_values); + +#endif // SDL_gamepad_c_h_ diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_gamepad_db.h b/contrib/SDL-3.2.8/src/joystick/SDL_gamepad_db.h new file mode 100644 index 0000000..d72e422 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/SDL_gamepad_db.h @@ -0,0 +1,904 @@ +/* + 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" + +/* Default mappings we support + + The easiest way to generate a new mapping is to start Steam in Big Picture + mode, configure your joystick and then look in config/config.vdf in your + Steam installation directory for the "SDL_GamepadBind" entry. + + Alternatively, you can use the app located in test/controllermap + */ +static const char *s_GamepadMappings[] = { +#ifdef SDL_JOYSTICK_PRIVATE + SDL_PRIVATE_GAMEPAD_DEFINITIONS +#endif +#ifdef SDL_JOYSTICK_XINPUT + "xinput,*,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", +#endif +#ifdef SDL_JOYSTICK_WGI + "03000000491900001904000000007700,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,", + "03000000d11800000094000000007700,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", + "030000007e0500000920000000007701,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "030000004c050000c405000000007701,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000e60c000000007700,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "0300000032150000000a000000007703,Razer Atrox Arcade Stick,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b11,dpup:b10,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,x:b2,y:b3,", + "03000000de280000ff11000000007701,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:b12,dpleft:b13,dpright:b11,dpup:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a4,leftx:a1,lefty:a0~,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a2~,start:b7,x:b2,y:b3,", +#endif +#ifdef SDL_JOYSTICK_DINPUT + "03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,", + "03000000c82d00000090000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00001038000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000650000000000000,8BitDo M30 Gamepad,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,", + "03000000c82d00005106000000000000,8BitDo M30 Gamepad,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,", + "03000000c82d00002090000000000000,8BitDo Micro gamepad,a:b1,b:b0,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,guide:b12,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00001590000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00006528000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "030000003512000012ab000000000000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000022000000090000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000203800000900000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00002038000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000660000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000060000000000000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000061000000000000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000102800000900000000000000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00001290000000000000,8BitDo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00006228000000000000,8BitDo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000260000000000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000261000000000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000160000000000000,8BitDo SN30 Pro,crc:769e,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", /* Firmware v2.00, USB */ + "03000000c82d00000160000000000000,8BitDo SN30 Pro,crc:fa59,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", /* Firmware v1.26, USB */ + "03000000c82d00000161000000000000,8BitDo SN30 Pro,crc:190b,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", /* Bluetooth, firmware v1.26 uses b2 for guide, firmware v2.00 uses b12 for guide */ + "030000003512000020ab000000000000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00001b30000000000000,8BitDo Ultimate 2C Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a2,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a5,start:b11,x:b3,y:b4,", /* Bluetooth */ + "03000000c82d00001130000000000000,8BitDo Ultimate Wired Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000c82d00001330000000000000,8BitDo Ultimate Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000c82d00001890000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00003032000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000a00500003232000000000000,8BitDo Zero Gamepad,a:b1,b:b0,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000050b00000579000000000000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000050b00000679000000000000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "030000008f0e00001200000000000000,Acme GA-02,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", + "03000000fa190000f0ff000000000000,Acteck AGJ-3200,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000341a00003608000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006f0e00000263000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006f0e00001101000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006f0e00001401000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006f0e00001402000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006f0e00001901000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006f0e00001a01000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000d62000001d57000000000000,Airflo PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000491900001904000000000000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,", + "03000000d62000002a79000000000000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000d81d00000b00000000000000,BUFFALO BSGP1601 Series ,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,", + "03000000d6200000e557000000000000,Batarang,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,", + "030000006f0e00003201000000000000,Battlefield 4 PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000bc2000006012000000000000,Betop 2126F,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000bc2000000055000000000000,Betop BFM Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000bc2000006312000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000bc2000006412000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000c01100000555000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000c01100000655000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000790000000700000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,", + "03000000808300000300000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,", + "030000006b1400000055000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "030000006b1400000103000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,", + "0300000066f700000500000000000000,BrutalLegendTest,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,", + "03000000e82000006058000000000000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,", + "03000000a306000022f6000000000000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,", + "03000000451300000830000000000000,Defender Game Racer X7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000790000000600000000000000,Defender Joystick Cobra R4,crc:c77a,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2~,righty:a3~,start:b9,x:b3,y:b0,", + "03000000791d00000103000000000000,Dual Box WII,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000bd12000002e0000000000000,Dual USB Vibration Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,", + "030000006f0e00003001000000000000,EA SPORTS PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000341a00000108000000000000,EXEQ RF USB Gamepad 8206,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "030000008f0e00000f31000000000000,EXEQ,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,", + "03000000b80500000410000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,", + "03000000b80500000610000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,", + "03000000852100000201000000000000,FF-GP1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00002700000000000000,FIGHTING STICK V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000008f0e00000d31000000000000,GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000300f00000b01000000000000,GGE909 Recoil Pad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,", + "03000000790000002201000000000000,Game Controller for PC,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "0300000066f700000100000000000000,Game VIB Joystick,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,", + "03000000491900000204000000000000,GameSir T4 Pro,crc:1aa4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000ac0500003d03000000000000,GameSir,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000ac0500004d04000000000000,GameSir,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000ac0500001a06000000000000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000ffff00000000000000000000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000c01100000140000000000000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000260900002625000000000000,Gamecube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", + "03000000280400000140000000000000,Gamepad Pro USB,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000005c1a00003330000000000000,Genius MaxFire Grandias 12V,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", + "030000008305000031b0000000000000,Genius Maxfire Blaze 3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000451300000010000000000000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "030000008305000009a0000000000000,Genius,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000f025000021c1000000000000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000f0250000c383000000000000,Gioteck VX2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000f0250000c483000000000000,Gioteck VX2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000f0250000c283000000000000,Gioteck,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000d11800000094000000000000,Google Stadia Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b11,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", + "03000000632500002605000000000000,HJD-X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "030000000d0f00008400000000000000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00008500000000000000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00008800000000000000,HORI Fighting Stick mini 4 (PS3),a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,", + "030000000d0f00008700000000000000,HORI Fighting Stick mini 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000000d0f00006e00000000000000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00006600000000000000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f0000ee00000000000000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000250900000017000000000000,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,", + "03000000341a00000302000000000000,Hama Scorpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00004900000000000000,Hatsune Miku Sho Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000d81400000862000000000000,HitBox Edition Cthulhu+,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,", + "030000000d0f00005f00000000000000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00005e00000000000000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00004000000000000000,Hori Fighting Stick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,", + "030000000d0f00000900000000000000,Hori Pad 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00005400000000000000,Hori Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00004d00000000000000,Hori Pad A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00009200000000000000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000000d0f0000c100000000000000,Horipad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000008f0e00001330000000000000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,", + "030000006f0e00002401000000000000,INJUSTICE FightStick PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "03000000ac0500002c02000000000000,IPEGA,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000b50700001403000000000000,Impact Black,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,", + "03000000491900000204000000000000,Ipega PG-9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "030000006e0500000520000000000000,JC-P301U,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,", + "030000006e0500000320000000000000,JC-U3613M (DInput),a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,", + "030000006e0500000720000000000000,JC-W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,", + "03000000790000000200000000000000,King PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,", + "030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d040000d2ca000000000000,Logitech Cordless Precision,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,", + "030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d04000018c2000000000000,Logitech F510 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d04000019c2000000000000,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", /* Guide button doesn't seem to be sent in DInput mode. */ + "030000006d0400001ac2000000000000,Logitech Precision Gamepad,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000380700006382000000000000,MLG Gamepad PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000c62400002a89000000000000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000c62400002b89000000000000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000c62400001a89000000000000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000c62400001b89000000000000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000250900006688000000000000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,", + "03000000380700006652000000000000,Mad Catz C.T.R.L.R,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,", + "03000000380700005032000000000000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000380700005082000000000000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000380700008433000000000000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000380700008483000000000000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000380700008134000000000000,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000380700008184000000000000,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000380700006252000000000000,Mad Catz Micro C.T.R.L.R,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,", + "03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000380700001888000000000000,MadCatz SFIV FightStick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000380700008532000000000000,Madcatz Arcade Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000002a0600001024000000000000,Matricom,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", + "03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,", + "03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", + "030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,", + "0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,", + "03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000efbe0000edfe000000000000,Monect Virtual Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b0,", + "030000006b140000010c000000000000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,", + "03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,", + "030000005509000000b4000000000000,NVIDIA Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000004b120000014d000000000000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", + "03000000790000004318000000000000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,", + "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,", + "03000000d62000006d57000000000000,OPP PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,", + "03000000782300000a10000000000000,Onlive Wireless Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,", + "030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,", + "0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,righttrigger:-a5,start:b11,x:b3,y:b4,", + "03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,", + "030000006f0e00000901000000000000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "03000000632500002306000000000000,PS Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000e30500009605000000000000,PS to USB convert cable,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,", + "03000000100800000100000000000000,PS1 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,", + "030000008f0e00007530000000000000,PS1 Controller,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000100800000300000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,", + "03000000250900008888000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,", + "03000000666600006706000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", + "030000006b1400000303000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "030000009d0d00001330000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000250900000500000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,", + "030000004c0500006802000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b10,lefttrigger:a3~,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:a4~,rightx:a2,righty:a5,start:b8,x:b3,y:b0,", + "03000000632500007505000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,", + "030000008f0e00001431000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000003807000056a8000000000000,PS3 RF pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000100000008200000000000000,PS360+ v1.66,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:h0.4,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000004c050000a00b000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,", + "030000004c050000cc09000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000e60c000000000000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,", + "03000000d62000002640000000000000,PowerA OPS v1 Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000d62000003340000000000000,PowerA OPS v3 Pro Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000d62000009557000000000000,Pro Elite PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000d62000009f31000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000d6200000c757000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000222c00000020000000000000,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,", + "03000000300f00000011000000000000,QanBa Arcade JoyStick 1008,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b10,x:b0,y:b3,", + "03000000300f00001611000000000000,QanBa Arcade JoyStick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,", + "03000000300f00001210000000000000,QanBa Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,", + "03000000341a00000104000000000000,QanBa Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,", + "03000000222c00000025000000000000,Qanba Dragon Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick (PS4),a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00001100000000000000,REAL ARCADE PRO.3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000000d0f00007000000000000000,REAL ARCADE PRO.4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000000d0f00002200000000000000,REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000050b00005819000000000000,ROG Chakram Core,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,", + "03000000050b0000181a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,", + "03000000050b00001a1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,", + "03000000050b00001c1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,", + "03000000050b0000e318000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,", + "03000000050b0000e518000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,", + "030000000d0f0000ad00000000000000,RX Gamepad,a:b0,b:b4,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b3,rightshoulder:b6,start:b9,x:b2,y:b1,", + "03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000321500000204000000000000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000321500000104000000000000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000321500000507000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000321500000707000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000321500000011000000000000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000321500000009000000000000,Razer Serval,+lefty:+a2,-lefty:-a1,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,leftx:a0,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000000d0f00006a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00006b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00008a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00008b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00005b00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,", + "0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,", + "03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "030000006b140000130d000000000000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000006f0e00001e01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006f0e00002801000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006f0e00002f01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000341a00000208000000000000,SL-6555-SBK,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,", + "03000000341a00000908000000000000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000790000000600000000000000,SPEEDLINK STRIKE Gamepad,crc:5811,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,", + "03000000790000001c18000000000000,STK-7024X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000ff1100003133000000000000,SVEN X-PAD,a:b2,b:b3,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a4,start:b5,x:b0,y:b1,", + "03000000457500002211000000000000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000a306000023f6000000000000,Saitek Cyborg V.1 Game pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,", + "03000000a30600001af5000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,", + "03000000300f00001201000000000000,Saitek Dual Analog Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,", + "03000000a30600000cff000000000000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,x:b0,y:b1,", + "03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,", + "03000000300f00001001000000000000,Saitek P480 Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,", + "03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,", + "03000000a30600000b04000000000000,Saitek P990,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,", + "03000000a30600002106000000000000,Saitek PS1000,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,", + "03000000a306000020f6000000000000,Saitek PS2700,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,", + "03000000300f00001101000000000000,Saitek Rumble Pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,", + "0300000000050000289b000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,", + "030000009b2800000500000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,", + "030000008f0e00000800000000000000,SpeedLink Strike FX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000c01100000591000000000000,Speedlink Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "030000004c0500006802000000000000,SplitFish Game Controller,crc:5628,a:b0,b:b16,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,leftshoulder:b17,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b10,", + "03000000de280000ff11000000000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000110100003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000381000001814000000000000,SteelSeries Stratus XL,a:b0,b:b1,back:b18,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b19,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b2,y:b3,", + "03000000110100001914000000000000,SteelSeries,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000d620000011a7000000000000,Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000004f04000007d0000000000000,T Mini Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,", + "030000004f0400000ed0000000000000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004f04000015b3000000000000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,", + "030000004f04000023b3000000000000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,", + "030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,", + "03000000666600000488000000000000,TigerGame PS/PS2 Game Controller Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,", + "03000000d62000006000000000000000,Tournament PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000005f140000c501000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000b80500000210000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000d90400000200000000000000,TwinShock PS2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,", + "03000000300f00000701000000000000,USB 4-Axis 12-Button Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,", + "03000000341a00002308000000000000,USB Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "030000006b1400000203000000000000,USB Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000790000000a00000000000000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,", + "03000000f0250000c183000000000000,USB Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000ff1100004133000000000000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,", + "03000000632500002305000000000000,USB Vibration Joystick (BM),a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000790000001b18000000000000,Venom Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000006f0e00000302000000000000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000006f0e00000702000000000000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "03000000450c00002043000000000000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000341a00000608000000000000,Xeox,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,", + "03000000c0160000e105000000000000,Xin-Mo Dual Arcade,crc:2246,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,", /* Ultimate Atari Fight Stick */ + "03000000790000004f18000000000000,ZD-T Android,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", + "03000000120c0000101e000000000000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000830500006020000000000000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000101c0000171c000000000000,uRage Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", +#endif +#ifdef SDL_PLATFORM_MACOS + "03000000c82d00000090000001000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000650000001000000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a5,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac05000004000000a8986d04,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,lefttrigger:b12,rightshoulder:b13,righttrigger:b14,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00001590000001000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "030000003512000012ab000001000000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000022000000090000001000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000660000000020000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000102800000900000000000000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00001290000001000000,8BitDo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000260000001000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00001b30000001000000,8BitDo Ultimate 2C Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", /* Bluetooth */ + "03000000c82d00001130000000020000,8BitDo Ultimate Wired Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000c82d00001330000000020000,8BitDo Ultimate Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000c82d00001890000001000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000a00500003232000008010000,8BitDo Zero Gamepad,a:b1,b:b2,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000a00500003232000009010000,8BitDo Zero Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000050b00000579000000010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b42,paddle1:b9,paddle2:b11,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,", + "03000000050b00000679000000010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b23,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,", + "03000000491900001904000001010000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,", + "03000000710100001904000000010000,Amazon Luna Controller,a:b0,b:b1,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "03000000c62400001a89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,", + "03000000c62400001b89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000d62000002a79000000010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,", + "03000000a306000022f6000001030000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,", + "030000000d0f00008400000000010000,Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00008500000000010000,Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000ac0500001a06000002020000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000c01100000140000000010000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000ad1b000001f9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,", + "03000000d11800000094000000010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", + "030000000d0f00005f00000000000000,HORI Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00005e00000000000000,HORI Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00008800000000010000,HORI Fighting Stick mini 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000000d0f00008700000000010000,HORI Fighting Stick mini 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000000d0f00004d00000000000000,HORI Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f0000aa00000072050000,HORI Real Arcade Pro,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "030000000d0f00006e00000000010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00006600000000010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00006600000000000000,HORIPAD FPS PLUS 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00005f00000000010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00005e00000000010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000008f0e00001330000011010000,HuiJia SNES Controller,a:b4,b:b2,back:b16,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b12,rightshoulder:b14,start:b18,x:b6,y:b0,", + "030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d04000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", /* Guide button doesn't seem to be sent in DInput mode. */ + "030000006d04000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d0400001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,", + "030000006d04000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", /* This includes F710 in DInput mode and the "Logitech Cordless RumblePad 2", at the very least. */ + "03000000d8140000cecf000000000000,MC Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "03000000c62400002a89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000c62400002b89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000380700005032000000010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000380700008433000000010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000380700008483000000010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", + "0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,", + "03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,", + "030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,", + "03000000550900001472000025050000,NVIDIA Controller v01.04,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,", + "030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", + "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "050000007e05000009200000ff070000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "0300000009120000072f000000010000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a2,leftx:a0,lefty:a1,righttrigger:a5,start:b11,x:b3,y:b4,", + "030000006f0e00000901000002010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", + "030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", + "030000004c050000a00b000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,", + "03000000222c00000225000000010000,Qanba Dragon Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,", + "03000000321500000204000000010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000321500000104000000010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000321500000010000000010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000321500000507000001010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000321500000011000000010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000321500000009000000020000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", + "0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,", + "03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,", + "03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "030000006b140000130d000000010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,", + "03000000457500002211000000010000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000b40400000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,", + "03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,", + "030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000005e0400008e02000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,", + "050000004e696d6275732b0000000000,SteelSeries Nimbus+,a:b0,b:b1,back:b15,dpdown:b11,dpleft:b13,dpright:b12,dpup:b10,guide:b16,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3~,start:b14,x:b2,y:b3,", + "03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,", + "03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,", + "03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,", + "03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,", + "03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,", + "030000004f0400000ed0000000020000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,", + "030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,", + "03000000bd12000015d0000000000000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,", + "03000000bd12000015d0000000010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,", + "03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,", + "030000006f0e00000302000025040000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000006f0e00000702000003060000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,", + "050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,", + "030000005e0400008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,", + "03000000c6240000045d000000000000,Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,", + "030000005e040000050b000003090000,Xbox Elite Wireless Controller,a:b0,b:b1,back:b38,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "030000005e040000d102000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,", + "030000005e040000dd02000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,", + "030000005e040000e302000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,", + "030000005e040000200b000011050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "030000005e040000e002000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000005e040000e002000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000005e040000ea02000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,", + "030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,", + "03000000c0160000e105000000040000,Xin-Mo Dual Arcade,crc:82d5,a:b2,b:b4,back:b18,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,rightshoulder:b8,righttrigger:b10,start:b16,x:b0,y:b6,", /* Ultimate Atari Fight Stick */ + "03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000120c0000101e000000010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,", +#endif +#if defined(SDL_JOYSTICK_LINUX) || defined(SDL_PLATFORM_OPENBSD) + "03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00002090000000010000,8BitDo Micro gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b12,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00001590000011010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "030000003512000012ab000010010000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000022000000090000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000190000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00002038000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000020000000000000,8BitDo Pro 2 Wired Controller for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "06000000c82d00000020000006010000,8BitDo Pro 2 Wired Controller for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000c82d00000660000011010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00000061000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000102800000900000000010000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00003028000000010000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000260000011010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "030000003512000020ab000010010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000202800000900000000010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00001b30000001000000,8BitDo Ultimate 2C Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", /* Bluetooth */ + "03000000c82d00001130000011010000,8BitDo Ultimate Wired Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000c82d00001330000011010000,8BitDo Ultimate Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000c82d00001890000011010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000a00500003232000001000000,8BitDo Zero Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000a00500003232000008010000,8BitDo Zero Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000c82d00000031000011010000,8Bitdo Receiver,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,", + "03000000c82d00001290000011010000,8Bitdo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d00006228000000010000,8Bitdo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,", + "05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,", + "03000000050b00000579000011010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b36,paddle1:b52,paddle2:b53,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "05000000050b00000679000000010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b21,paddle1:b22,paddle2:b23,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "030000006f0e00003901000020060000,Afterglow Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000006f0e00001302000000010000,Afterglow,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "05000000491900000204000021000000,Amazon Fire Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b17,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000491900001904000011010000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,", + "05000000710100001904000000010000,Amazon Luna Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "03000000790000003018000011010000,Arcade Fightstick F300,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "03000000503200000110000000000000,Atari Classic Controller,a:b0,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,x:b1,", + "05000000503200000110000000000000,Atari Classic Controller,a:b0,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,x:b1,", + "03000000503200000210000000000000,Atari Game Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,", + "05000000503200000210000000000000,Atari Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,", + "05000000503200000210000000000000128804098,Atari Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,", + "030000005e0400008e02000047010000,Atari Xbox 360 Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000c62400001b89000011010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000d62000002a79000011010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000120c0000f70e000011010000,Brook Universal Fighting Board,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:,lefty:,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:,righty:,start:b9,x:b0,y:b3,", + "03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,", + "03000000ffff0000ffff000000010000,Chinese-made Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", + "03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,", + "03000000a306000022f6000011010000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,", + "050000004c050000f20d000000010000,DualSense Edge Wireless Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000790000001100000010010000,Elecom Gamepad,crc:e86c,a:b2,b:b3,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b0,y:b1,", + "0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "0500000047532067616d657061640000,GS Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", + "03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "0500000049190000020400001b010000,GameSir T4 Pro,crc:8283,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b23,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000ac0500001a06000011010000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000c01100000140000011010000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000008f0e00000800000010010000,Gasia Co. Ltd PS(R) Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000d11800000094000011010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", + "05000000d11800000094000000010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", + "03000000280400000140000000010000,Gravis Gamepad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,", + "030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", + "03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00006b00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00005001000009040000,HORI Fighting Commander OCTA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000000d0f00008400000011010000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00008500000010010000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00008800000011010000,HORI Fighting Stick mini 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000000d0f00008700000011010000,HORI Fighting Stick mini 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,rightshoulder:b5,rightstick:b11,righttrigger:a4,start:b9,x:b0,y:b3,", + "030000000d0f0000d800000072056800,HORI Real Arcade Pro S,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "030000000d0f0000aa00000011010000,HORI Real Arcade Pro,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,", + "030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000000d0f00008600000002010000,Hori Fighting Commander,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,", + "03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000d80400008200000003000000,IMS PCU#0 Gamepad Interface,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b5,x:b3,y:b2,", + "03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,", + "05000000491900000204000000000000,Ipega PG-9087S,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,", + "03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,", + "03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,", + "030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000006d040000d1ca000011010000,Logitech Chillstream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", /* Guide button doesn't seem to be sent in DInput mode. */ + "030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,", + "03000000c62400002b89000011010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "05000000c62400002a89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "05000000c62400001a89000000010000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000250900006688000000010000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,", + "05000000380700006652000025010000,Mad Catz C.T.R.L.R ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000380700005032000011010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000380700008433000011010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000380700008483000011010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,", + "03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", + "03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,", + "030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,", + "030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", + "030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", + "030000005e0400008902000020010000,Microsoft Xbox Controller S,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", + "05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "030000006b140000010c000010010000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,", + "03000000550900001072000011010000,NVIDIA Controller v01.03,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", + "03000000550900001472000011010000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,", + "05000000550900001472000001000000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,", + "030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", + "03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000790000004318000010010000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e0500000620000001800000,Nintendo Switch Joy-Con (L),a:b16,b:b15,guide:b4,leftshoulder:b6,leftstick:b12,leftx:a1,lefty:a0~,rightshoulder:b8,start:b9,x:b14,y:b17,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "060000007e0500000620000000000000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "060000007e0500000820000000000000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e0500000720000001800000,Nintendo Switch Joy-Con (R),a:b1,b:b2,guide:b9,leftshoulder:b4,leftstick:b10,leftx:a1~,lefty:a0,rightshoulder:b6,start:b8,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000d620000013a7000011010000,Nintendo Switch PowerA Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000d620000011a7000011010000,Nintendo Switch PowerA Core Plus Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "030000007e0500000920000011810000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000004c69632050726f20436f6e00,Nintendo Switch Pro Controller,crc:15b7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e0500000920000001800000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e0500000603000000060000,Nintendo Wii Remote Classic Controller,crc:0d8a,a:b1,b:b0,back:b10,dpdown:b14,dpleft:b12,dpright:b13,dpup:b11,guide:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e0500000603000000060000,Nintendo Wii Remote,crc:60be,a:b1,b:b0,back:b4,dpdown:b8,dpleft:b6,dpright:b7,dpup:b5,guide:b2,start:b3,x:b9,y:b10,", + "05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,", + "05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,", + "05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,", + "030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,", + "03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000006f0e00000901000011010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,", + "03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", + "030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", + "030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", + "030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", + "030000006f0e00001402000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000008f0e00000300000010010000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", + "050000004c0500006802000000800000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", + "050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", + "05000000504c415953544154494f4e00,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", + "060000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", + "030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", + "030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", + "030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", + "050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", + "050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", + "050000004c050000cc09000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", + "030000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000e60c000011010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000e60c000011810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", + "050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "050000004c050000e60c000000810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", + "030000004c050000da0c000011010000,Playstation Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,", + "03000000c62400003a54000001010000,PowerA XBox One Controller,a:b0,b:b1,back:b6,dpdown:h0.7,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,", + "03000000222c00000225000011010000,Qanba Dragon Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000222c00000025000011010000,Qanba Dragon Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000222c00001220000011010000,Qanba Drone 2 Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000222c00001020000011010000,Qanba Drone 2 Arcade Joystick (PS5),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000222c00000020000011010000,Qanba Drone Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,", + "03000000222c00000223000011010000,Qanba Obsidian Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000222c00000023000011010000,Qanba Obsidian Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000321500000204000011010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000321500000104000011010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000321500000507000000010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000321500000011000011010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", + "050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", + "0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,", + "03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "030000006b140000130d000011010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000006f0e00001e01000011010000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000632500007505000010010000,SHANWAN PS3/PC Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000341a00000908000010010000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "03000000457500002211000010010000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,", + "03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,", + "03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,", + "03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,", + "03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,", + "03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,", + "03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,a:b12,b:b10,back:b4,dpdown:b2,dpleft:b3,dpright:b1,dpup:b0,leftshoulder:b9,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b8,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b5,x:b13,y:b11,", + "03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000632500002305000010010000,ShanWan USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,", + "03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,", + "030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", + "03000000de2800000112000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,", + "03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", + "03000000de2800000211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,", + "03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", + "03000000de2800004211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,", + "03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", + "05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", + "05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", + "03000000de2800000512000000016800,Steam Deck Controller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "03000000de2800000512000011010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,misc1:b2,paddle1:b21,paddle2:b20,paddle3:b23,paddle4:b22,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,", + "03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "05000000110100001914000009010000,SteelSeries Stratus XL,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b18,leftshoulder:b6,leftstick:b13,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:+a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000ad1b000038f0000090040000,Street Fighter IV FightStick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,", + "0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,", + "030000004f0400000ed0000011010000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,", + "030000004f04000015b3000001010000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,", + "030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,", + "030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,", + "030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000bd12000015d0000010010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,", + "03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,", + "03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,", + "03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,", + "03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,", + "05000000ac0500003232000001000000,VR-BOX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", + "030000006f0e00000302000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000006f0e00000702000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", + "030000000d0f0000ab01000011010000,Wireless HORIPAD For Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,misc3:b16,misc4:b17,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "050000000d0f00009601000091000000,Wireless HORIPAD For Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,misc3:b16,misc4:b17,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", + "0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,", + "030000005e040000a102000014010000,Xbox 360 Wireless Receiver (XBOX),a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", + "050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + "050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,", + "03000000c0160000e105000010010000,Xin-Mo Dual Arcade,crc:82d5,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,", /* Ultimate Atari Fight Stick */ + "03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", + "03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,", + "030000009b2800008000000020020000,raphnet technologies 1-player WUSBMote v2.2,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,", + "030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,", +#endif +#ifdef SDL_PLATFORM_OPENBSD + "030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "030000005e0400008e02000010010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4~,start:b7,x:b2,y:b3,", +#endif +#ifdef SDL_PLATFORM_ANDROID + "05000000c82d000006500000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a4,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000051060000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000020900000ffff3f00,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b5,leftshoulder:b9,lefttrigger:a6,rightshoulder:b10,righttrigger:a7,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000015900000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000065280000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000000220000000900000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000002038000009000000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000000600000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000000610000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000012900000ffff3f00,8BitDo SN30 Gamepad,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000062280000ffff3f00,8BitDo SN30 Gamepad,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000002600000ffff0f00,8BitDo SN30 Pro+,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000001600000ffff3f00,8BitDo SN30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000002028000009000000ffff3f00,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000003512000020ab000000780f00,8BitDo SNES30 Gamepad,a:b21,b:b20,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b24,y:b23,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000018900000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000c82d000030320000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000d6020000e5890000dfff3f80,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a3,rightx:a4,righty:a5,start:b6,x:b2,y:b3,", + "0500000031366332860c44aadfff0f00,GS Gamepad,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "05000000bc20000000550000ffff3f00,GameSir G3w,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005509000003720000cf7f3f00,NVIDIA Controller v01.01,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005509000010720000ffff3f00,NVIDIA Controller v01.03,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005509000014720000df7f3f80,NVIDIA Controller v01.04,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a3,rightx:a4,righty:a5,start:b6,x:b2,y:b3,", + "050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b2,y:b3,sdk>=:29,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b2,y:b17,sdk<=:28,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", /* Extremely slow in Bluetooth mode on Android */ + "050000004c05000068020000dfff3f00,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000004c050000c405000000783f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000004c050000c4050000fffe3f80,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a3,rightx:a4,righty:a5,start:b16,x:b0,y:b2,", + "050000004c050000c4050000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000004c050000cc090000fffe3f80,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a3,rightx:a4,righty:a5,start:b16,x:b0,y:b2,", + "050000004c050000cc090000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000004c050000e60c0000fffe3f80,PS5 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a3,rightx:a4,righty:a5,start:b16,x:b2,y:b17,", + "050000004c050000e60c0000ffff3f00,PS5 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "05000000f8270000bf0b0000ffff3f00,Razer Kishi,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000003215000005070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000003215000007070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000003215000000090000bf7f3f00,Razer Serval,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,", + "050000004f0400000ed00000fffe3f00,ThrustMaster eSwap PRO Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005e0400008e02000000783f00,Xbox 360 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005e040000000b000000783f80,Xbox One Elite 2 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005e040000050b0000ffff3f00,Xbox One Elite 2 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005e040000e002000000783f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005e040000ea02000000783f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005e040000fd020000ff7f3f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005e040000e00200000ffe3f80,Xbox One Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a2,righty:a3,start:b10,x:b17,y:b2,", + "050000005e040000fd020000ffff3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005e040000120b000000783f80,Xbox Series X Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005e040000130b0000ffff3f00,Xbox Series X Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", + "050000005e04000091020000ff073f80,Xbox Wireless Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", /* The DPAD doesn't seem to work on this controller on Android TV? */ + "050000001727000044310000ffff3f80,XiaoMi Game Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a4,righty:a5,start:b6,x:b2,y:b3,", + "0500000083050000602000000ffe0000,iBuffalo SNES Controller,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b15,rightshoulder:b16,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", +#endif +#ifdef SDL_JOYSTICK_MFI + "05000000ac050000010000004f066d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,", + "05000000ac05000001000000cf076d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,", + "05000000ac05000001000000df076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", + "05000000ac05000001000000ff076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", + "05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,", + "05000000ac05000004000000a8986d04,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,lefttrigger:b12,rightshoulder:b13,righttrigger:b14,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac050000040000003b8a6d04,8BitDo SN30 Pro+,crc:3e00,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000008a35000003010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", + "050000008a35000004010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", + "050000007e050000062000000f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e050000062000004f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e05000008200000df070000,Nintendo Switch Joy-Con (L/R),a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e050000072000000f060000,Nintendo Switch Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e050000072000004f060000,Nintendo Switch Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e05000009200000df870000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b10,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000007e05000009200000ff870000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000004c050000cc090000df070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", + "050000004c050000cc090000df870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", + "050000004c050000cc090000ff070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", + "050000004c050000cc090000ff870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,", + "050000004c050000e60c0000df870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,touchpad:b10,x:b2,y:b3,", + "050000004c050000e60c0000ff870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,", + "05000000ac0500000300000043006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,", + "050000005e040000050b0000df070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b10,paddle2:b12,paddle3:b11,paddle4:b13,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", + "050000005e040000050b0000ff070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", + "050000005e040000130b0000df870001,Xbox Series X Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b10,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", + "050000005e040000130b0000ff870001,Xbox Series X Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", + "050000005e040000e0020000df070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", + "050000005e040000e0020000ff070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", +#endif +#ifdef SDL_JOYSTICK_EMSCRIPTEN + "default,Standard Gamepad,a:b0,b:b1,back:b8,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b16,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", +#endif +#ifdef SDL_JOYSTICK_PS2 + "0000000050533220436f6e74726f6c00,PS2 Controller,crc:ed87,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", +#endif +#ifdef SDL_JOYSTICK_PSP + "00000000505350206275696c74696e00,PSP builtin joypad,crc:bb86,a:b2,b:b1,back:b10,dpdown:b6,dpleft:b7,dpright:b9,dpup:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", +#endif +#ifdef SDL_JOYSTICK_VITA + "0000000050535669746120436f6e7400,PSVita Controller,crc:d598,a:b2,b:b1,back:b10,dpdown:b6,dpleft:b7,dpright:b9,dpup:b8,leftshoulder:b4,leftstick:b14,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", +#endif +#ifdef SDL_JOYSTICK_N3DS + "000000004e696e74656e646f20334400,Nintendo 3DS,crc:3210,a:b0,b:b1,back:b2,dpdown:b7,dpleft:b5,dpright:b4,dpup:b6,leftshoulder:b9,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b8,righttrigger:b15,rightx:a2,righty:a3,start:b3,x:b10,y:b11,", +#endif + NULL +}; diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_joystick.c b/contrib/SDL-3.2.8/src/joystick/SDL_joystick.c new file mode 100644 index 0000000..7574adc --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/SDL_joystick.c @@ -0,0 +1,3675 @@ +/* + 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" + +// This is the joystick API for Simple DirectMedia Layer + +#include "SDL_sysjoystick.h" +#include "../SDL_hints_c.h" +#include "SDL_gamepad_c.h" +#include "SDL_joystick_c.h" +#include "SDL_steam_virtual_gamepad.h" + +#include "../events/SDL_events_c.h" +#include "../video/SDL_sysvideo.h" +#include "../sensor/SDL_sensor_c.h" +#include "hidapi/SDL_hidapijoystick_c.h" + +// This is included in only one place because it has a large static list of controllers +#include "controller_type.h" + +#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) +// Needed for checking for input remapping programs +#include "../core/windows/SDL_windows.h" + +#undef UNICODE // We want ASCII functions +#include +#endif + +#ifdef SDL_JOYSTICK_VIRTUAL +#include "./virtual/SDL_virtualjoystick_c.h" +#endif + +static SDL_JoystickDriver *SDL_joystick_drivers[] = { +#ifdef SDL_JOYSTICK_HIDAPI // Highest priority driver for supported devices + &SDL_HIDAPI_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_PRIVATE + &SDL_PRIVATE_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_GAMEINPUT // Higher priority than other Windows drivers + &SDL_GAMEINPUT_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_RAWINPUT // Before WINDOWS driver, as WINDOWS wants to check if this driver is handling things + &SDL_RAWINPUT_JoystickDriver, +#endif +#if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT) // Before WGI driver, as WGI wants to check if this driver is handling things + &SDL_WINDOWS_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_WGI + &SDL_WGI_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_WINMM + &SDL_WINMM_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_LINUX + &SDL_LINUX_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_IOKIT + &SDL_DARWIN_JoystickDriver, +#endif +#if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)) && !defined(SDL_JOYSTICK_DISABLED) + &SDL_IOS_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_ANDROID + &SDL_ANDROID_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_EMSCRIPTEN + &SDL_EMSCRIPTEN_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_HAIKU + &SDL_HAIKU_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_USBHID /* !!! FIXME: "USBHID" is a generic name, and doubly-confusing with HIDAPI next to it. This is the *BSD interface, rename this. */ + &SDL_BSD_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_PS2 + &SDL_PS2_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_PSP + &SDL_PSP_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_VIRTUAL + &SDL_VIRTUAL_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_VITA + &SDL_VITA_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_N3DS + &SDL_N3DS_JoystickDriver, +#endif +#if defined(SDL_JOYSTICK_DUMMY) || defined(SDL_JOYSTICK_DISABLED) + &SDL_DUMMY_JoystickDriver +#endif +}; + +#ifndef SDL_THREAD_SAFETY_ANALYSIS +static +#endif +SDL_Mutex *SDL_joystick_lock = NULL; // This needs to support recursive locks +static SDL_AtomicInt SDL_joystick_lock_pending; +static int SDL_joysticks_locked; +static bool SDL_joysticks_initialized; +static bool SDL_joysticks_quitting; +static bool SDL_joystick_being_added; +static SDL_Joystick *SDL_joysticks SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static int SDL_joystick_player_count SDL_GUARDED_BY(SDL_joystick_lock) = 0; +static SDL_JoystickID *SDL_joystick_players SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static bool SDL_joystick_allows_background_events = false; + +static Uint32 initial_arcadestick_devices[] = { + MAKE_VIDPID(0x0079, 0x181a), // Venom Arcade Stick + MAKE_VIDPID(0x0079, 0x181b), // Venom Arcade Stick + MAKE_VIDPID(0x0c12, 0x0ef6), // Hitbox Arcade Stick + MAKE_VIDPID(0x0e6f, 0x0109), // PDP Versus Fighting Pad + MAKE_VIDPID(0x0f0d, 0x0016), // Hori Real Arcade Pro.EX + MAKE_VIDPID(0x0f0d, 0x001b), // Hori Real Arcade Pro VX + MAKE_VIDPID(0x0f0d, 0x0063), // Hori Real Arcade Pro Hayabusa (USA) Xbox One + MAKE_VIDPID(0x0f0d, 0x006a), // Real Arcade Pro 4 + MAKE_VIDPID(0x0f0d, 0x0078), // Hori Real Arcade Pro V Kai Xbox One + MAKE_VIDPID(0x0f0d, 0x008a), // HORI Real Arcade Pro 4 + MAKE_VIDPID(0x0f0d, 0x008c), // Hori Real Arcade Pro 4 + MAKE_VIDPID(0x0f0d, 0x00aa), // HORI Real Arcade Pro V Hayabusa in Switch Mode + MAKE_VIDPID(0x0f0d, 0x00ed), // Hori Fighting Stick mini 4 kai + MAKE_VIDPID(0x0f0d, 0x011c), // Hori Fighting Stick Alpha in PS4 Mode + MAKE_VIDPID(0x0f0d, 0x011e), // Hori Fighting Stick Alpha in PC Mode + MAKE_VIDPID(0x0f0d, 0x0184), // Hori Fighting Stick Alpha in PS5 Mode + MAKE_VIDPID(0x146b, 0x0604), // NACON Daija Arcade Stick + MAKE_VIDPID(0x1532, 0x0a00), // Razer Atrox Arcade Stick + MAKE_VIDPID(0x1bad, 0xf03d), // Street Fighter IV Arcade Stick TE - Chun Li + MAKE_VIDPID(0x1bad, 0xf502), // Hori Real Arcade Pro.VX SA + MAKE_VIDPID(0x1bad, 0xf504), // Hori Real Arcade Pro. EX + MAKE_VIDPID(0x1bad, 0xf506), // Hori Real Arcade Pro.EX Premium VLX + MAKE_VIDPID(0x20d6, 0xa715), // PowerA Nintendo Switch Fusion Arcade Stick + MAKE_VIDPID(0x24c6, 0x5000), // Razer Atrox Arcade Stick + MAKE_VIDPID(0x24c6, 0x5501), // Hori Real Arcade Pro VX-SA + MAKE_VIDPID(0x24c6, 0x550e), // Hori Real Arcade Pro V Kai 360 + MAKE_VIDPID(0x2c22, 0x2300), // Qanba Obsidian Arcade Joystick in PS4 Mode + MAKE_VIDPID(0x2c22, 0x2302), // Qanba Obsidian Arcade Joystick in PS3 Mode + MAKE_VIDPID(0x2c22, 0x2303), // Qanba Obsidian Arcade Joystick in PC Mode + MAKE_VIDPID(0x2c22, 0x2500), // Qanba Dragon Arcade Joystick in PS4 Mode + MAKE_VIDPID(0x2c22, 0x2502), // Qanba Dragon Arcade Joystick in PS3 Mode + MAKE_VIDPID(0x2c22, 0x2503), // Qanba Dragon Arcade Joystick in PC Mode +}; +static SDL_vidpid_list arcadestick_devices = { + SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES, 0, 0, NULL, + SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_arcadestick_devices), initial_arcadestick_devices, + false +}; + +/* This list is taken from: + https://raw.githubusercontent.com/denilsonsa/udev-joystick-blacklist/master/generate_rules.py + */ +static Uint32 initial_blacklist_devices[] = { + // Microsoft Microsoft Wireless Optical Desktop 2.10 + // Microsoft Wireless Desktop - Comfort Edition + MAKE_VIDPID(0x045e, 0x009d), + + // Microsoft Microsoft Digital Media Pro Keyboard + // Microsoft Corp. Digital Media Pro Keyboard + MAKE_VIDPID(0x045e, 0x00b0), + + // Microsoft Microsoft Digital Media Keyboard + // Microsoft Corp. Digital Media Keyboard 1.0A + MAKE_VIDPID(0x045e, 0x00b4), + + // Microsoft Microsoft Digital Media Keyboard 3000 + MAKE_VIDPID(0x045e, 0x0730), + + // Microsoft Microsoft 2.4GHz Transceiver v6.0 + // Microsoft Microsoft 2.4GHz Transceiver v8.0 + // Microsoft Corp. Nano Transceiver v1.0 for Bluetooth + // Microsoft Wireless Mobile Mouse 1000 + // Microsoft Wireless Desktop 3000 + MAKE_VIDPID(0x045e, 0x0745), + + // Microsoft SideWinder(TM) 2.4GHz Transceiver + MAKE_VIDPID(0x045e, 0x0748), + + // Microsoft Corp. Wired Keyboard 600 + MAKE_VIDPID(0x045e, 0x0750), + + // Microsoft Corp. Sidewinder X4 keyboard + MAKE_VIDPID(0x045e, 0x0768), + + // Microsoft Corp. Arc Touch Mouse Transceiver + MAKE_VIDPID(0x045e, 0x0773), + + // Microsoft 2.4GHz Transceiver v9.0 + // Microsoft Nano Transceiver v2.1 + // Microsoft Sculpt Ergonomic Keyboard (5KV-00001) + MAKE_VIDPID(0x045e, 0x07a5), + + // Microsoft Nano Transceiver v1.0 + // Microsoft Wireless Keyboard 800 + MAKE_VIDPID(0x045e, 0x07b2), + + // Microsoft Nano Transceiver v2.0 + MAKE_VIDPID(0x045e, 0x0800), + + MAKE_VIDPID(0x046d, 0xc30a), // Logitech, Inc. iTouch Composite keyboard + + MAKE_VIDPID(0x04d9, 0xa0df), // Tek Syndicate Mouse (E-Signal USB Gaming Mouse) + + // List of Wacom devices at: http://linuxwacom.sourceforge.net/wiki/index.php/Device_IDs + MAKE_VIDPID(0x056a, 0x0010), // Wacom ET-0405 Graphire + MAKE_VIDPID(0x056a, 0x0011), // Wacom ET-0405A Graphire2 (4x5) + MAKE_VIDPID(0x056a, 0x0012), // Wacom ET-0507A Graphire2 (5x7) + MAKE_VIDPID(0x056a, 0x0013), // Wacom CTE-430 Graphire3 (4x5) + MAKE_VIDPID(0x056a, 0x0014), // Wacom CTE-630 Graphire3 (6x8) + MAKE_VIDPID(0x056a, 0x0015), // Wacom CTE-440 Graphire4 (4x5) + MAKE_VIDPID(0x056a, 0x0016), // Wacom CTE-640 Graphire4 (6x8) + MAKE_VIDPID(0x056a, 0x0017), // Wacom CTE-450 Bamboo Fun (4x5) + MAKE_VIDPID(0x056a, 0x0018), // Wacom CTE-650 Bamboo Fun 6x8 + MAKE_VIDPID(0x056a, 0x0019), // Wacom CTE-631 Bamboo One + MAKE_VIDPID(0x056a, 0x00d1), // Wacom Bamboo Pen and Touch CTH-460 + MAKE_VIDPID(0x056a, 0x030e), // Wacom Intuos Pen (S) CTL-480 + + MAKE_VIDPID(0x09da, 0x054f), // A4 Tech Co., G7 750 mouse + MAKE_VIDPID(0x09da, 0x1410), // A4 Tech Co., Ltd Bloody AL9 mouse + MAKE_VIDPID(0x09da, 0x3043), // A4 Tech Co., Ltd Bloody R8A Gaming Mouse + MAKE_VIDPID(0x09da, 0x31b5), // A4 Tech Co., Ltd Bloody TL80 Terminator Laser Gaming Mouse + MAKE_VIDPID(0x09da, 0x3997), // A4 Tech Co., Ltd Bloody RT7 Terminator Wireless + MAKE_VIDPID(0x09da, 0x3f8b), // A4 Tech Co., Ltd Bloody V8 mouse + MAKE_VIDPID(0x09da, 0x51f4), // Modecom MC-5006 Keyboard + MAKE_VIDPID(0x09da, 0x5589), // A4 Tech Co., Ltd Terminator TL9 Laser Gaming Mouse + MAKE_VIDPID(0x09da, 0x7b22), // A4 Tech Co., Ltd Bloody V5 + MAKE_VIDPID(0x09da, 0x7f2d), // A4 Tech Co., Ltd Bloody R3 mouse + MAKE_VIDPID(0x09da, 0x8090), // A4 Tech Co., Ltd X-718BK Oscar Optical Gaming Mouse + MAKE_VIDPID(0x09da, 0x9033), // A4 Tech Co., X7 X-705K + MAKE_VIDPID(0x09da, 0x9066), // A4 Tech Co., Sharkoon Fireglider Optical + MAKE_VIDPID(0x09da, 0x9090), // A4 Tech Co., Ltd XL-730K / XL-750BK / XL-755BK Laser Mouse + MAKE_VIDPID(0x09da, 0x90c0), // A4 Tech Co., Ltd X7 G800V keyboard + MAKE_VIDPID(0x09da, 0xf012), // A4 Tech Co., Ltd Bloody V7 mouse + MAKE_VIDPID(0x09da, 0xf32a), // A4 Tech Co., Ltd Bloody B540 keyboard + MAKE_VIDPID(0x09da, 0xf613), // A4 Tech Co., Ltd Bloody V2 mouse + MAKE_VIDPID(0x09da, 0xf624), // A4 Tech Co., Ltd Bloody B120 Keyboard + + MAKE_VIDPID(0x1b1c, 0x1b3c), // Corsair Harpoon RGB gaming mouse + + MAKE_VIDPID(0x1d57, 0xad03), // [T3] 2.4GHz and IR Air Mouse Remote Control + + MAKE_VIDPID(0x1e7d, 0x2e4a), // Roccat Tyon Mouse + + MAKE_VIDPID(0x20a0, 0x422d), // Winkeyless.kr Keyboards + + MAKE_VIDPID(0x2516, 0x001f), // Cooler Master Storm Mizar Mouse + MAKE_VIDPID(0x2516, 0x0028), // Cooler Master Storm Alcor Mouse + + /*****************************************************************/ + // Additional entries + /*****************************************************************/ + + MAKE_VIDPID(0x04d9, 0x8008), // OBINLB USB-HID Keyboard (Anne Pro II) + MAKE_VIDPID(0x04d9, 0x8009), // OBINLB USB-HID Keyboard (Anne Pro II) + MAKE_VIDPID(0x04d9, 0xa292), // OBINLB USB-HID Keyboard (Anne Pro II) + MAKE_VIDPID(0x04d9, 0xa293), // OBINLB USB-HID Keyboard (Anne Pro II) + MAKE_VIDPID(0x1532, 0x0266), // Razer Huntsman V2 Analog, non-functional DInput device + MAKE_VIDPID(0x1532, 0x0282), // Razer Huntsman Mini Analog, non-functional DInput device + MAKE_VIDPID(0x26ce, 0x01a2), // ASRock LED Controller + MAKE_VIDPID(0x20d6, 0x0002), // PowerA Enhanced Wireless Controller for Nintendo Switch (charging port only) +}; +static SDL_vidpid_list blacklist_devices = { + SDL_HINT_JOYSTICK_BLACKLIST_DEVICES, 0, 0, NULL, + SDL_HINT_JOYSTICK_BLACKLIST_DEVICES_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_blacklist_devices), initial_blacklist_devices, + false +}; + +static Uint32 initial_flightstick_devices[] = { + MAKE_VIDPID(0x044f, 0x0402), // HOTAS Warthog Joystick + MAKE_VIDPID(0x044f, 0xb10a), // ThrustMaster, Inc. T.16000M Joystick + MAKE_VIDPID(0x046d, 0xc215), // Logitech Extreme 3D + MAKE_VIDPID(0x0738, 0x2221), // Saitek Pro Flight X-56 Rhino Stick + MAKE_VIDPID(0x231d, 0x0126), // Gunfighter Mk.III 'Space Combat Edition' (right) + MAKE_VIDPID(0x231d, 0x0127), // Gunfighter Mk.III 'Space Combat Edition' (left) + MAKE_VIDPID(0x362c, 0x0001), // Yawman Arrow +}; +static SDL_vidpid_list flightstick_devices = { + SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES, 0, 0, NULL, + SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_flightstick_devices), initial_flightstick_devices, + false +}; + +static Uint32 initial_gamecube_devices[] = { + MAKE_VIDPID(0x0e6f, 0x0185), // PDP Wired Fight Pad Pro for Nintendo Switch + MAKE_VIDPID(0x20d6, 0xa711), // PowerA Wired Controller Nintendo GameCube Style +}; +static SDL_vidpid_list gamecube_devices = { + SDL_HINT_JOYSTICK_GAMECUBE_DEVICES, 0, 0, NULL, + SDL_HINT_JOYSTICK_GAMECUBE_DEVICES_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_gamecube_devices), initial_gamecube_devices, + false +}; + +static Uint32 initial_rog_gamepad_mice[] = { + MAKE_VIDPID(0x0b05, 0x18e3), // ROG Chakram (wired) Mouse + MAKE_VIDPID(0x0b05, 0x18e5), // ROG Chakram (wireless) Mouse + MAKE_VIDPID(0x0b05, 0x1906), // ROG Pugio II + MAKE_VIDPID(0x0b05, 0x1958), // ROG Chakram Core Mouse + MAKE_VIDPID(0x0b05, 0x1a18), // ROG Chakram X (wired) Mouse + MAKE_VIDPID(0x0b05, 0x1a1a), // ROG Chakram X (wireless) Mouse + MAKE_VIDPID(0x0b05, 0x1a1c), // ROG Chakram X (Bluetooth) Mouse +}; +static SDL_vidpid_list rog_gamepad_mice = { + SDL_HINT_ROG_GAMEPAD_MICE, 0, 0, NULL, + SDL_HINT_ROG_GAMEPAD_MICE_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_rog_gamepad_mice), initial_rog_gamepad_mice, + false +}; + +static Uint32 initial_throttle_devices[] = { + MAKE_VIDPID(0x044f, 0x0404), // HOTAS Warthog Throttle + MAKE_VIDPID(0x0738, 0xa221), // Saitek Pro Flight X-56 Rhino Throttle +}; +static SDL_vidpid_list throttle_devices = { + SDL_HINT_JOYSTICK_THROTTLE_DEVICES, 0, 0, NULL, + SDL_HINT_JOYSTICK_THROTTLE_DEVICES_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_throttle_devices), initial_throttle_devices, + false +}; + +static Uint32 initial_wheel_devices[] = { + MAKE_VIDPID(0x0079, 0x1864), // DragonRise Inc. Wired Wheel (active mode) (also known as PXN V900 (PS3), Superdrive SV-750, or a Genesis Seaborg 400) + MAKE_VIDPID(0x044f, 0xb65d), // Thrustmaster Wheel FFB + MAKE_VIDPID(0x044f, 0xb65e), // Thrustmaster T500RS + MAKE_VIDPID(0x044f, 0xb664), // Thrustmaster TX (initial mode) + MAKE_VIDPID(0x044f, 0xb669), // Thrustmaster TX (active mode) + MAKE_VIDPID(0x044f, 0xb66d), // Thrustmaster T300RS (PS4 mode) + MAKE_VIDPID(0x044f, 0xb66d), // Thrustmaster Wheel FFB + MAKE_VIDPID(0x044f, 0xb66e), // Thrustmaster T300RS (normal mode) + MAKE_VIDPID(0x044f, 0xb66f), // Thrustmaster T300RS (advanced mode) + MAKE_VIDPID(0x044f, 0xb677), // Thrustmaster T150 + MAKE_VIDPID(0x044f, 0xb67f), // Thrustmaster TMX + MAKE_VIDPID(0x044f, 0xb691), // Thrustmaster TS-XW (initial mode) + MAKE_VIDPID(0x044f, 0xb692), // Thrustmaster TS-XW (active mode) + MAKE_VIDPID(0x044f, 0xb696), // Thrustmaster T248 + MAKE_VIDPID(0x046d, 0xc24f), // Logitech G29 (PS3) + MAKE_VIDPID(0x046d, 0xc260), // Logitech G29 (PS4) + MAKE_VIDPID(0x046d, 0xc261), // Logitech G920 (initial mode) + MAKE_VIDPID(0x046d, 0xc262), // Logitech G920 (active mode) + MAKE_VIDPID(0x046d, 0xc266), // Logitech G923 for Playstation 4 and PC (PC mode) + MAKE_VIDPID(0x046d, 0xc267), // Logitech G923 for Playstation 4 and PC (PS4 mode) + MAKE_VIDPID(0x046d, 0xc268), // Logitech PRO Racing Wheel (PC mode) + MAKE_VIDPID(0x046d, 0xc269), // Logitech PRO Racing Wheel (PS4/PS5 mode) + MAKE_VIDPID(0x046d, 0xc26d), // Logitech G923 (Xbox) + MAKE_VIDPID(0x046d, 0xc26e), // Logitech G923 + MAKE_VIDPID(0x046d, 0xc272), // Logitech PRO Racing Wheel for Xbox (PC mode) + MAKE_VIDPID(0x046d, 0xc294), // Logitech generic wheel + MAKE_VIDPID(0x046d, 0xc295), // Logitech Momo Force + MAKE_VIDPID(0x046d, 0xc298), // Logitech Driving Force Pro + MAKE_VIDPID(0x046d, 0xc299), // Logitech G25 + MAKE_VIDPID(0x046d, 0xc29a), // Logitech Driving Force GT + MAKE_VIDPID(0x046d, 0xc29b), // Logitech G27 + MAKE_VIDPID(0x046d, 0xca03), // Logitech Momo Racing + MAKE_VIDPID(0x0483, 0x0522), // Simagic Wheelbase (including M10, Alpha Mini, Alpha, Alpha U) + MAKE_VIDPID(0x0483, 0xa355), // VRS DirectForce Pro Wheel Base + MAKE_VIDPID(0x0eb7, 0x0001), // Fanatec ClubSport Wheel Base V2 + MAKE_VIDPID(0x0eb7, 0x0004), // Fanatec ClubSport Wheel Base V2.5 + MAKE_VIDPID(0x0eb7, 0x0005), // Fanatec CSL Elite Wheel Base+ (PS4) + MAKE_VIDPID(0x0eb7, 0x0006), // Fanatec Podium Wheel Base DD1 + MAKE_VIDPID(0x0eb7, 0x0007), // Fanatec Podium Wheel Base DD2 + MAKE_VIDPID(0x0eb7, 0x0011), // Fanatec Forza Motorsport (CSR Wheel / CSR Elite Wheel) + MAKE_VIDPID(0x0eb7, 0x0020), // Fanatec generic wheel / CSL DD / GT DD Pro + MAKE_VIDPID(0x0eb7, 0x0197), // Fanatec Porsche Wheel (Turbo / GT3 RS / Turbo S / GT3 V2 / GT2) + MAKE_VIDPID(0x0eb7, 0x038e), // Fanatec ClubSport Wheel Base V1 + MAKE_VIDPID(0x0eb7, 0x0e03), // Fanatec CSL Elite Wheel Base + MAKE_VIDPID(0x11ff, 0x0511), // DragonRise Inc. Wired Wheel (initial mode) (also known as PXN V900 (PS3), Superdrive SV-750, or a Genesis Seaborg 400) + MAKE_VIDPID(0x1209, 0xffb0), // Generic FFBoard OpenFFBoard universal forcefeedback wheel + MAKE_VIDPID(0x16d0, 0x0d5a), // Simucube 1 Wheelbase + MAKE_VIDPID(0x16d0, 0x0d5f), // Simucube 2 Ultimate Wheelbase + MAKE_VIDPID(0x16d0, 0x0d60), // Simucube 2 Pro Wheelbase + MAKE_VIDPID(0x16d0, 0x0d61), // Simucube 2 Sport Wheelbase + MAKE_VIDPID(0x2433, 0xf300), // Asetek SimSports Invicta Wheelbase + MAKE_VIDPID(0x2433, 0xf301), // Asetek SimSports Forte Wheelbase + MAKE_VIDPID(0x2433, 0xf303), // Asetek SimSports La Prima Wheelbase + MAKE_VIDPID(0x2433, 0xf306), // Asetek SimSports Tony Kannan Wheelbase + MAKE_VIDPID(0x3416, 0x0301), // Cammus C5 Wheelbase + MAKE_VIDPID(0x3416, 0x0302), // Cammus C12 Wheelbase + MAKE_VIDPID(0x346e, 0x0000), // Moza R16/R21 Wheelbase + MAKE_VIDPID(0x346e, 0x0002), // Moza R9 Wheelbase + MAKE_VIDPID(0x346e, 0x0004), // Moza R5 Wheelbase + MAKE_VIDPID(0x346e, 0x0005), // Moza R3 Wheelbase + MAKE_VIDPID(0x346e, 0x0006), // Moza R12 Wheelbase +}; +static SDL_vidpid_list wheel_devices = { + SDL_HINT_JOYSTICK_WHEEL_DEVICES, 0, 0, NULL, + SDL_HINT_JOYSTICK_WHEEL_DEVICES_EXCLUDED, 0, 0, NULL, + SDL_arraysize(initial_wheel_devices), initial_wheel_devices, + false +}; + +static Uint32 initial_zero_centered_devices[] = { + MAKE_VIDPID(0x05a0, 0x3232), // 8Bitdo Zero Gamepad + MAKE_VIDPID(0x0e8f, 0x3013), // HuiJia SNES USB adapter +}; +static SDL_vidpid_list zero_centered_devices = { + SDL_HINT_JOYSTICK_ZERO_CENTERED_DEVICES, 0, 0, NULL, + NULL, 0, 0, NULL, + SDL_arraysize(initial_zero_centered_devices), initial_zero_centered_devices, + false +}; + +#define CHECK_JOYSTICK_MAGIC(joystick, result) \ + if (!SDL_ObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK)) { \ + SDL_InvalidParamError("joystick"); \ + SDL_UnlockJoysticks(); \ + return result; \ + } + +bool SDL_JoysticksInitialized(void) +{ + return SDL_joysticks_initialized; +} + +bool SDL_JoysticksQuitting(void) +{ + return SDL_joysticks_quitting; +} + +void SDL_LockJoysticks(void) +{ + (void)SDL_AtomicIncRef(&SDL_joystick_lock_pending); + SDL_LockMutex(SDL_joystick_lock); + (void)SDL_AtomicDecRef(&SDL_joystick_lock_pending); + + ++SDL_joysticks_locked; +} + +void SDL_UnlockJoysticks(void) +{ + bool last_unlock = false; + + --SDL_joysticks_locked; + + if (!SDL_joysticks_initialized) { + // NOTE: There's a small window here where another thread could lock the mutex after we've checked for pending locks + if (!SDL_joysticks_locked && SDL_GetAtomicInt(&SDL_joystick_lock_pending) == 0) { + last_unlock = true; + } + } + + /* The last unlock after joysticks are uninitialized will cleanup the mutex, + * allowing applications to lock joysticks while reinitializing the system. + */ + if (last_unlock) { + SDL_Mutex *joystick_lock = SDL_joystick_lock; + + SDL_LockMutex(joystick_lock); + { + SDL_UnlockMutex(SDL_joystick_lock); + + SDL_joystick_lock = NULL; + } + SDL_UnlockMutex(joystick_lock); + SDL_DestroyMutex(joystick_lock); + } else { + SDL_UnlockMutex(SDL_joystick_lock); + } +} + +bool SDL_JoysticksLocked(void) +{ + return (SDL_joysticks_locked > 0); +} + +void SDL_AssertJoysticksLocked(void) +{ + SDL_assert(SDL_JoysticksLocked()); +} + +/* + * Get the driver and device index for a joystick instance ID + * This should be called while the joystick lock is held, to prevent another thread from updating the list + */ +static bool SDL_GetDriverAndJoystickIndex(SDL_JoystickID instance_id, SDL_JoystickDriver **driver, int *driver_index) +{ + int i, num_joysticks, device_index; + + SDL_AssertJoysticksLocked(); + + if (instance_id > 0) { + for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) { + num_joysticks = SDL_joystick_drivers[i]->GetCount(); + for (device_index = 0; device_index < num_joysticks; ++device_index) { + SDL_JoystickID joystick_id = SDL_joystick_drivers[i]->GetDeviceInstanceID(device_index); + if (joystick_id == instance_id) { + *driver = SDL_joystick_drivers[i]; + *driver_index = device_index; + return true; + } + } + } + } + + SDL_SetError("Joystick %" SDL_PRIu32 " not found", instance_id); + return false; +} + +static int SDL_FindFreePlayerIndex(void) +{ + int player_index; + + SDL_AssertJoysticksLocked(); + + for (player_index = 0; player_index < SDL_joystick_player_count; ++player_index) { + if (SDL_joystick_players[player_index] == 0) { + break; + } + } + return player_index; +} + +static int SDL_GetPlayerIndexForJoystickID(SDL_JoystickID instance_id) +{ + int player_index; + + SDL_AssertJoysticksLocked(); + + for (player_index = 0; player_index < SDL_joystick_player_count; ++player_index) { + if (instance_id == SDL_joystick_players[player_index]) { + break; + } + } + if (player_index == SDL_joystick_player_count) { + player_index = -1; + } + return player_index; +} + +static SDL_JoystickID SDL_GetJoystickIDForPlayerIndex(int player_index) +{ + SDL_AssertJoysticksLocked(); + + if (player_index < 0 || player_index >= SDL_joystick_player_count) { + return 0; + } + return SDL_joystick_players[player_index]; +} + +static bool SDL_SetJoystickIDForPlayerIndex(int player_index, SDL_JoystickID instance_id) +{ + SDL_JoystickID existing_instance = SDL_GetJoystickIDForPlayerIndex(player_index); + SDL_JoystickDriver *driver; + int device_index; + int existing_player_index; + + SDL_AssertJoysticksLocked(); + + if (player_index >= SDL_joystick_player_count) { + SDL_JoystickID *new_players = (SDL_JoystickID *)SDL_realloc(SDL_joystick_players, (player_index + 1) * sizeof(*SDL_joystick_players)); + if (!new_players) { + return false; + } + + SDL_joystick_players = new_players; + SDL_memset(&SDL_joystick_players[SDL_joystick_player_count], 0, (player_index - SDL_joystick_player_count + 1) * sizeof(SDL_joystick_players[0])); + SDL_joystick_player_count = player_index + 1; + } else if (player_index >= 0 && SDL_joystick_players[player_index] == instance_id) { + // Joystick is already assigned the requested player index + return true; + } + + // Clear the old player index + existing_player_index = SDL_GetPlayerIndexForJoystickID(instance_id); + if (existing_player_index >= 0) { + SDL_joystick_players[existing_player_index] = 0; + } + + if (player_index >= 0) { + SDL_joystick_players[player_index] = instance_id; + } + + // Update the driver with the new index + if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { + driver->SetDevicePlayerIndex(device_index, player_index); + } + + // Move any existing joystick to another slot + if (existing_instance > 0) { + SDL_SetJoystickIDForPlayerIndex(SDL_FindFreePlayerIndex(), existing_instance); + } + return true; +} + +static void SDLCALL SDL_JoystickAllowBackgroundEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + if (SDL_GetStringBoolean(hint, false)) { + SDL_joystick_allows_background_events = true; + } else { + SDL_joystick_allows_background_events = false; + } +} + +bool SDL_InitJoysticks(void) +{ + int i; + bool result = false; + + // Create the joystick list lock + if (SDL_joystick_lock == NULL) { + SDL_joystick_lock = SDL_CreateMutex(); + } + + if (!SDL_InitSubSystem(SDL_INIT_EVENTS)) { + return false; + } + + SDL_LockJoysticks(); + + SDL_joysticks_initialized = true; + + SDL_InitGamepadMappings(); + + SDL_LoadVIDPIDList(&arcadestick_devices); + SDL_LoadVIDPIDList(&blacklist_devices); + SDL_LoadVIDPIDList(&flightstick_devices); + SDL_LoadVIDPIDList(&gamecube_devices); + SDL_LoadVIDPIDList(&rog_gamepad_mice); + SDL_LoadVIDPIDList(&throttle_devices); + SDL_LoadVIDPIDList(&wheel_devices); + SDL_LoadVIDPIDList(&zero_centered_devices); + + // See if we should allow joystick events while in the background + SDL_AddHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, + SDL_JoystickAllowBackgroundEventsChanged, NULL); + + SDL_InitSteamVirtualGamepadInfo(); + + for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) { + if (SDL_joystick_drivers[i]->Init()) { + result = true; + } + } + SDL_UnlockJoysticks(); + + if (!result) { + SDL_QuitJoysticks(); + } + + return result; +} + +bool SDL_JoysticksOpened(void) +{ + bool opened; + + SDL_LockJoysticks(); + { + if (SDL_joysticks != NULL) { + opened = true; + } else { + opened = false; + } + } + SDL_UnlockJoysticks(); + + return opened; +} + +bool SDL_JoystickHandledByAnotherDriver(struct SDL_JoystickDriver *driver, Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + int i; + bool result = false; + + SDL_LockJoysticks(); + { + for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) { + if (driver == SDL_joystick_drivers[i]) { + // Higher priority drivers do not have this device + break; + } + if (SDL_joystick_drivers[i]->IsDevicePresent(vendor_id, product_id, version, name)) { + result = true; + break; + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +bool SDL_HasJoystick(void) +{ + int i; + int total_joysticks = 0; + + SDL_LockJoysticks(); + { + for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) { + total_joysticks += SDL_joystick_drivers[i]->GetCount(); + } + } + SDL_UnlockJoysticks(); + + if (total_joysticks > 0) { + return true; + } + return false; +} + +SDL_JoystickID *SDL_GetJoysticks(int *count) +{ + int i, num_joysticks, device_index; + int joystick_index = 0, total_joysticks = 0; + SDL_JoystickID *joysticks; + + SDL_LockJoysticks(); + { + for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) { + total_joysticks += SDL_joystick_drivers[i]->GetCount(); + } + + joysticks = (SDL_JoystickID *)SDL_malloc((total_joysticks + 1) * sizeof(*joysticks)); + if (joysticks) { + if (count) { + *count = total_joysticks; + } + + for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) { + num_joysticks = SDL_joystick_drivers[i]->GetCount(); + for (device_index = 0; device_index < num_joysticks; ++device_index) { + SDL_assert(joystick_index < total_joysticks); + joysticks[joystick_index] = SDL_joystick_drivers[i]->GetDeviceInstanceID(device_index); + SDL_assert(joysticks[joystick_index] > 0); + ++joystick_index; + } + } + SDL_assert(joystick_index == total_joysticks); + joysticks[joystick_index] = 0; + } else { + if (count) { + *count = 0; + } + } + } + SDL_UnlockJoysticks(); + + return joysticks; +} + +const SDL_SteamVirtualGamepadInfo *SDL_GetJoystickVirtualGamepadInfoForID(SDL_JoystickID instance_id) +{ + SDL_JoystickDriver *driver; + int device_index; + const SDL_SteamVirtualGamepadInfo *info = NULL; + + if (SDL_SteamVirtualGamepadEnabled() && + SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { + info = SDL_GetSteamVirtualGamepadInfo(driver->GetDeviceSteamVirtualGamepadSlot(device_index)); + } + return info; +} + +/* + * Get the implementation dependent name of a joystick + */ +const char *SDL_GetJoystickNameForID(SDL_JoystickID instance_id) +{ + SDL_JoystickDriver *driver; + int device_index; + const char *name = NULL; + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id); + if (info) { + name = SDL_GetPersistentString(info->name); + } else if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { + name = SDL_GetPersistentString(driver->GetDeviceName(device_index)); + } + SDL_UnlockJoysticks(); + + return name; +} + +/* + * Get the implementation dependent path of a joystick + */ +const char *SDL_GetJoystickPathForID(SDL_JoystickID instance_id) +{ + SDL_JoystickDriver *driver; + int device_index; + const char *path = NULL; + + SDL_LockJoysticks(); + if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { + path = SDL_GetPersistentString(driver->GetDevicePath(device_index)); + } + SDL_UnlockJoysticks(); + + if (!path) { + SDL_Unsupported(); + } + return path; +} + +/* + * Get the player index of a joystick, or -1 if it's not available + */ +int SDL_GetJoystickPlayerIndexForID(SDL_JoystickID instance_id) +{ + int player_index; + + SDL_LockJoysticks(); + player_index = SDL_GetPlayerIndexForJoystickID(instance_id); + SDL_UnlockJoysticks(); + + return player_index; +} + +/* + * Return true if this joystick is known to have all axes centered at zero + * This isn't generally needed unless the joystick never generates an initial axis value near zero, + * e.g. it's emulating axes with digital buttons + */ +static bool SDL_JoystickAxesCenteredAtZero(SDL_Joystick *joystick) +{ + // printf("JOYSTICK '%s' VID/PID 0x%.4x/0x%.4x AXES: %d\n", joystick->name, vendor, product, joystick->naxes); + + if (joystick->naxes == 2) { + // Assume D-pad or thumbstick style axes are centered at 0 + return true; + } + + return SDL_VIDPIDInList(SDL_GetJoystickVendor(joystick), SDL_GetJoystickProduct(joystick), &zero_centered_devices); +} + +static bool IsROGAlly(SDL_Joystick *joystick) +{ + Uint16 vendor, product; + SDL_GUID guid = SDL_GetJoystickGUID(joystick); + + // The ROG Ally controller spoofs an Xbox 360 controller + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + if (vendor == USB_VENDOR_MICROSOFT && product == USB_PRODUCT_XBOX360_WIRED_CONTROLLER) { + // Check to see if this system has the expected sensors + bool has_ally_accel = false; + bool has_ally_gyro = false; + + if (SDL_InitSubSystem(SDL_INIT_SENSOR)) { + SDL_SensorID *sensors = SDL_GetSensors(NULL); + if (sensors) { + int i; + for (i = 0; sensors[i]; ++i) { + SDL_SensorID sensor = sensors[i]; + + if (!has_ally_accel && SDL_GetSensorTypeForID(sensor) == SDL_SENSOR_ACCEL) { + const char *sensor_name = SDL_GetSensorNameForID(sensor); + if (sensor_name && SDL_strcmp(sensor_name, "Sensor BMI320 Acc") == 0) { + has_ally_accel = true; + } + } + if (!has_ally_gyro && SDL_GetSensorTypeForID(sensor) == SDL_SENSOR_GYRO) { + const char *sensor_name = SDL_GetSensorNameForID(sensor); + if (sensor_name && SDL_strcmp(sensor_name, "Sensor BMI320 Gyr") == 0) { + has_ally_gyro = true; + } + } + } + SDL_free(sensors); + } + SDL_QuitSubSystem(SDL_INIT_SENSOR); + } + if (has_ally_accel && has_ally_gyro) { + return true; + } + } + return false; +} + +static bool ShouldAttemptSensorFusion(SDL_Joystick *joystick, bool *invert_sensors) +{ + SDL_AssertJoysticksLocked(); + + *invert_sensors = false; + + // The SDL controller sensor API is only available for gamepads (at the moment) + if (!SDL_IsGamepad(joystick->instance_id)) { + return false; + } + + // If the controller already has sensors, use those + if (joystick->nsensors > 0) { + return false; + } + + const char *hint = SDL_GetHint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION); + if (hint && *hint) { + if (*hint == '@' || SDL_strncmp(hint, "0x", 2) == 0) { + SDL_vidpid_list gamepads; + SDL_GUID guid; + Uint16 vendor, product; + bool enabled; + SDL_zero(gamepads); + + // See if the gamepad is in our list of devices to enable + guid = SDL_GetJoystickGUID(joystick); + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + SDL_LoadVIDPIDListFromHints(&gamepads, hint, NULL); + enabled = SDL_VIDPIDInList(vendor, product, &gamepads); + SDL_FreeVIDPIDList(&gamepads); + if (enabled) { + return true; + } + } else { + return SDL_GetStringBoolean(hint, false); + } + } + + // See if this is another known wraparound gamepad + if (joystick->name && + (SDL_strstr(joystick->name, "Backbone One") || + SDL_strstr(joystick->name, "Kishi"))) { + return true; + } + if (IsROGAlly(joystick)) { + /* I'm not sure if this is a Windows thing, or a quirk for ROG Ally, + * but we need to invert the sensor data on all axes. + */ + *invert_sensors = true; + return true; + } + return false; +} + +static void AttemptSensorFusion(SDL_Joystick *joystick, bool invert_sensors) +{ + SDL_SensorID *sensors; + unsigned int i, j; + + SDL_AssertJoysticksLocked(); + + if (!SDL_InitSubSystem(SDL_INIT_SENSOR)) { + return; + } + + sensors = SDL_GetSensors(NULL); + if (sensors) { + for (i = 0; sensors[i]; ++i) { + SDL_SensorID sensor = sensors[i]; + + if (!joystick->accel_sensor && SDL_GetSensorTypeForID(sensor) == SDL_SENSOR_ACCEL) { + // Increment the sensor subsystem reference count + SDL_InitSubSystem(SDL_INIT_SENSOR); + + joystick->accel_sensor = sensor; + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f); + } + if (!joystick->gyro_sensor && SDL_GetSensorTypeForID(sensor) == SDL_SENSOR_GYRO) { + // Increment the sensor subsystem reference count + SDL_InitSubSystem(SDL_INIT_SENSOR); + + joystick->gyro_sensor = sensor; + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f); + } + } + SDL_free(sensors); + } + SDL_QuitSubSystem(SDL_INIT_SENSOR); + + /* SDL defines sensor orientation for phones relative to the natural + orientation, and for gamepads relative to being held in front of you. + When a phone is being used as a gamepad, its orientation changes, + so adjust sensor axes to match. + */ + if (SDL_GetNaturalDisplayOrientation(SDL_GetPrimaryDisplay()) == SDL_ORIENTATION_LANDSCAPE) { + /* When a device in landscape orientation is laid flat, the axes change + orientation as follows: + -X to +X becomes -X to +X + -Y to +Y becomes +Z to -Z + -Z to +Z becomes -Y to +Y + */ + joystick->sensor_transform[0][0] = 1.0f; + joystick->sensor_transform[1][2] = 1.0f; + joystick->sensor_transform[2][1] = -1.0f; + } else { + /* When a device in portrait orientation is rotated left and laid flat, + the axes change orientation as follows: + -X to +X becomes +Z to -Z + -Y to +Y becomes +X to -X + -Z to +Z becomes -Y to +Y + */ + joystick->sensor_transform[0][1] = -1.0f; + joystick->sensor_transform[1][2] = 1.0f; + joystick->sensor_transform[2][0] = -1.0f; + } + + if (invert_sensors) { + for (i = 0; i < SDL_arraysize(joystick->sensor_transform); ++i) { + for (j = 0; j < SDL_arraysize(joystick->sensor_transform[i]); ++j) { + joystick->sensor_transform[i][j] *= -1.0f; + } + } + } +} + +static void CleanupSensorFusion(SDL_Joystick *joystick) +{ + SDL_AssertJoysticksLocked(); + + if (joystick->accel_sensor || joystick->gyro_sensor) { + if (joystick->accel_sensor) { + if (joystick->accel) { + SDL_CloseSensor(joystick->accel); + joystick->accel = NULL; + } + joystick->accel_sensor = 0; + + // Decrement the sensor subsystem reference count + SDL_QuitSubSystem(SDL_INIT_SENSOR); + } + if (joystick->gyro_sensor) { + if (joystick->gyro) { + SDL_CloseSensor(joystick->gyro); + joystick->gyro = NULL; + } + joystick->gyro_sensor = 0; + + // Decrement the sensor subsystem reference count + SDL_QuitSubSystem(SDL_INIT_SENSOR); + } + } +} + +/* + * Open a joystick for use - the index passed as an argument refers to + * the N'th joystick on the system. This index is the value which will + * identify this joystick in future joystick events. + * + * This function returns a joystick identifier, or NULL if an error occurred. + */ +SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id) +{ + SDL_JoystickDriver *driver; + int device_index; + SDL_Joystick *joystick; + SDL_Joystick *joysticklist; + const char *joystickname = NULL; + const char *joystickpath = NULL; + bool invert_sensors = false; + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + + if (!SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { + SDL_UnlockJoysticks(); + return NULL; + } + + joysticklist = SDL_joysticks; + /* If the joystick is already open, return it + * it is important that we have a single joystick for each instance id + */ + while (joysticklist) { + if (instance_id == joysticklist->instance_id) { + joystick = joysticklist; + ++joystick->ref_count; + SDL_UnlockJoysticks(); + return joystick; + } + joysticklist = joysticklist->next; + } + + // Create and initialize the joystick + joystick = (SDL_Joystick *)SDL_calloc(1, sizeof(*joystick)); + if (!joystick) { + SDL_UnlockJoysticks(); + return NULL; + } + SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, true); + joystick->driver = driver; + joystick->instance_id = instance_id; + joystick->attached = true; + joystick->led_expiration = SDL_GetTicks(); + joystick->battery_percent = -1; + + if (!driver->Open(joystick, device_index)) { + SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, false); + SDL_free(joystick); + SDL_UnlockJoysticks(); + return NULL; + } + + joystickname = driver->GetDeviceName(device_index); + if (joystickname) { + joystick->name = SDL_strdup(joystickname); + } + + joystickpath = driver->GetDevicePath(device_index); + if (joystickpath) { + joystick->path = SDL_strdup(joystickpath); + } + + joystick->guid = driver->GetDeviceGUID(device_index); + + if (joystick->naxes > 0) { + joystick->axes = (SDL_JoystickAxisInfo *)SDL_calloc(joystick->naxes, sizeof(*joystick->axes)); + } + if (joystick->nballs > 0) { + joystick->balls = (SDL_JoystickBallData *)SDL_calloc(joystick->nballs, sizeof(*joystick->balls)); + } + if (joystick->nhats > 0) { + joystick->hats = (Uint8 *)SDL_calloc(joystick->nhats, sizeof(*joystick->hats)); + } + if (joystick->nbuttons > 0) { + joystick->buttons = (bool *)SDL_calloc(joystick->nbuttons, sizeof(*joystick->buttons)); + } + if (((joystick->naxes > 0) && !joystick->axes) || + ((joystick->nballs > 0) && !joystick->balls) || + ((joystick->nhats > 0) && !joystick->hats) || + ((joystick->nbuttons > 0) && !joystick->buttons)) { + SDL_CloseJoystick(joystick); + SDL_UnlockJoysticks(); + return NULL; + } + + // If this joystick is known to have all zero centered axes, skip the auto-centering code + if (SDL_JoystickAxesCenteredAtZero(joystick)) { + int i; + + for (i = 0; i < joystick->naxes; ++i) { + joystick->axes[i].has_initial_value = true; + } + } + + // Get the Steam Input API handle + info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id); + if (info) { + joystick->steam_handle = info->handle; + } + + // Use system gyro and accelerometer if the gamepad doesn't have built-in sensors + if (ShouldAttemptSensorFusion(joystick, &invert_sensors)) { + AttemptSensorFusion(joystick, invert_sensors); + } + + // Add joystick to list + ++joystick->ref_count; + // Link the joystick in the list + joystick->next = SDL_joysticks; + SDL_joysticks = joystick; + + driver->Update(joystick); + + SDL_UnlockJoysticks(); + + return joystick; +} + +SDL_JoystickID SDL_AttachVirtualJoystick(const SDL_VirtualJoystickDesc *desc) +{ +#ifdef SDL_JOYSTICK_VIRTUAL + SDL_JoystickID result; + + SDL_LockJoysticks(); + result = SDL_JoystickAttachVirtualInner(desc); + SDL_UnlockJoysticks(); + return result; +#else + SDL_SetError("SDL not built with virtual-joystick support"); + return 0; +#endif +} + +bool SDL_DetachVirtualJoystick(SDL_JoystickID instance_id) +{ +#ifdef SDL_JOYSTICK_VIRTUAL + bool result; + + SDL_LockJoysticks(); + result = SDL_JoystickDetachVirtualInner(instance_id); + SDL_UnlockJoysticks(); + return result; +#else + return SDL_SetError("SDL not built with virtual-joystick support"); +#endif +} + +bool SDL_IsJoystickVirtual(SDL_JoystickID instance_id) +{ +#ifdef SDL_JOYSTICK_VIRTUAL + SDL_JoystickDriver *driver; + int device_index; + bool is_virtual = false; + + SDL_LockJoysticks(); + if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { + if (driver == &SDL_VIRTUAL_JoystickDriver) { + is_virtual = true; + } + } + SDL_UnlockJoysticks(); + + return is_virtual; +#else + return false; +#endif +} + +bool SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + +#ifdef SDL_JOYSTICK_VIRTUAL + result = SDL_SetJoystickVirtualAxisInner(joystick, axis, value); +#else + result = SDL_SetError("SDL not built with virtual-joystick support"); +#endif + } + SDL_UnlockJoysticks(); + + return result; +} + +bool SDL_SetJoystickVirtualBall(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + +#ifdef SDL_JOYSTICK_VIRTUAL + result = SDL_SetJoystickVirtualBallInner(joystick, ball, xrel, yrel); +#else + result = SDL_SetError("SDL not built with virtual-joystick support"); +#endif + } + SDL_UnlockJoysticks(); + + return result; +} + +bool SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, int button, bool down) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + +#ifdef SDL_JOYSTICK_VIRTUAL + result = SDL_SetJoystickVirtualButtonInner(joystick, button, down); +#else + result = SDL_SetError("SDL not built with virtual-joystick support"); +#endif + } + SDL_UnlockJoysticks(); + + return result; +} + +bool SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + +#ifdef SDL_JOYSTICK_VIRTUAL + result = SDL_SetJoystickVirtualHatInner(joystick, hat, value); +#else + result = SDL_SetError("SDL not built with virtual-joystick support"); +#endif + } + SDL_UnlockJoysticks(); + + return result; +} + +bool SDL_SetJoystickVirtualTouchpad(SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + +#ifdef SDL_JOYSTICK_VIRTUAL + result = SDL_SetJoystickVirtualTouchpadInner(joystick, touchpad, finger, down, x, y, pressure); +#else + result = SDL_SetError("SDL not built with virtual-joystick support"); +#endif + } + SDL_UnlockJoysticks(); + + return result; +} + +bool SDL_SendJoystickVirtualSensorData(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + +#ifdef SDL_JOYSTICK_VIRTUAL + result = SDL_SendJoystickVirtualSensorDataInner(joystick, type, sensor_timestamp, data, num_values); +#else + result = SDL_SetError("SDL not built with virtual-joystick support"); +#endif + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Checks to make sure the joystick is valid. + */ +bool SDL_IsJoystickValid(SDL_Joystick *joystick) +{ + SDL_AssertJoysticksLocked(); + return SDL_ObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK); +} + +bool SDL_PrivateJoystickGetAutoGamepadMapping(SDL_JoystickID instance_id, SDL_GamepadMapping *out) +{ + SDL_JoystickDriver *driver; + int device_index; + bool is_ok = false; + + SDL_LockJoysticks(); + if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { + is_ok = driver->GetGamepadMapping(device_index, out); + } + SDL_UnlockJoysticks(); + + return is_ok; +} + +/* + * Get the number of multi-dimensional axis controls on a joystick + */ +int SDL_GetNumJoystickAxes(SDL_Joystick *joystick) +{ + int result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, -1); + + result = joystick->naxes; + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the number of hats on a joystick + */ +int SDL_GetNumJoystickHats(SDL_Joystick *joystick) +{ + int result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, -1); + + result = joystick->nhats; + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the number of trackballs on a joystick + */ +int SDL_GetNumJoystickBalls(SDL_Joystick *joystick) +{ + CHECK_JOYSTICK_MAGIC(joystick, -1); + + return joystick->nballs; +} + +/* + * Get the number of buttons on a joystick + */ +int SDL_GetNumJoystickButtons(SDL_Joystick *joystick) +{ + int result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, -1); + + result = joystick->nbuttons; + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the current state of an axis control on a joystick + */ +Sint16 SDL_GetJoystickAxis(SDL_Joystick *joystick, int axis) +{ + Sint16 state; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, 0); + + if (axis < joystick->naxes) { + state = joystick->axes[axis].value; + } else { + SDL_SetError("Joystick only has %d axes", joystick->naxes); + state = 0; + } + } + SDL_UnlockJoysticks(); + + return state; +} + +/* + * Get the initial state of an axis control on a joystick + */ +bool SDL_GetJoystickAxisInitialState(SDL_Joystick *joystick, int axis, Sint16 *state) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + + if (axis >= joystick->naxes) { + SDL_SetError("Joystick only has %d axes", joystick->naxes); + result = false; + } else { + if (state) { + *state = joystick->axes[axis].initial_value; + } + result = joystick->axes[axis].has_initial_value; + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the current state of a hat on a joystick + */ +Uint8 SDL_GetJoystickHat(SDL_Joystick *joystick, int hat) +{ + Uint8 state; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, 0); + + if (hat < joystick->nhats) { + state = joystick->hats[hat]; + } else { + SDL_SetError("Joystick only has %d hats", joystick->nhats); + state = 0; + } + } + SDL_UnlockJoysticks(); + + return state; +} + +/* + * Get the ball axis change since the last poll + */ +bool SDL_GetJoystickBall(SDL_Joystick *joystick, int ball, int *dx, int *dy) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + + if (ball < joystick->nballs) { + if (dx) { + *dx = joystick->balls[ball].dx; + } + if (dy) { + *dy = joystick->balls[ball].dy; + } + joystick->balls[ball].dx = 0; + joystick->balls[ball].dy = 0; + result = true; + } else { + result = SDL_SetError("Joystick only has %d balls", joystick->nballs); + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the current state of a button on a joystick + */ +bool SDL_GetJoystickButton(SDL_Joystick *joystick, int button) +{ + bool down = false; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + + if (button < joystick->nbuttons) { + down = joystick->buttons[button]; + } else { + SDL_SetError("Joystick only has %d buttons", joystick->nbuttons); + } + } + SDL_UnlockJoysticks(); + + return down; +} + +/* + * Return if the joystick in question is currently attached to the system, + * \return false if not plugged in, true if still present. + */ +bool SDL_JoystickConnected(SDL_Joystick *joystick) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + + result = joystick->attached; + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the instance id for this opened joystick + */ +SDL_JoystickID SDL_GetJoystickID(SDL_Joystick *joystick) +{ + SDL_JoystickID result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, 0); + + result = joystick->instance_id; + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Return the SDL_Joystick associated with an instance id. + */ +SDL_Joystick *SDL_GetJoystickFromID(SDL_JoystickID instance_id) +{ + SDL_Joystick *joystick; + + SDL_LockJoysticks(); + for (joystick = SDL_joysticks; joystick; joystick = joystick->next) { + if (joystick->instance_id == instance_id) { + break; + } + } + SDL_UnlockJoysticks(); + return joystick; +} + +/** + * Return the SDL_Joystick associated with a player index. + */ +SDL_Joystick *SDL_GetJoystickFromPlayerIndex(int player_index) +{ + SDL_JoystickID instance_id; + SDL_Joystick *joystick; + + SDL_LockJoysticks(); + instance_id = SDL_GetJoystickIDForPlayerIndex(player_index); + for (joystick = SDL_joysticks; joystick; joystick = joystick->next) { + if (joystick->instance_id == instance_id) { + break; + } + } + SDL_UnlockJoysticks(); + return joystick; +} + +/* + * Get the properties associated with a joystick + */ +SDL_PropertiesID SDL_GetJoystickProperties(SDL_Joystick *joystick) +{ + SDL_PropertiesID result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, 0); + + if (joystick->props == 0) { + joystick->props = SDL_CreateProperties(); + } + result = joystick->props; + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the friendly name of this joystick + */ +const char *SDL_GetJoystickName(SDL_Joystick *joystick) +{ + const char *result; + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, NULL); + + info = SDL_GetJoystickVirtualGamepadInfoForID(joystick->instance_id); + if (info) { + result = SDL_GetPersistentString(info->name); + } else { + result = SDL_GetPersistentString(joystick->name); + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Get the implementation dependent path of this joystick + */ +const char *SDL_GetJoystickPath(SDL_Joystick *joystick) +{ + const char *result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, NULL); + + if (joystick->path) { + result = SDL_GetPersistentString(joystick->path); + } else { + SDL_Unsupported(); + result = NULL; + } + } + SDL_UnlockJoysticks(); + + return result; +} + +/** + * Get the player index of an opened joystick, or -1 if it's not available + */ +int SDL_GetJoystickPlayerIndex(SDL_Joystick *joystick) +{ + int result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, -1); + + result = SDL_GetPlayerIndexForJoystickID(joystick->instance_id); + } + SDL_UnlockJoysticks(); + + return result; +} + +/** + * Set the player index of an opened joystick + */ +bool SDL_SetJoystickPlayerIndex(SDL_Joystick *joystick, int player_index) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + + result = SDL_SetJoystickIDForPlayerIndex(player_index, joystick->instance_id); + } + SDL_UnlockJoysticks(); + + return result; +} + +bool SDL_RumbleJoystick(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + + if (low_frequency_rumble == joystick->low_frequency_rumble && + high_frequency_rumble == joystick->high_frequency_rumble) { + // Just update the expiration + result = true; + } else { + result = joystick->driver->Rumble(joystick, low_frequency_rumble, high_frequency_rumble); + if (result) { + joystick->rumble_resend = SDL_GetTicks() + SDL_RUMBLE_RESEND_MS; + if (joystick->rumble_resend == 0) { + joystick->rumble_resend = 1; + } + } else { + joystick->rumble_resend = 0; + } + } + + if (result) { + joystick->low_frequency_rumble = low_frequency_rumble; + joystick->high_frequency_rumble = high_frequency_rumble; + + if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) { + joystick->rumble_expiration = SDL_GetTicks() + SDL_min(duration_ms, SDL_MAX_RUMBLE_DURATION_MS); + if (!joystick->rumble_expiration) { + joystick->rumble_expiration = 1; + } + } else { + joystick->rumble_expiration = 0; + joystick->rumble_resend = 0; + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +bool SDL_RumbleJoystickTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + + if (left_rumble == joystick->left_trigger_rumble && right_rumble == joystick->right_trigger_rumble) { + // Just update the expiration + result = true; + } else { + result = joystick->driver->RumbleTriggers(joystick, left_rumble, right_rumble); + } + + if (result) { + joystick->left_trigger_rumble = left_rumble; + joystick->right_trigger_rumble = right_rumble; + + if ((left_rumble || right_rumble) && duration_ms) { + joystick->trigger_rumble_expiration = SDL_GetTicks() + SDL_min(duration_ms, SDL_MAX_RUMBLE_DURATION_MS); + } else { + joystick->trigger_rumble_expiration = 0; + } + } + } + SDL_UnlockJoysticks(); + + return result; +} + +bool SDL_SetJoystickLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + bool result; + bool isfreshvalue; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + + isfreshvalue = red != joystick->led_red || + green != joystick->led_green || + blue != joystick->led_blue; + + if (isfreshvalue || SDL_GetTicks() >= joystick->led_expiration) { + result = joystick->driver->SetLED(joystick, red, green, blue); + joystick->led_expiration = SDL_GetTicks() + SDL_LED_MIN_REPEAT_MS; + } else { + // Avoid spamming the driver + result = true; + } + + // Save the LED value regardless of success, so we don't spam the driver + joystick->led_red = red; + joystick->led_green = green; + joystick->led_blue = blue; + } + SDL_UnlockJoysticks(); + + return result; +} + +bool SDL_SendJoystickEffect(SDL_Joystick *joystick, const void *data, int size) +{ + bool result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, false); + + result = joystick->driver->SendEffect(joystick, data, size); + } + SDL_UnlockJoysticks(); + + return result; +} + +/* + * Close a joystick previously opened with SDL_OpenJoystick() + */ +void SDL_CloseJoystick(SDL_Joystick *joystick) +{ + SDL_Joystick *joysticklist; + SDL_Joystick *joysticklistprev; + int i; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick,); + + // First decrement ref count + if (--joystick->ref_count > 0) { + SDL_UnlockJoysticks(); + return; + } + + SDL_DestroyProperties(joystick->props); + + if (joystick->rumble_expiration) { + SDL_RumbleJoystick(joystick, 0, 0, 0); + } + if (joystick->trigger_rumble_expiration) { + SDL_RumbleJoystickTriggers(joystick, 0, 0, 0); + } + + CleanupSensorFusion(joystick); + + joystick->driver->Close(joystick); + joystick->hwdata = NULL; + SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, false); + + joysticklist = SDL_joysticks; + joysticklistprev = NULL; + while (joysticklist) { + if (joystick == joysticklist) { + if (joysticklistprev) { + // unlink this entry + joysticklistprev->next = joysticklist->next; + } else { + SDL_joysticks = joystick->next; + } + break; + } + joysticklistprev = joysticklist; + joysticklist = joysticklist->next; + } + + // Free the data associated with this joystick + SDL_free(joystick->name); + SDL_free(joystick->path); + SDL_free(joystick->serial); + SDL_free(joystick->axes); + SDL_free(joystick->balls); + SDL_free(joystick->hats); + SDL_free(joystick->buttons); + for (i = 0; i < joystick->ntouchpads; i++) { + SDL_JoystickTouchpadInfo *touchpad = &joystick->touchpads[i]; + SDL_free(touchpad->fingers); + } + SDL_free(joystick->touchpads); + SDL_free(joystick->sensors); + SDL_free(joystick); + } + SDL_UnlockJoysticks(); +} + +void SDL_QuitJoysticks(void) +{ + int i; + SDL_JoystickID *joysticks; + + SDL_LockJoysticks(); + + SDL_joysticks_quitting = true; + + joysticks = SDL_GetJoysticks(NULL); + if (joysticks) { + for (i = 0; joysticks[i]; ++i) { + SDL_PrivateJoystickRemoved(joysticks[i]); + } + SDL_free(joysticks); + } + + while (SDL_joysticks) { + SDL_joysticks->ref_count = 1; + SDL_CloseJoystick(SDL_joysticks); + } + + // Quit drivers in reverse order to avoid breaking dependencies between drivers + for (i = SDL_arraysize(SDL_joystick_drivers) - 1; i >= 0; --i) { + SDL_joystick_drivers[i]->Quit(); + } + + if (SDL_joystick_players) { + SDL_free(SDL_joystick_players); + SDL_joystick_players = NULL; + SDL_joystick_player_count = 0; + } + + SDL_QuitSubSystem(SDL_INIT_EVENTS); + + SDL_QuitSteamVirtualGamepadInfo(); + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, + SDL_JoystickAllowBackgroundEventsChanged, NULL); + + SDL_FreeVIDPIDList(&arcadestick_devices); + SDL_FreeVIDPIDList(&blacklist_devices); + SDL_FreeVIDPIDList(&flightstick_devices); + SDL_FreeVIDPIDList(&gamecube_devices); + SDL_FreeVIDPIDList(&rog_gamepad_mice); + SDL_FreeVIDPIDList(&throttle_devices); + SDL_FreeVIDPIDList(&wheel_devices); + SDL_FreeVIDPIDList(&zero_centered_devices); + + SDL_QuitGamepadMappings(); + + SDL_joysticks_quitting = false; + SDL_joysticks_initialized = false; + + SDL_UnlockJoysticks(); +} + +static bool SDL_PrivateJoystickShouldIgnoreEvent(void) +{ + if (SDL_joystick_allows_background_events) { + return false; + } + + if (SDL_HasWindows() && SDL_GetKeyboardFocus() == NULL) { + // We have windows but we don't have focus, ignore the event. + return true; + } + return false; +} + +// These are global for SDL_sysjoystick.c and SDL_events.c + +void SDL_PrivateJoystickAddTouchpad(SDL_Joystick *joystick, int nfingers) +{ + int ntouchpads; + SDL_JoystickTouchpadInfo *touchpads; + + SDL_AssertJoysticksLocked(); + + ntouchpads = joystick->ntouchpads + 1; + touchpads = (SDL_JoystickTouchpadInfo *)SDL_realloc(joystick->touchpads, (ntouchpads * sizeof(SDL_JoystickTouchpadInfo))); + if (touchpads) { + SDL_JoystickTouchpadInfo *touchpad = &touchpads[ntouchpads - 1]; + SDL_JoystickTouchpadFingerInfo *fingers = (SDL_JoystickTouchpadFingerInfo *)SDL_calloc(nfingers, sizeof(SDL_JoystickTouchpadFingerInfo)); + + if (fingers) { + touchpad->nfingers = nfingers; + touchpad->fingers = fingers; + } else { + // Out of memory, this touchpad won't be active + touchpad->nfingers = 0; + touchpad->fingers = NULL; + } + + joystick->ntouchpads = ntouchpads; + joystick->touchpads = touchpads; + } +} + +void SDL_PrivateJoystickAddSensor(SDL_Joystick *joystick, SDL_SensorType type, float rate) +{ + int nsensors; + SDL_JoystickSensorInfo *sensors; + + SDL_AssertJoysticksLocked(); + + nsensors = joystick->nsensors + 1; + sensors = (SDL_JoystickSensorInfo *)SDL_realloc(joystick->sensors, (nsensors * sizeof(SDL_JoystickSensorInfo))); + if (sensors) { + SDL_JoystickSensorInfo *sensor = &sensors[nsensors - 1]; + + SDL_zerop(sensor); + sensor->type = type; + sensor->rate = rate; + + joystick->nsensors = nsensors; + joystick->sensors = sensors; + } +} + +void SDL_PrivateJoystickSensorRate(SDL_Joystick *joystick, SDL_SensorType type, float rate) +{ + int i; + SDL_AssertJoysticksLocked(); + + for (i = 0; i < joystick->nsensors; ++i) { + if (joystick->sensors[i].type == type) { + joystick->sensors[i].rate = rate; + } + } +} + +void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id) +{ + SDL_JoystickDriver *driver; + int device_index; + int player_index = -1; + + SDL_AssertJoysticksLocked(); + + if (SDL_JoysticksQuitting()) { + return; + } + + SDL_joystick_being_added = true; + + if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { + player_index = driver->GetDeviceSteamVirtualGamepadSlot(device_index); + if (player_index < 0) { + player_index = driver->GetDevicePlayerIndex(device_index); + } + } + if (player_index < 0 && SDL_IsGamepad(instance_id)) { + player_index = SDL_FindFreePlayerIndex(); + } + if (player_index >= 0) { + SDL_SetJoystickIDForPlayerIndex(player_index, instance_id); + } + + { + SDL_Event event; + + event.type = SDL_EVENT_JOYSTICK_ADDED; + event.common.timestamp = 0; + + if (SDL_EventEnabled(event.type)) { + event.jdevice.which = instance_id; + SDL_PushEvent(&event); + } + } + + SDL_joystick_being_added = false; + + if (SDL_IsGamepad(instance_id)) { + SDL_PrivateGamepadAdded(instance_id); + } +} + +bool SDL_IsJoystickBeingAdded(void) +{ + return SDL_joystick_being_added; +} + +void SDL_PrivateJoystickForceRecentering(SDL_Joystick *joystick) +{ + Uint8 i, j; + Uint64 timestamp = SDL_GetTicksNS(); + + SDL_AssertJoysticksLocked(); + + // Tell the app that everything is centered/unpressed... + for (i = 0; i < joystick->naxes; i++) { + if (joystick->axes[i].has_initial_value) { + SDL_SendJoystickAxis(timestamp, joystick, i, joystick->axes[i].zero); + } + } + + for (i = 0; i < joystick->nbuttons; i++) { + SDL_SendJoystickButton(timestamp, joystick, i, false); + } + + for (i = 0; i < joystick->nhats; i++) { + SDL_SendJoystickHat(timestamp, joystick, i, SDL_HAT_CENTERED); + } + + for (i = 0; i < joystick->ntouchpads; i++) { + SDL_JoystickTouchpadInfo *touchpad = &joystick->touchpads[i]; + + for (j = 0; j < touchpad->nfingers; ++j) { + SDL_SendJoystickTouchpad(timestamp, joystick, i, j, false, 0.0f, 0.0f, 0.0f); + } + } +} + +void SDL_PrivateJoystickRemoved(SDL_JoystickID instance_id) +{ + SDL_Joystick *joystick = NULL; + int player_index; + SDL_Event event; + + SDL_AssertJoysticksLocked(); + + // Find this joystick... + for (joystick = SDL_joysticks; joystick; joystick = joystick->next) { + if (joystick->instance_id == instance_id) { + SDL_PrivateJoystickForceRecentering(joystick); + joystick->attached = false; + break; + } + } + + if (SDL_IsGamepad(instance_id)) { + SDL_PrivateGamepadRemoved(instance_id); + } + + event.type = SDL_EVENT_JOYSTICK_REMOVED; + event.common.timestamp = 0; + + if (SDL_EventEnabled(event.type)) { + event.jdevice.which = instance_id; + SDL_PushEvent(&event); + } + + player_index = SDL_GetPlayerIndexForJoystickID(instance_id); + if (player_index >= 0) { + SDL_joystick_players[player_index] = 0; + } +} + +void SDL_SendJoystickAxis(Uint64 timestamp, SDL_Joystick *joystick, Uint8 axis, Sint16 value) +{ + SDL_JoystickAxisInfo *info; + + SDL_AssertJoysticksLocked(); + + // Make sure we're not getting garbage or duplicate events + if (axis >= joystick->naxes) { + return; + } + + info = &joystick->axes[axis]; + if (!info->has_initial_value || + (!info->has_second_value && (info->initial_value <= -32767 || info->initial_value == 32767) && SDL_abs(value) < (SDL_JOYSTICK_AXIS_MAX / 4))) { + info->initial_value = value; + info->value = value; + info->zero = value; + info->has_initial_value = true; + } else if (value == info->value && !info->sending_initial_value) { + return; + } else { + info->has_second_value = true; + } + if (!info->sent_initial_value) { + // Make sure we don't send motion until there's real activity on this axis + const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; // ShanWan PS3 controller needed 96 + if (SDL_abs(value - info->value) <= MAX_ALLOWED_JITTER && + !SDL_IsJoystickVIRTUAL(joystick->guid)) { + return; + } + info->sent_initial_value = true; + info->sending_initial_value = true; + SDL_SendJoystickAxis(timestamp, joystick, axis, info->initial_value); + info->sending_initial_value = false; + } + + /* We ignore events if we don't have keyboard focus, except for centering + * events. + */ + if (SDL_PrivateJoystickShouldIgnoreEvent()) { + if (info->sending_initial_value || + (value > info->zero && value >= info->value) || + (value < info->zero && value <= info->value)) { + return; + } + } + + // Update internal joystick state + SDL_assert(timestamp != 0); + info->value = value; + joystick->update_complete = timestamp; + + // Post the event, if desired + if (SDL_EventEnabled(SDL_EVENT_JOYSTICK_AXIS_MOTION)) { + SDL_Event event; + event.type = SDL_EVENT_JOYSTICK_AXIS_MOTION; + event.common.timestamp = timestamp; + event.jaxis.which = joystick->instance_id; + event.jaxis.axis = axis; + event.jaxis.value = value; + SDL_PushEvent(&event); + } +} + +void SDL_SendJoystickBall(Uint64 timestamp, SDL_Joystick *joystick, Uint8 ball, Sint16 xrel, Sint16 yrel) +{ + SDL_AssertJoysticksLocked(); + + // Make sure we're not getting garbage events + if (ball >= joystick->nballs) { + return; + } + + // We ignore events if we don't have keyboard focus. + if (SDL_PrivateJoystickShouldIgnoreEvent()) { + return; + } + + // Update internal mouse state + joystick->balls[ball].dx += xrel; + joystick->balls[ball].dy += yrel; + + // Post the event, if desired + if (SDL_EventEnabled(SDL_EVENT_JOYSTICK_BALL_MOTION)) { + SDL_Event event; + event.type = SDL_EVENT_JOYSTICK_BALL_MOTION; + event.common.timestamp = timestamp; + event.jball.which = joystick->instance_id; + event.jball.ball = ball; + event.jball.xrel = xrel; + event.jball.yrel = yrel; + SDL_PushEvent(&event); + } +} + +void SDL_SendJoystickHat(Uint64 timestamp, SDL_Joystick *joystick, Uint8 hat, Uint8 value) +{ + SDL_AssertJoysticksLocked(); + + // Make sure we're not getting garbage or duplicate events + if (hat >= joystick->nhats) { + return; + } + if (value == joystick->hats[hat]) { + return; + } + + /* We ignore events if we don't have keyboard focus, except for centering + * events. + */ + if (SDL_PrivateJoystickShouldIgnoreEvent()) { + if (value != SDL_HAT_CENTERED) { + return; + } + } + + // Update internal joystick state + SDL_assert(timestamp != 0); + joystick->hats[hat] = value; + joystick->update_complete = timestamp; + + // Post the event, if desired + if (SDL_EventEnabled(SDL_EVENT_JOYSTICK_HAT_MOTION)) { + SDL_Event event; + event.type = SDL_EVENT_JOYSTICK_HAT_MOTION; + event.common.timestamp = timestamp; + event.jhat.which = joystick->instance_id; + event.jhat.hat = hat; + event.jhat.value = value; + SDL_PushEvent(&event); + } +} + +void SDL_SendJoystickButton(Uint64 timestamp, SDL_Joystick *joystick, Uint8 button, bool down) +{ + SDL_Event event; + + SDL_AssertJoysticksLocked(); + + if (down) { + event.type = SDL_EVENT_JOYSTICK_BUTTON_DOWN; + } else { + event.type = SDL_EVENT_JOYSTICK_BUTTON_UP; + } + + // Make sure we're not getting garbage or duplicate events + if (button >= joystick->nbuttons) { + return; + } + if (down == joystick->buttons[button]) { + return; + } + + /* We ignore events if we don't have keyboard focus, except for button + * release. */ + if (SDL_PrivateJoystickShouldIgnoreEvent()) { + if (down) { + return; + } + } + + // Update internal joystick state + SDL_assert(timestamp != 0); + joystick->buttons[button] = down; + joystick->update_complete = timestamp; + + // Post the event, if desired + if (SDL_EventEnabled(event.type)) { + event.common.timestamp = timestamp; + event.jbutton.which = joystick->instance_id; + event.jbutton.button = button; + event.jbutton.down = down; + SDL_PushEvent(&event); + } +} + +static void SendSteamHandleUpdateEvents(void) +{ + SDL_Joystick *joystick; + const SDL_SteamVirtualGamepadInfo *info; + + // Check to see if any Steam handles changed + for (joystick = SDL_joysticks; joystick; joystick = joystick->next) { + bool changed = false; + + if (!SDL_IsGamepad(joystick->instance_id)) { + continue; + } + + info = SDL_GetJoystickVirtualGamepadInfoForID(joystick->instance_id); + if (info) { + if (joystick->steam_handle != info->handle) { + joystick->steam_handle = info->handle; + changed = true; + } + } else { + if (joystick->steam_handle != 0) { + joystick->steam_handle = 0; + changed = true; + } + } + if (changed) { + SDL_Event event; + + SDL_zero(event); + event.type = SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED; + event.common.timestamp = 0; + event.gdevice.which = joystick->instance_id; + SDL_PushEvent(&event); + } + } +} + +void SDL_UpdateJoysticks(void) +{ + int i; + Uint64 now; + SDL_Joystick *joystick; + + if (!SDL_WasInit(SDL_INIT_JOYSTICK)) { + return; + } + + SDL_LockJoysticks(); + + if (SDL_UpdateSteamVirtualGamepadInfo()) { + SendSteamHandleUpdateEvents(); + } + +#ifdef SDL_JOYSTICK_HIDAPI + // Special function for HIDAPI devices, as a single device can provide multiple SDL_Joysticks + HIDAPI_UpdateDevices(); +#endif // SDL_JOYSTICK_HIDAPI + + for (joystick = SDL_joysticks; joystick; joystick = joystick->next) { + if (!joystick->attached) { + continue; + } + + joystick->driver->Update(joystick); + + if (joystick->delayed_guide_button) { + SDL_GamepadHandleDelayedGuideButton(joystick); + } + + now = SDL_GetTicks(); + if (joystick->rumble_expiration && now >= joystick->rumble_expiration) { + SDL_RumbleJoystick(joystick, 0, 0, 0); + joystick->rumble_resend = 0; + } + + if (joystick->rumble_resend && now >= joystick->rumble_resend) { + joystick->driver->Rumble(joystick, joystick->low_frequency_rumble, joystick->high_frequency_rumble); + joystick->rumble_resend = now + SDL_RUMBLE_RESEND_MS; + if (joystick->rumble_resend == 0) { + joystick->rumble_resend = 1; + } + } + + if (joystick->trigger_rumble_expiration && now >= joystick->trigger_rumble_expiration) { + SDL_RumbleJoystickTriggers(joystick, 0, 0, 0); + } + } + + if (SDL_EventEnabled(SDL_EVENT_JOYSTICK_UPDATE_COMPLETE)) { + for (joystick = SDL_joysticks; joystick; joystick = joystick->next) { + if (joystick->update_complete) { + SDL_Event event; + + event.type = SDL_EVENT_JOYSTICK_UPDATE_COMPLETE; + event.common.timestamp = joystick->update_complete; + event.jdevice.which = joystick->instance_id; + SDL_PushEvent(&event); + + joystick->update_complete = 0; + } + } + } + + /* this needs to happen AFTER walking the joystick list above, so that any + dangling hardware data from removed devices can be free'd + */ + for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) { + SDL_joystick_drivers[i]->Detect(); + } + + SDL_UnlockJoysticks(); +} + +static const Uint32 SDL_joystick_event_list[] = { + SDL_EVENT_JOYSTICK_AXIS_MOTION, + SDL_EVENT_JOYSTICK_BALL_MOTION, + SDL_EVENT_JOYSTICK_HAT_MOTION, + SDL_EVENT_JOYSTICK_BUTTON_DOWN, + SDL_EVENT_JOYSTICK_BUTTON_UP, + SDL_EVENT_JOYSTICK_ADDED, + SDL_EVENT_JOYSTICK_REMOVED, + SDL_EVENT_JOYSTICK_BATTERY_UPDATED +}; + +void SDL_SetJoystickEventsEnabled(bool enabled) +{ + unsigned int i; + + for (i = 0; i < SDL_arraysize(SDL_joystick_event_list); ++i) { + SDL_SetEventEnabled(SDL_joystick_event_list[i], enabled); + } +} + +bool SDL_JoystickEventsEnabled(void) +{ + bool enabled = false; + unsigned int i; + + for (i = 0; i < SDL_arraysize(SDL_joystick_event_list); ++i) { + enabled = SDL_EventEnabled(SDL_joystick_event_list[i]); + if (enabled) { + break; + } + } + return enabled; +} + +void SDL_GetJoystickGUIDInfo(SDL_GUID guid, Uint16 *vendor, Uint16 *product, Uint16 *version, Uint16 *crc16) +{ + Uint16 *guid16 = (Uint16 *)guid.data; + Uint16 bus = SDL_Swap16LE(guid16[0]); + + if ((bus < ' ' || bus == SDL_HARDWARE_BUS_VIRTUAL) && guid16[3] == 0x0000 && guid16[5] == 0x0000) { + /* This GUID fits the standard form: + * 16-bit bus + * 16-bit CRC16 of the joystick name (can be zero) + * 16-bit vendor ID + * 16-bit zero + * 16-bit product ID + * 16-bit zero + * 16-bit version + * 8-bit driver identifier ('h' for HIDAPI, 'x' for XInput, etc.) + * 8-bit driver-dependent type info + */ + if (vendor) { + *vendor = SDL_Swap16LE(guid16[2]); + } + if (product) { + *product = SDL_Swap16LE(guid16[4]); + } + if (version) { + *version = SDL_Swap16LE(guid16[6]); + } + if (crc16) { + *crc16 = SDL_Swap16LE(guid16[1]); + } + } else if (bus < ' ' || bus == SDL_HARDWARE_BUS_VIRTUAL) { + /* This GUID fits the unknown VID/PID form: + * 16-bit bus + * 16-bit CRC16 of the joystick name (can be zero) + * 11 characters of the joystick name, null terminated + */ + if (vendor) { + *vendor = 0; + } + if (product) { + *product = 0; + } + if (version) { + *version = 0; + } + if (crc16) { + *crc16 = SDL_Swap16LE(guid16[1]); + } + } else { + if (vendor) { + *vendor = 0; + } + if (product) { + *product = 0; + } + if (version) { + *version = 0; + } + if (crc16) { + *crc16 = 0; + } + } +} + +char *SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name) +{ + const char *custom_name = GuessControllerName(vendor, product); + if (custom_name) { + return SDL_strdup(custom_name); + } + + return SDL_CreateDeviceName(vendor, product, vendor_name, product_name, "Controller"); +} + +SDL_GUID SDL_CreateJoystickGUID(Uint16 bus, Uint16 vendor, Uint16 product, Uint16 version, const char *vendor_name, const char *product_name, Uint8 driver_signature, Uint8 driver_data) +{ + SDL_GUID guid; + Uint16 *guid16 = (Uint16 *)guid.data; + Uint16 crc = 0; + + SDL_zero(guid); + + if (vendor_name && *vendor_name && product_name && *product_name) { + crc = SDL_crc16(crc, vendor_name, SDL_strlen(vendor_name)); + crc = SDL_crc16(crc, " ", 1); + crc = SDL_crc16(crc, product_name, SDL_strlen(product_name)); + } else if (product_name) { + crc = SDL_crc16(crc, product_name, SDL_strlen(product_name)); + } + + // We only need 16 bits for each of these; space them out to fill 128. + // Byteswap so devices get same GUID on little/big endian platforms. + *guid16++ = SDL_Swap16LE(bus); + *guid16++ = SDL_Swap16LE(crc); + + if (vendor) { + *guid16++ = SDL_Swap16LE(vendor); + *guid16++ = 0; + *guid16++ = SDL_Swap16LE(product); + *guid16++ = 0; + *guid16++ = SDL_Swap16LE(version); + guid.data[14] = driver_signature; + guid.data[15] = driver_data; + } else { + size_t available_space = sizeof(guid.data) - 4; + + if (driver_signature) { + available_space -= 2; + guid.data[14] = driver_signature; + guid.data[15] = driver_data; + } + if (product_name) { + SDL_strlcpy((char *)guid16, product_name, available_space); + } + } + return guid; +} + +SDL_GUID SDL_CreateJoystickGUIDForName(const char *name) +{ + return SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_UNKNOWN, 0, 0, 0, NULL, name, 0, 0); +} + +void SDL_SetJoystickGUIDVendor(SDL_GUID *guid, Uint16 vendor) +{ + Uint16 *guid16 = (Uint16 *)guid->data; + + guid16[2] = SDL_Swap16LE(vendor); +} + +void SDL_SetJoystickGUIDProduct(SDL_GUID *guid, Uint16 product) +{ + Uint16 *guid16 = (Uint16 *)guid->data; + + guid16[4] = SDL_Swap16LE(product); +} + +void SDL_SetJoystickGUIDVersion(SDL_GUID *guid, Uint16 version) +{ + Uint16 *guid16 = (Uint16 *)guid->data; + + guid16[6] = SDL_Swap16LE(version); +} + +void SDL_SetJoystickGUIDCRC(SDL_GUID *guid, Uint16 crc) +{ + Uint16 *guid16 = (Uint16 *)guid->data; + + guid16[1] = SDL_Swap16LE(crc); +} + +SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, const char *name, bool forUI) +{ + SDL_GamepadType type = SDL_GAMEPAD_TYPE_STANDARD; + + if (vendor == 0x0000 && product == 0x0000) { + // Some devices are only identifiable by their name + if (name && + (SDL_strcmp(name, "Lic Pro Controller") == 0 || + SDL_strcmp(name, "Nintendo Wireless Gamepad") == 0 || + SDL_strcmp(name, "Wireless Gamepad") == 0)) { + // HORI or PowerA Switch Pro Controller clone + type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; + } + + } else if (vendor == 0x0001 && product == 0x0001) { + type = SDL_GAMEPAD_TYPE_STANDARD; + + } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) { + type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT; + + } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) { + if (name && SDL_strstr(name, "NES Controller") != NULL) { + // We don't have a type for the Nintendo Online NES Controller + type = SDL_GAMEPAD_TYPE_STANDARD; + } else { + type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT; + } + + } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) { + if (name && SDL_strstr(name, "(L)") != NULL) { + type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT; + } else { + type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT; + } + + } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) { + type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR; + + } else if (forUI && SDL_IsJoystickGameCube(vendor, product)) { + // We don't have a type for the Nintendo GameCube controller + type = SDL_GAMEPAD_TYPE_STANDARD; + + } else { + switch (GuessControllerType(vendor, product)) { + case k_eControllerType_XBox360Controller: + type = SDL_GAMEPAD_TYPE_XBOX360; + break; + case k_eControllerType_XBoxOneController: + type = SDL_GAMEPAD_TYPE_XBOXONE; + break; + case k_eControllerType_PS3Controller: + type = SDL_GAMEPAD_TYPE_PS3; + break; + case k_eControllerType_PS4Controller: + type = SDL_GAMEPAD_TYPE_PS4; + break; + case k_eControllerType_PS5Controller: + type = SDL_GAMEPAD_TYPE_PS5; + break; + case k_eControllerType_XInputPS4Controller: + if (forUI) { + type = SDL_GAMEPAD_TYPE_PS4; + } else { + type = SDL_GAMEPAD_TYPE_STANDARD; + } + break; + case k_eControllerType_SwitchProController: + case k_eControllerType_SwitchInputOnlyController: + type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; + break; + case k_eControllerType_XInputSwitchController: + if (forUI) { + type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; + } else { + type = SDL_GAMEPAD_TYPE_STANDARD; + } + break; + default: + break; + } + } + return type; +} + +SDL_GamepadType SDL_GetGamepadTypeFromGUID(SDL_GUID guid, const char *name) +{ + SDL_GamepadType type; + Uint16 vendor, product; + + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + type = SDL_GetGamepadTypeFromVIDPID(vendor, product, name, true); + if (type == SDL_GAMEPAD_TYPE_STANDARD) { + if (SDL_IsJoystickXInput(guid)) { + // This is probably an Xbox One controller + return SDL_GAMEPAD_TYPE_XBOXONE; + } +#ifdef SDL_JOYSTICK_HIDAPI + if (SDL_IsJoystickHIDAPI(guid)) { + return HIDAPI_GetGamepadTypeFromGUID(guid); + } +#endif // SDL_JOYSTICK_HIDAPI + } + return type; +} + +bool SDL_JoystickGUIDUsesVersion(SDL_GUID guid) +{ + Uint16 vendor, product; + + if (SDL_IsJoystickMFI(guid)) { + // The version bits are used as button capability mask + return false; + } + + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + if (vendor && product) { + return true; + } + return false; +} + +bool SDL_IsJoystickXboxOne(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_XBoxOneController; +} + +bool SDL_IsJoystickXboxOneElite(Uint16 vendor_id, Uint16 product_id) +{ + if (vendor_id == USB_VENDOR_MICROSOFT) { + if (product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1 || + product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2 || + product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH || + product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLE) { + return true; + } + } + return false; +} + +bool SDL_IsJoystickXboxSeriesX(Uint16 vendor_id, Uint16 product_id) +{ + if (vendor_id == USB_VENDOR_MICROSOFT) { + if (product_id == USB_PRODUCT_XBOX_SERIES_X || + product_id == USB_PRODUCT_XBOX_SERIES_X_BLE) { + return true; + } + } + if (vendor_id == USB_VENDOR_PDP) { + if (product_id == USB_PRODUCT_XBOX_SERIES_X_VICTRIX_GAMBIT || + product_id == USB_PRODUCT_XBOX_SERIES_X_PDP_BLUE || + product_id == USB_PRODUCT_XBOX_SERIES_X_PDP_AFTERGLOW) { + return true; + } + } + if (vendor_id == USB_VENDOR_POWERA_ALT) { + if ((product_id >= 0x2001 && product_id <= 0x201a) || + product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO2 || + product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO4 || + product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_USB || + product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_DONGLE || + product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_MOGA_XP_ULTRA || + product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_SPECTRA) { + return true; + } + } + if (vendor_id == USB_VENDOR_HORI) { + if (product_id == USB_PRODUCT_HORI_FIGHTING_COMMANDER_OCTA_SERIES_X || + product_id == USB_PRODUCT_HORI_HORIPAD_PRO_SERIES_X) { + return true; + } + } + if (vendor_id == USB_VENDOR_HP) { + if (product_id == USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX || + product_id == USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX_RGB) { + return true; + } + } + if (vendor_id == USB_VENDOR_RAZER) { + if (product_id == USB_PRODUCT_RAZER_WOLVERINE_V2 || + product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_CHROMA || + product_id == USB_PRODUCT_RAZER_WOLVERINE_V3_PRO) { + return true; + } + } + if (vendor_id == USB_VENDOR_THRUSTMASTER) { + if (product_id == USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_SERIES_X) { + return true; + } + } + if (vendor_id == USB_VENDOR_TURTLE_BEACH) { + if (product_id == USB_PRODUCT_TURTLE_BEACH_SERIES_X_REACT_R || + product_id == USB_PRODUCT_TURTLE_BEACH_SERIES_X_RECON) { + return true; + } + } + if (vendor_id == USB_VENDOR_8BITDO) { + if (product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 || + product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER2) { + return true; + } + } + if (vendor_id == USB_VENDOR_GAMESIR) { + if (product_id == USB_PRODUCT_GAMESIR_G7) { + return true; + } + } + if (vendor_id == USB_VENDOR_ASUS) { + if (product_id == USB_PRODUCT_ROG_RAIKIRI) { + return true; + } + } + return false; +} + +bool SDL_IsJoystickBluetoothXboxOne(Uint16 vendor_id, Uint16 product_id) +{ + if (vendor_id == USB_VENDOR_MICROSOFT) { + if (product_id == USB_PRODUCT_XBOX_ONE_ADAPTIVE_BLUETOOTH || + product_id == USB_PRODUCT_XBOX_ONE_ADAPTIVE_BLE || + product_id == USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH || + product_id == USB_PRODUCT_XBOX_ONE_S_REV2_BLUETOOTH || + product_id == USB_PRODUCT_XBOX_ONE_S_REV2_BLE || + product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH || + product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLE || + product_id == USB_PRODUCT_XBOX_SERIES_X_BLE) { + return true; + } + } + return false; +} + +bool SDL_IsJoystickPS4(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_PS4Controller; +} + +bool SDL_IsJoystickPS5(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_PS5Controller; +} + +bool SDL_IsJoystickDualSenseEdge(Uint16 vendor_id, Uint16 product_id) +{ + if (vendor_id == USB_VENDOR_SONY) { + if (product_id == USB_PRODUCT_SONY_DS5_EDGE) { + return true; + } + } + return false; +} + +bool SDL_IsJoystickNintendoSwitchPro(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_SwitchProController || eType == k_eControllerType_SwitchInputOnlyController; +} + +bool SDL_IsJoystickNintendoSwitchProInputOnly(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_SwitchInputOnlyController; +} + +bool SDL_IsJoystickNintendoSwitchJoyCon(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_SwitchJoyConLeft || eType == k_eControllerType_SwitchJoyConRight; +} + +bool SDL_IsJoystickNintendoSwitchJoyConLeft(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_SwitchJoyConLeft; +} + +bool SDL_IsJoystickNintendoSwitchJoyConRight(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_SwitchJoyConRight; +} + +bool SDL_IsJoystickNintendoSwitchJoyConGrip(Uint16 vendor_id, Uint16 product_id) +{ + return vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP; +} + +bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product_id) +{ + return vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; +} + +bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id) +{ + return SDL_VIDPIDInList(vendor_id, product_id, &gamecube_devices); +} + +bool SDL_IsJoystickAmazonLunaController(Uint16 vendor_id, Uint16 product_id) +{ + return ((vendor_id == USB_VENDOR_AMAZON && product_id == USB_PRODUCT_AMAZON_LUNA_CONTROLLER) || + (vendor_id == BLUETOOTH_VENDOR_AMAZON && product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER)); +} + +bool SDL_IsJoystickGoogleStadiaController(Uint16 vendor_id, Uint16 product_id) +{ + return vendor_id == USB_VENDOR_GOOGLE && product_id == USB_PRODUCT_GOOGLE_STADIA_CONTROLLER; +} + +bool SDL_IsJoystickNVIDIASHIELDController(Uint16 vendor_id, Uint16 product_id) +{ + return (vendor_id == USB_VENDOR_NVIDIA && + (product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103 || + product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104)); +} + +bool SDL_IsJoystickSteamVirtualGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version) +{ +#ifdef SDL_PLATFORM_MACOS + return (vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX360_WIRED_CONTROLLER && version == 0); +#else + return (vendor_id == USB_VENDOR_VALVE && product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD); +#endif +} + +bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_SteamController || eType == k_eControllerType_SteamControllerV2; +} + +bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id) +{ + return vendor_id == USB_VENDOR_HORI && (product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER || product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT); +} + +bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id) +{ + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_SteamControllerNeptune; +} + +bool SDL_IsJoystickXInput(SDL_GUID guid) +{ + return (guid.data[14] == 'x') ? true : false; +} + +bool SDL_IsJoystickWGI(SDL_GUID guid) +{ + return (guid.data[14] == 'w') ? true : false; +} + +bool SDL_IsJoystickHIDAPI(SDL_GUID guid) +{ + return (guid.data[14] == 'h') ? true : false; +} + +bool SDL_IsJoystickMFI(SDL_GUID guid) +{ + return (guid.data[14] == 'm') ? true : false; +} + +bool SDL_IsJoystickRAWINPUT(SDL_GUID guid) +{ + return (guid.data[14] == 'r') ? true : false; +} + +bool SDL_IsJoystickVIRTUAL(SDL_GUID guid) +{ + return (guid.data[14] == 'v') ? true : false; +} + +static bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id) +{ + return SDL_VIDPIDInList(vendor_id, product_id, &wheel_devices); +} + +static bool SDL_IsJoystickArcadeStick(Uint16 vendor_id, Uint16 product_id) +{ + return SDL_VIDPIDInList(vendor_id, product_id, &arcadestick_devices); +} + +static bool SDL_IsJoystickFlightStick(Uint16 vendor_id, Uint16 product_id) +{ + return SDL_VIDPIDInList(vendor_id, product_id, &flightstick_devices); +} + +static bool SDL_IsJoystickThrottle(Uint16 vendor_id, Uint16 product_id) +{ + return SDL_VIDPIDInList(vendor_id, product_id, &throttle_devices); +} + +static SDL_JoystickType SDL_GetJoystickGUIDType(SDL_GUID guid) +{ + Uint16 vendor; + Uint16 product; + + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + + if (SDL_IsJoystickWheel(vendor, product)) { + return SDL_JOYSTICK_TYPE_WHEEL; + } + + if (SDL_IsJoystickArcadeStick(vendor, product)) { + return SDL_JOYSTICK_TYPE_ARCADE_STICK; + } + + if (SDL_IsJoystickFlightStick(vendor, product)) { + return SDL_JOYSTICK_TYPE_FLIGHT_STICK; + } + + if (SDL_IsJoystickThrottle(vendor, product)) { + return SDL_JOYSTICK_TYPE_THROTTLE; + } + + if (SDL_IsJoystickXInput(guid)) { + // XInput GUID, get the type based on the XInput device subtype + switch (guid.data[15]) { + case 0x01: // XINPUT_DEVSUBTYPE_GAMEPAD + return SDL_JOYSTICK_TYPE_GAMEPAD; + case 0x02: // XINPUT_DEVSUBTYPE_WHEEL + return SDL_JOYSTICK_TYPE_WHEEL; + case 0x03: // XINPUT_DEVSUBTYPE_ARCADE_STICK + return SDL_JOYSTICK_TYPE_ARCADE_STICK; + case 0x04: // XINPUT_DEVSUBTYPE_FLIGHT_STICK + return SDL_JOYSTICK_TYPE_FLIGHT_STICK; + case 0x05: // XINPUT_DEVSUBTYPE_DANCE_PAD + return SDL_JOYSTICK_TYPE_DANCE_PAD; + case 0x06: // XINPUT_DEVSUBTYPE_GUITAR + case 0x07: // XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE + case 0x0B: // XINPUT_DEVSUBTYPE_GUITAR_BASS + return SDL_JOYSTICK_TYPE_GUITAR; + case 0x08: // XINPUT_DEVSUBTYPE_DRUM_KIT + return SDL_JOYSTICK_TYPE_DRUM_KIT; + case 0x13: // XINPUT_DEVSUBTYPE_ARCADE_PAD + return SDL_JOYSTICK_TYPE_ARCADE_PAD; + default: + return SDL_JOYSTICK_TYPE_UNKNOWN; + } + } + + if (SDL_IsJoystickWGI(guid)) { + return (SDL_JoystickType)guid.data[15]; + } + + if (SDL_IsJoystickVIRTUAL(guid)) { + return (SDL_JoystickType)guid.data[15]; + } + +#ifdef SDL_JOYSTICK_HIDAPI + if (SDL_IsJoystickHIDAPI(guid)) { + return HIDAPI_GetJoystickTypeFromGUID(guid); + } +#endif // SDL_JOYSTICK_HIDAPI + + if (GuessControllerType(vendor, product) != k_eControllerType_UnknownNonSteamController) { + return SDL_JOYSTICK_TYPE_GAMEPAD; + } + + return SDL_JOYSTICK_TYPE_UNKNOWN; +} + +bool SDL_ShouldIgnoreJoystick(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // Check the joystick blacklist + if (SDL_VIDPIDInList(vendor_id, product_id, &blacklist_devices)) { + return true; + } + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_ROG_CHAKRAM, false)) { + if (SDL_VIDPIDInList(vendor_id, product_id, &rog_gamepad_mice)) { + return true; + } + } + + if (SDL_ShouldIgnoreGamepad(vendor_id, product_id, version, name)) { + return true; + } + + return false; +} + +// return the guid for this index +SDL_GUID SDL_GetJoystickGUIDForID(SDL_JoystickID instance_id) +{ + SDL_JoystickDriver *driver; + int device_index; + SDL_GUID guid; + + SDL_LockJoysticks(); + if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) { + guid = driver->GetDeviceGUID(device_index); + } else { + SDL_zero(guid); + } + SDL_UnlockJoysticks(); + + return guid; +} + +Uint16 SDL_GetJoystickVendorForID(SDL_JoystickID instance_id) +{ + Uint16 vendor; + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id); + if (info) { + vendor = info->vendor_id; + } else { + SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id); + + SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL); + } + SDL_UnlockJoysticks(); + + return vendor; +} + +Uint16 SDL_GetJoystickProductForID(SDL_JoystickID instance_id) +{ + Uint16 product; + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id); + if (info) { + product = info->product_id; + } else { + SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id); + + SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL); + } + SDL_UnlockJoysticks(); + + return product; +} + +Uint16 SDL_GetJoystickProductVersionForID(SDL_JoystickID instance_id) +{ + Uint16 version; + SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id); + + SDL_GetJoystickGUIDInfo(guid, NULL, NULL, &version, NULL); + return version; +} + +SDL_JoystickType SDL_GetJoystickTypeForID(SDL_JoystickID instance_id) +{ + SDL_JoystickType type; + SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id); + + type = SDL_GetJoystickGUIDType(guid); + if (type == SDL_JOYSTICK_TYPE_UNKNOWN) { + if (SDL_IsGamepad(instance_id)) { + type = SDL_JOYSTICK_TYPE_GAMEPAD; + } + } + return type; +} + +SDL_GUID SDL_GetJoystickGUID(SDL_Joystick *joystick) +{ + SDL_GUID result; + + SDL_LockJoysticks(); + { + static SDL_GUID emptyGUID; + + CHECK_JOYSTICK_MAGIC(joystick, emptyGUID); + + result = joystick->guid; + } + SDL_UnlockJoysticks(); + + return result; +} + +Uint16 SDL_GetJoystickVendor(SDL_Joystick *joystick) +{ + Uint16 vendor; + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, 0); + + info = SDL_GetJoystickVirtualGamepadInfoForID(joystick->instance_id); + if (info) { + vendor = info->vendor_id; + } else { + SDL_GUID guid = SDL_GetJoystickGUID(joystick); + + SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL); + } + } + SDL_UnlockJoysticks(); + + return vendor; +} + +Uint16 SDL_GetJoystickProduct(SDL_Joystick *joystick) +{ + Uint16 product; + const SDL_SteamVirtualGamepadInfo *info; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, 0); + + info = SDL_GetJoystickVirtualGamepadInfoForID(joystick->instance_id); + if (info) { + product = info->product_id; + } else { + SDL_GUID guid = SDL_GetJoystickGUID(joystick); + + SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL); + } + } + SDL_UnlockJoysticks(); + + return product; +} + +Uint16 SDL_GetJoystickProductVersion(SDL_Joystick *joystick) +{ + Uint16 version; + SDL_GUID guid = SDL_GetJoystickGUID(joystick); + + SDL_GetJoystickGUIDInfo(guid, NULL, NULL, &version, NULL); + return version; +} + +Uint16 SDL_GetJoystickFirmwareVersion(SDL_Joystick *joystick) +{ + Uint16 result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, 0); + + result = joystick->firmware_version; + } + SDL_UnlockJoysticks(); + + return result; +} + +const char *SDL_GetJoystickSerial(SDL_Joystick *joystick) +{ + const char *result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, NULL); + + result = SDL_GetPersistentString(joystick->serial); + } + SDL_UnlockJoysticks(); + + return result; +} + +SDL_JoystickType SDL_GetJoystickType(SDL_Joystick *joystick) +{ + SDL_JoystickType type; + SDL_GUID guid = SDL_GetJoystickGUID(joystick); + + type = SDL_GetJoystickGUIDType(guid); + if (type == SDL_JOYSTICK_TYPE_UNKNOWN) { + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, SDL_JOYSTICK_TYPE_UNKNOWN); + + if (SDL_IsGamepad(joystick->instance_id)) { + type = SDL_JOYSTICK_TYPE_GAMEPAD; + } + } + SDL_UnlockJoysticks(); + } + return type; +} + +void SDL_SendJoystickPowerInfo(SDL_Joystick *joystick, SDL_PowerState state, int percent) +{ + SDL_AssertJoysticksLocked(); + + if (state != joystick->battery_state || percent != joystick->battery_percent) { + joystick->battery_state = state; + joystick->battery_percent = percent; + + if (SDL_EventEnabled(SDL_EVENT_JOYSTICK_BATTERY_UPDATED)) { + SDL_Event event; + event.type = SDL_EVENT_JOYSTICK_BATTERY_UPDATED; + event.common.timestamp = 0; + event.jbattery.which = joystick->instance_id; + event.jbattery.state = state; + event.jbattery.percent = percent; + SDL_PushEvent(&event); + } + } +} + +SDL_JoystickConnectionState SDL_GetJoystickConnectionState(SDL_Joystick *joystick) +{ + SDL_JoystickConnectionState result; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, SDL_JOYSTICK_CONNECTION_INVALID); + + result = joystick->connection_state; + } + SDL_UnlockJoysticks(); + + return result; +} + +SDL_PowerState SDL_GetJoystickPowerInfo(SDL_Joystick *joystick, int *percent) +{ + SDL_PowerState result; + + if (percent) { + *percent = -1; + } + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, SDL_POWERSTATE_ERROR); + + result = joystick->battery_state; + + if (percent) { + *percent = joystick->battery_percent; + } + } + SDL_UnlockJoysticks(); + + return result; +} + +void SDL_SendJoystickTouchpad(Uint64 timestamp, SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure) +{ + SDL_JoystickTouchpadInfo *touchpad_info; + SDL_JoystickTouchpadFingerInfo *finger_info; + Uint32 event_type; + + SDL_AssertJoysticksLocked(); + + if (touchpad < 0 || touchpad >= joystick->ntouchpads) { + return; + } + + touchpad_info = &joystick->touchpads[touchpad]; + if (finger < 0 || finger >= touchpad_info->nfingers) { + return; + } + + finger_info = &touchpad_info->fingers[finger]; + + if (!down) { + if (x == 0.0f && y == 0.0f) { + x = finger_info->x; + y = finger_info->y; + } + pressure = 0.0f; + } + + if (x < 0.0f) { + x = 0.0f; + } else if (x > 1.0f) { + x = 1.0f; + } + if (y < 0.0f) { + y = 0.0f; + } else if (y > 1.0f) { + y = 1.0f; + } + if (pressure < 0.0f) { + pressure = 0.0f; + } else if (pressure > 1.0f) { + pressure = 1.0f; + } + + if (down == finger_info->down) { + if (!down || + (x == finger_info->x && y == finger_info->y && pressure == finger_info->pressure)) { + return; + } + } + + if (down == finger_info->down) { + event_type = SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION; + } else if (down) { + event_type = SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN; + } else { + event_type = SDL_EVENT_GAMEPAD_TOUCHPAD_UP; + } + + // We ignore events if we don't have keyboard focus, except for touch release + if (SDL_PrivateJoystickShouldIgnoreEvent()) { + if (event_type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP) { + return; + } + } + + // Update internal joystick state + SDL_assert(timestamp != 0); + finger_info->down = down; + finger_info->x = x; + finger_info->y = y; + finger_info->pressure = pressure; + joystick->update_complete = timestamp; + + // Post the event, if desired + if (SDL_EventEnabled(event_type)) { + SDL_Event event; + event.type = event_type; + event.common.timestamp = timestamp; + event.gtouchpad.which = joystick->instance_id; + event.gtouchpad.touchpad = touchpad; + event.gtouchpad.finger = finger; + event.gtouchpad.x = x; + event.gtouchpad.y = y; + event.gtouchpad.pressure = pressure; + SDL_PushEvent(&event); + } +} + +void SDL_SendJoystickSensor(Uint64 timestamp, SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values) +{ + SDL_AssertJoysticksLocked(); + + // We ignore events if we don't have keyboard focus + if (SDL_PrivateJoystickShouldIgnoreEvent()) { + return; + } + + for (int i = 0; i < joystick->nsensors; ++i) { + SDL_JoystickSensorInfo *sensor = &joystick->sensors[i]; + + if (sensor->type == type) { + if (sensor->enabled) { + num_values = SDL_min(num_values, SDL_arraysize(sensor->data)); + + // Update internal sensor state + SDL_memcpy(sensor->data, data, num_values * sizeof(*data)); + joystick->update_complete = timestamp; + + // Post the event, if desired + if (SDL_EventEnabled(SDL_EVENT_GAMEPAD_SENSOR_UPDATE)) { + SDL_Event event; + event.type = SDL_EVENT_GAMEPAD_SENSOR_UPDATE; + event.common.timestamp = timestamp; + event.gsensor.which = joystick->instance_id; + event.gsensor.sensor = type; + num_values = SDL_min(num_values, + SDL_arraysize(event.gsensor.data)); + SDL_memset(event.gsensor.data, 0, + sizeof(event.gsensor.data)); + SDL_memcpy(event.gsensor.data, data, + num_values * sizeof(*data)); + event.gsensor.sensor_timestamp = sensor_timestamp; + SDL_PushEvent(&event); + } + } + break; + } + } +} + +static void SDL_LoadVIDPIDListFromHint(const char *hint, int *num_entries, int *max_entries, Uint32 **entries) +{ + Uint32 entry; + char *spot; + char *file = NULL; + + if (hint && *hint == '@') { + spot = file = (char *)SDL_LoadFile(hint + 1, NULL); + } else { + spot = (char *)hint; + } + + if (!spot) { + return; + } + + while ((spot = SDL_strstr(spot, "0x")) != NULL) { + entry = (Uint16)SDL_strtol(spot, &spot, 0); + entry <<= 16; + spot = SDL_strstr(spot, "0x"); + if (!spot) { + break; + } + entry |= (Uint16)SDL_strtol(spot, &spot, 0); + + if (*num_entries == *max_entries) { + int new_max_entries = *max_entries + 16; + Uint32 *new_entries = (Uint32 *)SDL_realloc(*entries, new_max_entries * sizeof(**entries)); + if (!new_entries) { + // Out of memory, go with what we have already + break; + } + *entries = new_entries; + *max_entries = new_max_entries; + } + (*entries)[(*num_entries)++] = entry; + } + + if (file) { + SDL_free(file); + } +} + +void SDL_LoadVIDPIDListFromHints(SDL_vidpid_list *list, const char *included_list, const char *excluded_list) +{ + // Empty the list + list->num_included_entries = 0; + list->num_excluded_entries = 0; + + // Add the initial entries + if (list->num_initial_entries > 0) { + if (list->num_included_entries < list->num_initial_entries) { + Uint32 *entries = (Uint32 *)SDL_malloc(list->num_initial_entries * sizeof(*entries)); + if (entries) { + SDL_memcpy(entries, list->initial_entries, list->num_initial_entries * sizeof(*entries)); + list->included_entries = entries; + list->num_included_entries = list->num_initial_entries; + list->max_included_entries = list->num_initial_entries; + } + } + } + + // Add the included entries from the hint + SDL_LoadVIDPIDListFromHint(included_list, &list->num_included_entries, &list->max_included_entries, &list->included_entries); + + // Add the excluded entries from the hint + SDL_LoadVIDPIDListFromHint(excluded_list, &list->num_excluded_entries, &list->max_excluded_entries, &list->excluded_entries); +} + +static void SDLCALL SDL_VIDPIDIncludedHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_vidpid_list *list = (SDL_vidpid_list *)userdata; + const char *included_list = hint; + const char *excluded_list = NULL; + + if (!list->initialized) { + return; + } + + if (list->excluded_hint_name) { + excluded_list = SDL_GetHint(list->excluded_hint_name); + } + SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list); +} + +static void SDLCALL SDL_VIDPIDExcludedHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_vidpid_list *list = (SDL_vidpid_list *)userdata; + const char *included_list = NULL; + const char *excluded_list = hint; + + if (!list->initialized) { + return; + } + + if (list->included_hint_name) { + included_list = SDL_GetHint(list->included_hint_name); + } + SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list); +} + +void SDL_LoadVIDPIDList(SDL_vidpid_list *list) +{ + const char *included_list = NULL; + const char *excluded_list = NULL; + + if (list->included_hint_name) { + SDL_AddHintCallback(list->included_hint_name, SDL_VIDPIDIncludedHintChanged, list); + } + + if (list->excluded_hint_name) { + SDL_AddHintCallback(list->excluded_hint_name, SDL_VIDPIDExcludedHintChanged, list); + } + + list->initialized = true; + + if (list->included_hint_name) { + included_list = SDL_GetHint(list->included_hint_name); + } + if (list->excluded_hint_name) { + excluded_list = SDL_GetHint(list->excluded_hint_name); + } + SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list); +} + +bool SDL_VIDPIDInList(Uint16 vendor_id, Uint16 product_id, const SDL_vidpid_list *list) +{ + int i; + Uint32 vidpid = MAKE_VIDPID(vendor_id, product_id); + + for (i = 0; i < list->num_excluded_entries; ++i) { + if (vidpid == list->excluded_entries[i]) { + return false; + } + } + for (i = 0; i < list->num_included_entries; ++i) { + if (vidpid == list->included_entries[i]) { + return true; + } + } + return false; +} + +void SDL_FreeVIDPIDList(SDL_vidpid_list *list) +{ + if (list->included_hint_name) { + SDL_RemoveHintCallback(list->included_hint_name, SDL_VIDPIDIncludedHintChanged, list); + } + + if (list->excluded_hint_name) { + SDL_RemoveHintCallback(list->excluded_hint_name, SDL_VIDPIDExcludedHintChanged, list); + } + + if (list->included_entries) { + SDL_free(list->included_entries); + list->included_entries = NULL; + list->num_included_entries = 0; + list->max_included_entries = 0; + } + + if (list->excluded_entries) { + SDL_free(list->excluded_entries); + list->excluded_entries = NULL; + list->num_excluded_entries = 0; + list->max_excluded_entries = 0; + } + + list->initialized = false; +} diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_joystick_c.h b/contrib/SDL-3.2.8/src/joystick/SDL_joystick_c.h new file mode 100644 index 0000000..d931cf7 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/SDL_joystick_c.h @@ -0,0 +1,270 @@ +/* + 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. +*/ + +#ifndef SDL_joystick_c_h_ +#define SDL_joystick_c_h_ + +#include "SDL_internal.h" + +// Useful functions and variables from SDL_joystick.c + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +struct SDL_JoystickDriver; +struct SDL_SteamVirtualGamepadInfo; + +// Initialization and shutdown functions +extern bool SDL_InitJoysticks(void); +extern void SDL_QuitJoysticks(void); + +// Return whether the joystick system is currently initialized +extern bool SDL_JoysticksInitialized(void); + +// Return whether the joystick system is shutting down +extern bool SDL_JoysticksQuitting(void); + +// Return whether the joysticks are currently locked +extern bool SDL_JoysticksLocked(void); + +// Make sure we currently have the joysticks locked +extern void SDL_AssertJoysticksLocked(void) SDL_ASSERT_CAPABILITY(SDL_joystick_lock); + +// Function to return whether there are any joysticks opened by the application +extern bool SDL_JoysticksOpened(void); + +// Function to determine whether a device is currently detected by this driver +extern bool SDL_JoystickHandledByAnotherDriver(struct SDL_JoystickDriver *driver, Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name); + +/* Function to standardize the name for a controller + This should be freed with SDL_free() when no longer needed + */ +extern char *SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name); + +// Function to create a GUID for a joystick based on the VID/PID and name +extern SDL_GUID SDL_CreateJoystickGUID(Uint16 bus, Uint16 vendor, Uint16 product, Uint16 version, const char *vendor_name, const char *product_name, Uint8 driver_signature, Uint8 driver_data); + +// Function to create a GUID for a joystick based on the name, with no VID/PID information +extern SDL_GUID SDL_CreateJoystickGUIDForName(const char *name); + +// Function to set the vendor field of a joystick GUID +extern void SDL_SetJoystickGUIDVendor(SDL_GUID *guid, Uint16 vendor); + +// Function to set the product field of a joystick GUID +extern void SDL_SetJoystickGUIDProduct(SDL_GUID *guid, Uint16 product); + +// Function to set the version field of a joystick GUID +extern void SDL_SetJoystickGUIDVersion(SDL_GUID *guid, Uint16 version); + +// Function to set the CRC field of a joystick GUID +extern void SDL_SetJoystickGUIDCRC(SDL_GUID *guid, Uint16 crc); + +// Function to return the type of a controller +extern SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, const char *name, bool forUI); +extern SDL_GamepadType SDL_GetGamepadTypeFromGUID(SDL_GUID guid, const char *name); + +// Function to return whether a joystick GUID uses the version field +extern bool SDL_JoystickGUIDUsesVersion(SDL_GUID guid); + +// Function to return whether a joystick is an Xbox One controller +extern bool SDL_IsJoystickXboxOne(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is an Xbox One Elite controller +extern bool SDL_IsJoystickXboxOneElite(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is an Xbox Series X controller +extern bool SDL_IsJoystickXboxSeriesX(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is an Xbox One controller connected via Bluetooth +extern bool SDL_IsJoystickBluetoothXboxOne(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is a PS4 controller +extern bool SDL_IsJoystickPS4(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is a PS5 controller +extern bool SDL_IsJoystickPS5(Uint16 vendor_id, Uint16 product_id); +extern bool SDL_IsJoystickDualSenseEdge(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is a Nintendo Switch Pro controller +extern bool SDL_IsJoystickNintendoSwitchPro(Uint16 vendor_id, Uint16 product_id); +extern bool SDL_IsJoystickNintendoSwitchProInputOnly(Uint16 vendor_id, Uint16 product_id); +extern bool SDL_IsJoystickNintendoSwitchJoyCon(Uint16 vendor_id, Uint16 product_id); +extern bool SDL_IsJoystickNintendoSwitchJoyConLeft(Uint16 vendor_id, Uint16 product_id); +extern bool SDL_IsJoystickNintendoSwitchJoyConRight(Uint16 vendor_id, Uint16 product_id); +extern bool SDL_IsJoystickNintendoSwitchJoyConGrip(Uint16 vendor_id, Uint16 product_id); +extern bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is a Nintendo GameCube style controller +extern bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is an Amazon Luna controller +extern bool SDL_IsJoystickAmazonLunaController(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is a Google Stadia controller +extern bool SDL_IsJoystickGoogleStadiaController(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is an NVIDIA SHIELD controller +extern bool SDL_IsJoystickNVIDIASHIELDController(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is a Steam Virtual Gamepad +extern bool SDL_IsJoystickSteamVirtualGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version); + +// Function to return whether a joystick is a Steam Controller +extern bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is a HORI Steam controller +extern bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick is a Steam Deck +extern bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id); + +// Function to return whether a joystick guid comes from the XInput driver +extern bool SDL_IsJoystickXInput(SDL_GUID guid); + +// Function to return whether a joystick guid comes from the WGI driver +extern bool SDL_IsJoystickWGI(SDL_GUID guid); + +// Function to return whether a joystick guid comes from the HIDAPI driver +extern bool SDL_IsJoystickHIDAPI(SDL_GUID guid); + +// Function to return whether a joystick guid comes from the MFI driver +extern bool SDL_IsJoystickMFI(SDL_GUID guid); + +// Function to return whether a joystick guid comes from the RAWINPUT driver +extern bool SDL_IsJoystickRAWINPUT(SDL_GUID guid); + +// Function to return whether a joystick guid comes from the Virtual driver +extern bool SDL_IsJoystickVIRTUAL(SDL_GUID guid); + +// Function to return whether a joystick should be ignored +extern bool SDL_ShouldIgnoreJoystick(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name); + +// Internal event queueing functions +extern void SDL_PrivateJoystickAddTouchpad(SDL_Joystick *joystick, int nfingers); +extern void SDL_PrivateJoystickAddSensor(SDL_Joystick *joystick, SDL_SensorType type, float rate); +extern void SDL_PrivateJoystickSensorRate(SDL_Joystick *joystick, SDL_SensorType type, float rate); +extern void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id); +extern bool SDL_IsJoystickBeingAdded(void); +extern void SDL_PrivateJoystickRemoved(SDL_JoystickID instance_id); +extern void SDL_PrivateJoystickForceRecentering(SDL_Joystick *joystick); +extern void SDL_SendJoystickAxis(Uint64 timestamp, SDL_Joystick *joystick, Uint8 axis, Sint16 value); +extern void SDL_SendJoystickBall(Uint64 timestamp, SDL_Joystick *joystick, Uint8 ball, Sint16 xrel, Sint16 yrel); +extern void SDL_SendJoystickHat(Uint64 timestamp, SDL_Joystick *joystick, Uint8 hat, Uint8 value); +extern void SDL_SendJoystickButton(Uint64 timestamp, SDL_Joystick *joystick, Uint8 button, bool down); +extern void SDL_SendJoystickTouchpad(Uint64 timestamp, SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure); +extern void SDL_SendJoystickSensor(Uint64 timestamp, SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values); +extern void SDL_SendJoystickPowerInfo(SDL_Joystick *joystick, SDL_PowerState state, int percent); + +// Function to get the Steam virtual gamepad info for a joystick +extern const struct SDL_SteamVirtualGamepadInfo *SDL_GetJoystickVirtualGamepadInfoForID(SDL_JoystickID instance_id); + +// Internal sanity checking functions +extern bool SDL_IsJoystickValid(SDL_Joystick *joystick); + +typedef enum +{ + EMappingKind_None, + EMappingKind_Button, + EMappingKind_Axis, + EMappingKind_Hat, +} EMappingKind; + +typedef struct SDL_InputMapping +{ + EMappingKind kind; + Uint8 target; + bool axis_reversed; + bool half_axis_positive; + bool half_axis_negative; +} SDL_InputMapping; + +typedef struct SDL_GamepadMapping +{ + SDL_InputMapping a; + SDL_InputMapping b; + SDL_InputMapping x; + SDL_InputMapping y; + SDL_InputMapping back; + SDL_InputMapping guide; + SDL_InputMapping start; + SDL_InputMapping leftstick; + SDL_InputMapping rightstick; + SDL_InputMapping leftshoulder; + SDL_InputMapping rightshoulder; + SDL_InputMapping dpup; + SDL_InputMapping dpdown; + SDL_InputMapping dpleft; + SDL_InputMapping dpright; + SDL_InputMapping misc1; + SDL_InputMapping misc2; + SDL_InputMapping misc3; + SDL_InputMapping misc4; + SDL_InputMapping misc5; + SDL_InputMapping misc6; + SDL_InputMapping right_paddle1; + SDL_InputMapping left_paddle1; + SDL_InputMapping right_paddle2; + SDL_InputMapping left_paddle2; + SDL_InputMapping leftx; + SDL_InputMapping lefty; + SDL_InputMapping rightx; + SDL_InputMapping righty; + SDL_InputMapping lefttrigger; + SDL_InputMapping righttrigger; + SDL_InputMapping touchpad; +} SDL_GamepadMapping; + +// Function to get autodetected gamepad controller mapping from the driver +extern bool SDL_PrivateJoystickGetAutoGamepadMapping(SDL_JoystickID instance_id, + SDL_GamepadMapping *out); + + +typedef struct +{ + const char *included_hint_name; + int num_included_entries; + int max_included_entries; + Uint32 *included_entries; + + const char *excluded_hint_name; + int num_excluded_entries; + int max_excluded_entries; + Uint32 *excluded_entries; + + int num_initial_entries; + Uint32 *initial_entries; + + bool initialized; +} SDL_vidpid_list; + +extern void SDL_LoadVIDPIDList(SDL_vidpid_list *list); +extern void SDL_LoadVIDPIDListFromHints(SDL_vidpid_list *list, const char *included_list, const char *excluded_list); +extern bool SDL_VIDPIDInList(Uint16 vendor_id, Uint16 product_id, const SDL_vidpid_list *list); +extern void SDL_FreeVIDPIDList(SDL_vidpid_list *list); + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif + +#endif // SDL_joystick_c_h_ diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.c b/contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.c new file mode 100644 index 0000000..6b253de --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.c @@ -0,0 +1,254 @@ +/* + 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" + +#include "SDL_joystick_c.h" +#include "SDL_steam_virtual_gamepad.h" + +#ifdef SDL_PLATFORM_WIN32 +#include "../core/windows/SDL_windows.h" +#else +#include +#include +#endif + +#define SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE "SteamVirtualGamepadInfo" + +static char *SDL_steam_virtual_gamepad_info_file SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static Uint64 SDL_steam_virtual_gamepad_info_file_mtime SDL_GUARDED_BY(SDL_joystick_lock) = 0; +static Uint64 SDL_steam_virtual_gamepad_info_check_time SDL_GUARDED_BY(SDL_joystick_lock) = 0; +static SDL_SteamVirtualGamepadInfo **SDL_steam_virtual_gamepad_info SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static int SDL_steam_virtual_gamepad_info_count SDL_GUARDED_BY(SDL_joystick_lock) = 0; + + +static Uint64 GetFileModificationTime(const char *file) +{ + Uint64 modification_time = 0; + +#ifdef SDL_PLATFORM_WIN32 + WCHAR *wFile = WIN_UTF8ToStringW(file); + if (wFile) { + HANDLE hFile = CreateFileW(wFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + FILETIME last_write_time; + if (GetFileTime(hFile, NULL, NULL, &last_write_time)) { + modification_time = last_write_time.dwHighDateTime; + modification_time <<= 32; + modification_time |= last_write_time.dwLowDateTime; + } + CloseHandle(hFile); + } + SDL_free(wFile); + } +#else + struct stat sb; + + if (stat(file, &sb) == 0) { + modification_time = (Uint64)sb.st_mtime; + } +#endif + return modification_time; +} + +static void SDL_FreeSteamVirtualGamepadInfo(void) +{ + int i; + + SDL_AssertJoysticksLocked(); + + for (i = 0; i < SDL_steam_virtual_gamepad_info_count; ++i) { + SDL_SteamVirtualGamepadInfo *entry = SDL_steam_virtual_gamepad_info[i]; + if (entry) { + SDL_free(entry->name); + SDL_free(entry); + } + } + SDL_free(SDL_steam_virtual_gamepad_info); + SDL_steam_virtual_gamepad_info = NULL; + SDL_steam_virtual_gamepad_info_count = 0; +} + +static void AddVirtualGamepadInfo(int slot, SDL_SteamVirtualGamepadInfo *info) +{ + SDL_SteamVirtualGamepadInfo *new_info; + + SDL_AssertJoysticksLocked(); + + if (slot < 0) { + return; + } + + if (slot >= SDL_steam_virtual_gamepad_info_count) { + SDL_SteamVirtualGamepadInfo **slots = (SDL_SteamVirtualGamepadInfo **)SDL_realloc(SDL_steam_virtual_gamepad_info, (slot + 1)*sizeof(*SDL_steam_virtual_gamepad_info)); + if (!slots) { + return; + } + while (SDL_steam_virtual_gamepad_info_count <= slot) { + slots[SDL_steam_virtual_gamepad_info_count++] = NULL; + } + SDL_steam_virtual_gamepad_info = slots; + } + + if (SDL_steam_virtual_gamepad_info[slot]) { + // We already have this slot info + return; + } + + new_info = (SDL_SteamVirtualGamepadInfo *)SDL_malloc(sizeof(*new_info)); + if (!new_info) { + return; + } + SDL_copyp(new_info, info); + SDL_steam_virtual_gamepad_info[slot] = new_info; + SDL_zerop(info); +} + +void SDL_InitSteamVirtualGamepadInfo(void) +{ + const char *file; + + SDL_AssertJoysticksLocked(); + + // The file isn't available inside the macOS sandbox + if (SDL_GetSandbox() == SDL_SANDBOX_MACOS) { + return; + } + + file = SDL_GetHint(SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE); + if (file && *file) { + SDL_steam_virtual_gamepad_info_file = SDL_strdup(file); + } + SDL_UpdateSteamVirtualGamepadInfo(); +} + +bool SDL_SteamVirtualGamepadEnabled(void) +{ + SDL_AssertJoysticksLocked(); + + return (SDL_steam_virtual_gamepad_info != NULL); +} + +bool SDL_UpdateSteamVirtualGamepadInfo(void) +{ + const int UPDATE_CHECK_INTERVAL_MS = 3000; + Uint64 now; + Uint64 mtime; + char *data, *end, *next, *line, *value; + size_t size; + int slot, new_slot; + SDL_SteamVirtualGamepadInfo info; + + SDL_AssertJoysticksLocked(); + + if (!SDL_steam_virtual_gamepad_info_file) { + return false; + } + + now = SDL_GetTicks(); + if (SDL_steam_virtual_gamepad_info_check_time && + now < (SDL_steam_virtual_gamepad_info_check_time + UPDATE_CHECK_INTERVAL_MS)) { + return false; + } + SDL_steam_virtual_gamepad_info_check_time = now; + + mtime = GetFileModificationTime(SDL_steam_virtual_gamepad_info_file); + if (mtime == 0 || mtime == SDL_steam_virtual_gamepad_info_file_mtime) { + return false; + } + + data = (char *)SDL_LoadFile(SDL_steam_virtual_gamepad_info_file, &size); + if (!data) { + return false; + } + + SDL_FreeSteamVirtualGamepadInfo(); + + slot = -1; + SDL_zero(info); + + for (next = data, end = data + size; next < end; ) { + while (next < end && (*next == '\0' || *next == '\r' || *next == '\n')) { + ++next; + } + + line = next; + + while (next < end && (*next != '\r' && *next != '\n')) { + ++next; + } + *next = '\0'; + + if (SDL_sscanf(line, "[slot %d]", &new_slot) == 1) { + if (slot >= 0) { + AddVirtualGamepadInfo(slot, &info); + } + slot = new_slot; + } else { + value = SDL_strchr(line, '='); + if (value) { + *value++ = '\0'; + + if (SDL_strcmp(line, "name") == 0) { + SDL_free(info.name); + info.name = SDL_strdup(value); + } else if (SDL_strcmp(line, "VID") == 0) { + info.vendor_id = (Uint16)SDL_strtoul(value, NULL, 0); + } else if (SDL_strcmp(line, "PID") == 0) { + info.product_id = (Uint16)SDL_strtoul(value, NULL, 0); + } else if (SDL_strcmp(line, "type") == 0) { + info.type = SDL_GetGamepadTypeFromString(value); + } else if (SDL_strcmp(line, "handle") == 0) { + info.handle = (Uint64)SDL_strtoull(value, NULL, 0); + } + } + } + } + if (slot >= 0) { + AddVirtualGamepadInfo(slot, &info); + } + SDL_free(info.name); + SDL_free(data); + + SDL_steam_virtual_gamepad_info_file_mtime = mtime; + + return true; +} + +const SDL_SteamVirtualGamepadInfo *SDL_GetSteamVirtualGamepadInfo(int slot) +{ + SDL_AssertJoysticksLocked(); + + if (slot < 0 || slot >= SDL_steam_virtual_gamepad_info_count) { + return NULL; + } + return SDL_steam_virtual_gamepad_info[slot]; +} + +void SDL_QuitSteamVirtualGamepadInfo(void) +{ + SDL_AssertJoysticksLocked(); + + if (SDL_steam_virtual_gamepad_info_file) { + SDL_FreeSteamVirtualGamepadInfo(); + SDL_free(SDL_steam_virtual_gamepad_info_file); + SDL_steam_virtual_gamepad_info_file = NULL; + } +} diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.h b/contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.h new file mode 100644 index 0000000..65696ea --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.h @@ -0,0 +1,36 @@ +/* + 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" + +typedef struct SDL_SteamVirtualGamepadInfo +{ + Uint64 handle; + char *name; + Uint16 vendor_id; + Uint16 product_id; + SDL_GamepadType type; +} SDL_SteamVirtualGamepadInfo; + +void SDL_InitSteamVirtualGamepadInfo(void); +bool SDL_SteamVirtualGamepadEnabled(void); +bool SDL_UpdateSteamVirtualGamepadInfo(void); +const SDL_SteamVirtualGamepadInfo *SDL_GetSteamVirtualGamepadInfo(int slot); +void SDL_QuitSteamVirtualGamepadInfo(void); diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_sysjoystick.h b/contrib/SDL-3.2.8/src/joystick/SDL_sysjoystick.h new file mode 100644 index 0000000..343d206 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/SDL_sysjoystick.h @@ -0,0 +1,269 @@ +/* + 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" + +#ifndef SDL_sysjoystick_h_ +#define SDL_sysjoystick_h_ + +// This is the system specific header for the SDL joystick API +#include "SDL_joystick_c.h" + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +// The SDL joystick structure + +typedef struct SDL_JoystickAxisInfo +{ + Sint16 initial_value; // Initial axis state + Sint16 value; // Current axis state + Sint16 zero; // Zero point on the axis (-32768 for triggers) + bool has_initial_value; // Whether we've seen a value on the axis yet + bool has_second_value; // Whether we've seen a second value on the axis yet + bool sent_initial_value; // Whether we've sent the initial axis value + bool sending_initial_value; // Whether we are sending the initial axis value +} SDL_JoystickAxisInfo; + +typedef struct SDL_JoystickBallData +{ + int dx; + int dy; +} SDL_JoystickBallData; + +typedef struct SDL_JoystickTouchpadFingerInfo +{ + bool down; + float x; + float y; + float pressure; +} SDL_JoystickTouchpadFingerInfo; + +typedef struct SDL_JoystickTouchpadInfo +{ + int nfingers; + SDL_JoystickTouchpadFingerInfo *fingers; +} SDL_JoystickTouchpadInfo; + +typedef struct SDL_JoystickSensorInfo +{ + SDL_SensorType type; + bool enabled; + float rate; + float data[3]; // If this needs to expand, update SDL_GamepadSensorEvent +} SDL_JoystickSensorInfo; + +#define _guarded SDL_GUARDED_BY(SDL_joystick_lock) + +struct SDL_Joystick +{ + SDL_JoystickID instance_id _guarded; // Device instance, monotonically increasing from 0 + char *name _guarded; // Joystick name - system dependent + char *path _guarded; // Joystick path - system dependent + char *serial _guarded; // Joystick serial + SDL_GUID guid _guarded; // Joystick guid + Uint16 firmware_version _guarded; // Firmware version, if available + Uint64 steam_handle _guarded; // Steam controller API handle + + int naxes _guarded; // Number of axis controls on the joystick + SDL_JoystickAxisInfo *axes _guarded; + + int nballs _guarded; // Number of trackballs on the joystick + SDL_JoystickBallData *balls _guarded; // Current ball motion deltas + + int nhats _guarded; // Number of hats on the joystick + Uint8 *hats _guarded; // Current hat states + + int nbuttons _guarded; // Number of buttons on the joystick + bool *buttons _guarded; // Current button states + + int ntouchpads _guarded; // Number of touchpads on the joystick + SDL_JoystickTouchpadInfo *touchpads _guarded; // Current touchpad states + + int nsensors _guarded; // Number of sensors on the joystick + int nsensors_enabled _guarded; + SDL_JoystickSensorInfo *sensors _guarded; + + Uint16 low_frequency_rumble _guarded; + Uint16 high_frequency_rumble _guarded; + Uint64 rumble_expiration _guarded; + Uint64 rumble_resend _guarded; + + Uint16 left_trigger_rumble _guarded; + Uint16 right_trigger_rumble _guarded; + Uint64 trigger_rumble_expiration _guarded; + + Uint8 led_red _guarded; + Uint8 led_green _guarded; + Uint8 led_blue _guarded; + Uint64 led_expiration _guarded; + + bool attached _guarded; + SDL_JoystickConnectionState connection_state _guarded; + SDL_PowerState battery_state _guarded; + int battery_percent _guarded; + + bool delayed_guide_button _guarded; // true if this device has the guide button event delayed + + SDL_SensorID accel_sensor _guarded; + SDL_Sensor *accel _guarded; + SDL_SensorID gyro_sensor _guarded; + SDL_Sensor *gyro _guarded; + float sensor_transform[3][3] _guarded; + + Uint64 update_complete _guarded; + + struct SDL_JoystickDriver *driver _guarded; + + struct joystick_hwdata *hwdata _guarded; // Driver dependent information + + SDL_PropertiesID props _guarded; + + int ref_count _guarded; // Reference count for multiple opens + + struct SDL_Joystick *next _guarded; // pointer to next joystick we have allocated +}; + +#undef _guarded + +// Device bus definitions +#define SDL_HARDWARE_BUS_UNKNOWN 0x00 +#define SDL_HARDWARE_BUS_USB 0x03 +#define SDL_HARDWARE_BUS_BLUETOOTH 0x05 +#define SDL_HARDWARE_BUS_VIRTUAL 0xFF + +// Macro to combine a USB vendor ID and product ID into a single Uint32 value +#define MAKE_VIDPID(VID, PID) (((Uint32)(VID)) << 16 | (PID)) + +typedef struct SDL_JoystickDriver +{ + /* Function to scan the system for joysticks. + * Joystick 0 should be the system default joystick. + * This function should return 0, or -1 on an unrecoverable error. + */ + bool (*Init)(void); + + // Function to return the number of joystick devices plugged in right now + int (*GetCount)(void); + + // Function to cause any queued joystick insertions to be processed + void (*Detect)(void); + + // Function to determine whether a device is currently detected by this driver + bool (*IsDevicePresent)(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name); + + // Function to get the device-dependent name of a joystick + const char *(*GetDeviceName)(int device_index); + + // Function to get the device-dependent path of a joystick + const char *(*GetDevicePath)(int device_index); + + // Function to get the Steam virtual gamepad slot of a joystick + int (*GetDeviceSteamVirtualGamepadSlot)(int device_index); + + // Function to get the player index of a joystick + int (*GetDevicePlayerIndex)(int device_index); + + // Function to set the player index of a joystick + void (*SetDevicePlayerIndex)(int device_index, int player_index); + + // Function to return the stable GUID for a plugged in device + SDL_GUID (*GetDeviceGUID)(int device_index); + + // Function to get the current instance id of the joystick located at device_index + SDL_JoystickID (*GetDeviceInstanceID)(int device_index); + + /* Function to open a joystick for use. + The joystick to open is specified by the device index. + This should fill the nbuttons and naxes fields of the joystick structure. + It returns 0, or -1 if there is an error. + */ + bool (*Open)(SDL_Joystick *joystick, int device_index); + + // Rumble functionality + bool (*Rumble)(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); + bool (*RumbleTriggers)(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble); + + // LED functionality + bool (*SetLED)(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue); + + // General effects + bool (*SendEffect)(SDL_Joystick *joystick, const void *data, int size); + + // Sensor functionality + bool (*SetSensorsEnabled)(SDL_Joystick *joystick, bool enabled); + + /* Function to update the state of a joystick - called as a device poll. + * This function shouldn't update the joystick structure directly, + * but instead should call SDL_PrivateJoystick*() to deliver events + * and update joystick device state. + */ + void (*Update)(SDL_Joystick *joystick); + + // Function to close a joystick after use + void (*Close)(SDL_Joystick *joystick); + + // Function to perform any system-specific joystick related cleanup + void (*Quit)(void); + + // Function to get the autodetected controller mapping; returns false if there isn't any. + bool (*GetGamepadMapping)(int device_index, SDL_GamepadMapping *out); + +} SDL_JoystickDriver; + +// Windows and Mac OSX has a limit of MAX_DWORD / 1000, Linux kernel has a limit of 0xFFFF +#define SDL_MAX_RUMBLE_DURATION_MS 0xFFFF + +/* Dualshock4 only rumbles for about 5 seconds max, resend rumble command every 2 seconds + * to make long rumble work. */ +#define SDL_RUMBLE_RESEND_MS 2000 + +#define SDL_LED_MIN_REPEAT_MS 5000 + +// The available joystick drivers +extern SDL_JoystickDriver SDL_PRIVATE_JoystickDriver; +extern SDL_JoystickDriver SDL_ANDROID_JoystickDriver; +extern SDL_JoystickDriver SDL_BSD_JoystickDriver; +extern SDL_JoystickDriver SDL_DARWIN_JoystickDriver; +extern SDL_JoystickDriver SDL_DUMMY_JoystickDriver; +extern SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver; +extern SDL_JoystickDriver SDL_HAIKU_JoystickDriver; +extern SDL_JoystickDriver SDL_HIDAPI_JoystickDriver; +extern SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver; +extern SDL_JoystickDriver SDL_IOS_JoystickDriver; +extern SDL_JoystickDriver SDL_LINUX_JoystickDriver; +extern SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver; +extern SDL_JoystickDriver SDL_WGI_JoystickDriver; +extern SDL_JoystickDriver SDL_WINDOWS_JoystickDriver; +extern SDL_JoystickDriver SDL_WINMM_JoystickDriver; +extern SDL_JoystickDriver SDL_PS2_JoystickDriver; +extern SDL_JoystickDriver SDL_PSP_JoystickDriver; +extern SDL_JoystickDriver SDL_VITA_JoystickDriver; +extern SDL_JoystickDriver SDL_N3DS_JoystickDriver; +extern SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver; + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif + +#endif // SDL_sysjoystick_h_ diff --git a/contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick.c new file mode 100644 index 0000000..9a3402e --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick.c @@ -0,0 +1,681 @@ +/* + 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_JOYSTICK_ANDROID + +#include // For the definition of NULL + +#include "SDL_sysjoystick_c.h" +#include "../SDL_joystick_c.h" +#include "../../events/SDL_keyboard_c.h" +#include "../../core/android/SDL_android.h" +#include "../hidapi/SDL_hidapijoystick_c.h" + +#include "android/keycodes.h" + +// As of platform android-14, android/keycodes.h is missing these defines +#ifndef AKEYCODE_BUTTON_1 +#define AKEYCODE_BUTTON_1 188 +#define AKEYCODE_BUTTON_2 189 +#define AKEYCODE_BUTTON_3 190 +#define AKEYCODE_BUTTON_4 191 +#define AKEYCODE_BUTTON_5 192 +#define AKEYCODE_BUTTON_6 193 +#define AKEYCODE_BUTTON_7 194 +#define AKEYCODE_BUTTON_8 195 +#define AKEYCODE_BUTTON_9 196 +#define AKEYCODE_BUTTON_10 197 +#define AKEYCODE_BUTTON_11 198 +#define AKEYCODE_BUTTON_12 199 +#define AKEYCODE_BUTTON_13 200 +#define AKEYCODE_BUTTON_14 201 +#define AKEYCODE_BUTTON_15 202 +#define AKEYCODE_BUTTON_16 203 +#endif + +#define ANDROID_MAX_NBUTTONS 36 + +static SDL_joylist_item *JoystickByDeviceId(int device_id); + +static SDL_joylist_item *SDL_joylist = NULL; +static SDL_joylist_item *SDL_joylist_tail = NULL; +static int numjoysticks = 0; + +/* Function to convert Android keyCodes into SDL ones. + * This code manipulation is done to get a sequential list of codes. + * FIXME: This is only suited for the case where we use a fixed number of buttons determined by ANDROID_MAX_NBUTTONS + */ +static int keycode_to_SDL(int keycode) +{ + // FIXME: If this function gets too unwieldy in the future, replace with a lookup table + int button = 0; + switch (keycode) { + // Some gamepad buttons (API 9) + case AKEYCODE_BUTTON_A: + button = SDL_GAMEPAD_BUTTON_SOUTH; + break; + case AKEYCODE_BUTTON_B: + button = SDL_GAMEPAD_BUTTON_EAST; + break; + case AKEYCODE_BUTTON_X: + button = SDL_GAMEPAD_BUTTON_WEST; + break; + case AKEYCODE_BUTTON_Y: + button = SDL_GAMEPAD_BUTTON_NORTH; + break; + case AKEYCODE_BUTTON_L1: + button = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER; + break; + case AKEYCODE_BUTTON_R1: + button = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER; + break; + case AKEYCODE_BUTTON_THUMBL: + button = SDL_GAMEPAD_BUTTON_LEFT_STICK; + break; + case AKEYCODE_BUTTON_THUMBR: + button = SDL_GAMEPAD_BUTTON_RIGHT_STICK; + break; + case AKEYCODE_MENU: + case AKEYCODE_BUTTON_START: + button = SDL_GAMEPAD_BUTTON_START; + break; + case AKEYCODE_BACK: + case AKEYCODE_BUTTON_SELECT: + button = SDL_GAMEPAD_BUTTON_BACK; + break; + case AKEYCODE_BUTTON_MODE: + button = SDL_GAMEPAD_BUTTON_GUIDE; + break; + case AKEYCODE_BUTTON_L2: + button = 15; + break; + case AKEYCODE_BUTTON_R2: + button = 16; + break; + case AKEYCODE_BUTTON_C: + button = 17; + break; + case AKEYCODE_BUTTON_Z: + button = 18; + break; + + // D-Pad key codes (API 1) + case AKEYCODE_DPAD_UP: + button = SDL_GAMEPAD_BUTTON_DPAD_UP; + break; + case AKEYCODE_DPAD_DOWN: + button = SDL_GAMEPAD_BUTTON_DPAD_DOWN; + break; + case AKEYCODE_DPAD_LEFT: + button = SDL_GAMEPAD_BUTTON_DPAD_LEFT; + break; + case AKEYCODE_DPAD_RIGHT: + button = SDL_GAMEPAD_BUTTON_DPAD_RIGHT; + break; + case AKEYCODE_DPAD_CENTER: + // This is handled better by applications as the A button + // button = 19; + button = SDL_GAMEPAD_BUTTON_SOUTH; + break; + + // More gamepad buttons (API 12), these get mapped to 20...35 + case AKEYCODE_BUTTON_1: + case AKEYCODE_BUTTON_2: + case AKEYCODE_BUTTON_3: + case AKEYCODE_BUTTON_4: + case AKEYCODE_BUTTON_5: + case AKEYCODE_BUTTON_6: + case AKEYCODE_BUTTON_7: + case AKEYCODE_BUTTON_8: + case AKEYCODE_BUTTON_9: + case AKEYCODE_BUTTON_10: + case AKEYCODE_BUTTON_11: + case AKEYCODE_BUTTON_12: + case AKEYCODE_BUTTON_13: + case AKEYCODE_BUTTON_14: + case AKEYCODE_BUTTON_15: + case AKEYCODE_BUTTON_16: + button = 20 + (keycode - AKEYCODE_BUTTON_1); + break; + + default: + return -1; + // break; -Wunreachable-code-break + } + + /* This is here in case future generations, probably with six fingers per hand, + * happily add new cases up above and forget to update the max number of buttons. + */ + SDL_assert(button < ANDROID_MAX_NBUTTONS); + return button; +} + +static SDL_Scancode button_to_scancode(int button) +{ + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + return SDL_SCANCODE_RETURN; + case SDL_GAMEPAD_BUTTON_EAST: + return SDL_SCANCODE_ESCAPE; + case SDL_GAMEPAD_BUTTON_BACK: + return SDL_SCANCODE_ESCAPE; + case SDL_GAMEPAD_BUTTON_START: + return SDL_SCANCODE_MENU; + case SDL_GAMEPAD_BUTTON_DPAD_UP: + return SDL_SCANCODE_UP; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + return SDL_SCANCODE_DOWN; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + return SDL_SCANCODE_LEFT; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + return SDL_SCANCODE_RIGHT; + } + + // Unsupported button + return SDL_SCANCODE_UNKNOWN; +} + +bool Android_OnPadDown(int device_id, int keycode) +{ + Uint64 timestamp = SDL_GetTicksNS(); + SDL_joylist_item *item; + int button = keycode_to_SDL(keycode); + if (button >= 0) { + SDL_LockJoysticks(); + item = JoystickByDeviceId(device_id); + if (item && item->joystick) { + SDL_SendJoystickButton(timestamp, item->joystick, button, true); + } else { + SDL_SendKeyboardKey(timestamp, SDL_GLOBAL_KEYBOARD_ID, keycode, button_to_scancode(button), true); + } + SDL_UnlockJoysticks(); + return true; + } + + return false; +} + +bool Android_OnPadUp(int device_id, int keycode) +{ + Uint64 timestamp = SDL_GetTicksNS(); + SDL_joylist_item *item; + int button = keycode_to_SDL(keycode); + if (button >= 0) { + SDL_LockJoysticks(); + item = JoystickByDeviceId(device_id); + if (item && item->joystick) { + SDL_SendJoystickButton(timestamp, item->joystick, button, false); + } else { + SDL_SendKeyboardKey(timestamp, SDL_GLOBAL_KEYBOARD_ID, keycode, button_to_scancode(button), false); + } + SDL_UnlockJoysticks(); + return true; + } + + return false; +} + +bool Android_OnJoy(int device_id, int axis, float value) +{ + Uint64 timestamp = SDL_GetTicksNS(); + // Android gives joy info normalized as [-1.0, 1.0] or [0.0, 1.0] + SDL_joylist_item *item; + + SDL_LockJoysticks(); + item = JoystickByDeviceId(device_id); + if (item && item->joystick) { + SDL_SendJoystickAxis(timestamp, item->joystick, axis, (Sint16)(32767. * value)); + } + SDL_UnlockJoysticks(); + + return true; +} + +bool Android_OnHat(int device_id, int hat_id, int x, int y) +{ + Uint64 timestamp = SDL_GetTicksNS(); + const int DPAD_UP_MASK = (1 << SDL_GAMEPAD_BUTTON_DPAD_UP); + const int DPAD_DOWN_MASK = (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN); + const int DPAD_LEFT_MASK = (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT); + const int DPAD_RIGHT_MASK = (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT); + + if (x >= -1 && x <= 1 && y >= -1 && y <= 1) { + SDL_joylist_item *item; + + SDL_LockJoysticks(); + item = JoystickByDeviceId(device_id); + if (item && item->joystick) { + int dpad_state = 0; + int dpad_delta; + if (x < 0) { + dpad_state |= DPAD_LEFT_MASK; + } else if (x > 0) { + dpad_state |= DPAD_RIGHT_MASK; + } + if (y < 0) { + dpad_state |= DPAD_UP_MASK; + } else if (y > 0) { + dpad_state |= DPAD_DOWN_MASK; + } + + dpad_delta = (dpad_state ^ item->dpad_state); + if (dpad_delta) { + if (dpad_delta & DPAD_UP_MASK) { + bool down = ((dpad_state & DPAD_UP_MASK) != 0); + SDL_SendJoystickButton(timestamp, item->joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, down); + } + if (dpad_delta & DPAD_DOWN_MASK) { + bool down = ((dpad_state & DPAD_DOWN_MASK) != 0); + SDL_SendJoystickButton(timestamp, item->joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, down); + } + if (dpad_delta & DPAD_LEFT_MASK) { + bool down = ((dpad_state & DPAD_LEFT_MASK) != 0); + SDL_SendJoystickButton(timestamp, item->joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, down); + } + if (dpad_delta & DPAD_RIGHT_MASK) { + bool down = ((dpad_state & DPAD_RIGHT_MASK) != 0); + SDL_SendJoystickButton(timestamp, item->joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, down); + } + item->dpad_state = dpad_state; + } + } + SDL_UnlockJoysticks(); + return true; + } + + return false; +} + +void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble) +{ + SDL_joylist_item *item; + SDL_GUID guid; + int i; + + SDL_LockJoysticks(); + + if (!SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, true)) { + // Ignore devices that aren't actually controllers (e.g. remotes), they'll be handled as keyboard input + if (naxes < 2 && nhats < 1) { + goto done; + } + } + + if (JoystickByDeviceId(device_id) != NULL || !name) { + goto done; + } + + if (SDL_JoystickHandledByAnotherDriver(&SDL_ANDROID_JoystickDriver, vendor_id, product_id, 0, name)) { + goto done; + } + +#ifdef DEBUG_JOYSTICK + SDL_Log("Joystick: %s, descriptor %s, vendor = 0x%.4x, product = 0x%.4x, %d axes, %d hats", name, desc, vendor_id, product_id, naxes, nhats); +#endif + + if (nhats > 0) { + // Hat is translated into DPAD buttons + button_mask |= ((1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | + (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | + (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT) | + (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT)); + nhats = 0; + } + + guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor_id, product_id, 0, NULL, desc, 0, 0); + + // Update the GUID with capability bits + { + Uint16 *guid16 = (Uint16 *)guid.data; + guid16[6] = SDL_Swap16LE(button_mask); + guid16[7] = SDL_Swap16LE(axis_mask); + } + + item = (SDL_joylist_item *)SDL_malloc(sizeof(SDL_joylist_item)); + if (!item) { + goto done; + } + + SDL_zerop(item); + item->guid = guid; + item->device_id = device_id; + item->name = SDL_CreateJoystickName(vendor_id, product_id, NULL, name); + if (!item->name) { + SDL_free(item); + goto done; + } + + if (button_mask == 0xFFFFFFFF) { + item->nbuttons = ANDROID_MAX_NBUTTONS; + } else { + for (i = 0; i < sizeof(button_mask) * 8; ++i) { + if (button_mask & (1 << i)) { + item->nbuttons = i + 1; + } + } + } + item->naxes = naxes; + item->nhats = nhats; + item->can_rumble = can_rumble; + item->device_instance = SDL_GetNextObjectID(); + if (!SDL_joylist_tail) { + SDL_joylist = SDL_joylist_tail = item; + } else { + SDL_joylist_tail->next = item; + SDL_joylist_tail = item; + } + + // Need to increment the joystick count before we post the event + ++numjoysticks; + + SDL_PrivateJoystickAdded(item->device_instance); + +#ifdef DEBUG_JOYSTICK + SDL_Log("Added joystick %s with device_id %d", item->name, device_id); +#endif + +done: + SDL_UnlockJoysticks(); +} + +void Android_RemoveJoystick(int device_id) +{ + SDL_joylist_item *item = SDL_joylist; + SDL_joylist_item *prev = NULL; + + SDL_LockJoysticks(); + + // Don't call JoystickByDeviceId here or there'll be an infinite loop! + while (item) { + if (item->device_id == device_id) { + break; + } + prev = item; + item = item->next; + } + + if (!item) { + goto done; + } + + if (item->joystick) { + item->joystick->hwdata = NULL; + } + + if (prev) { + prev->next = item->next; + } else { + SDL_assert(SDL_joylist == item); + SDL_joylist = item->next; + } + if (item == SDL_joylist_tail) { + SDL_joylist_tail = prev; + } + + // Need to decrement the joystick count before we post the event + --numjoysticks; + + SDL_PrivateJoystickRemoved(item->device_instance); + +#ifdef DEBUG_JOYSTICK + SDL_Log("Removed joystick with device_id %d", device_id); +#endif + + SDL_free(item->name); + SDL_free(item); + +done: + SDL_UnlockJoysticks(); +} + +static void ANDROID_JoystickDetect(void); + +static bool ANDROID_JoystickInit(void) +{ + ANDROID_JoystickDetect(); + return true; +} + +static int ANDROID_JoystickGetCount(void) +{ + return numjoysticks; +} + +static void ANDROID_JoystickDetect(void) +{ + /* Support for device connect/disconnect is API >= 16 only, + * so we poll every three seconds + * Ref: http://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html + */ + static Uint64 timeout = 0; + Uint64 now = SDL_GetTicks(); + if (!timeout || now >= timeout) { + timeout = now + 3000; + Android_JNI_PollInputDevices(); + } +} + +static bool ANDROID_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers + return false; +} + +static SDL_joylist_item *GetJoystickByDevIndex(int device_index) +{ + SDL_joylist_item *item = SDL_joylist; + + if ((device_index < 0) || (device_index >= numjoysticks)) { + return NULL; + } + + while (device_index > 0) { + SDL_assert(item != NULL); + device_index--; + item = item->next; + } + + return item; +} + +static SDL_joylist_item *JoystickByDeviceId(int device_id) +{ + SDL_joylist_item *item = SDL_joylist; + + while (item) { + if (item->device_id == device_id) { + return item; + } + item = item->next; + } + + // Joystick not found, try adding it + ANDROID_JoystickDetect(); + + while (item) { + if (item->device_id == device_id) { + return item; + } + item = item->next; + } + + return NULL; +} + +static const char *ANDROID_JoystickGetDeviceName(int device_index) +{ + return GetJoystickByDevIndex(device_index)->name; +} + +static const char *ANDROID_JoystickGetDevicePath(int device_index) +{ + return NULL; +} + +static int ANDROID_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + +static int ANDROID_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void ANDROID_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static SDL_GUID ANDROID_JoystickGetDeviceGUID(int device_index) +{ + return GetJoystickByDevIndex(device_index)->guid; +} + +static SDL_JoystickID ANDROID_JoystickGetDeviceInstanceID(int device_index) +{ + return GetJoystickByDevIndex(device_index)->device_instance; +} + +static bool ANDROID_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + SDL_joylist_item *item = GetJoystickByDevIndex(device_index); + + if (!item) { + return SDL_SetError("No such device"); + } + + if (item->joystick) { + return SDL_SetError("Joystick already opened"); + } + + joystick->hwdata = (struct joystick_hwdata *)item; + item->joystick = joystick; + joystick->nhats = item->nhats; + joystick->nbuttons = item->nbuttons; + joystick->naxes = item->naxes; + + if (item->can_rumble) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } + + return true; +} + +static bool ANDROID_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; + if (!item) { + return SDL_SetError("Rumble failed, device disconnected"); + } + if (!item->can_rumble) { + return SDL_Unsupported(); + } + + float low_frequency_intensity = (float)low_frequency_rumble / SDL_MAX_UINT16; + float high_frequency_intensity = (float)high_frequency_rumble / SDL_MAX_UINT16; + Android_JNI_HapticRumble(item->device_id, low_frequency_intensity, high_frequency_intensity, 5000); + return true; +} + +static bool ANDROID_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static bool ANDROID_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool ANDROID_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool ANDROID_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void ANDROID_JoystickUpdate(SDL_Joystick *joystick) +{ +} + +static void ANDROID_JoystickClose(SDL_Joystick *joystick) +{ + SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; + if (item) { + item->joystick = NULL; + } +} + +static void ANDROID_JoystickQuit(void) +{ +/* We don't have any way to scan for joysticks at init, so don't wipe the list + * of joysticks here in case this is a reinit. + */ +#if 0 + SDL_joylist_item *item = NULL; + SDL_joylist_item *next = NULL; + + for (item = SDL_joylist; item; item = next) { + next = item->next; + SDL_free(item->name); + SDL_free(item); + } + + SDL_joylist = SDL_joylist_tail = NULL; + + numjoysticks = 0; +#endif // 0 +} + +static bool ANDROID_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +SDL_JoystickDriver SDL_ANDROID_JoystickDriver = { + ANDROID_JoystickInit, + ANDROID_JoystickGetCount, + ANDROID_JoystickDetect, + ANDROID_JoystickIsDevicePresent, + ANDROID_JoystickGetDeviceName, + ANDROID_JoystickGetDevicePath, + ANDROID_JoystickGetDeviceSteamVirtualGamepadSlot, + ANDROID_JoystickGetDevicePlayerIndex, + ANDROID_JoystickSetDevicePlayerIndex, + ANDROID_JoystickGetDeviceGUID, + ANDROID_JoystickGetDeviceInstanceID, + ANDROID_JoystickOpen, + ANDROID_JoystickRumble, + ANDROID_JoystickRumbleTriggers, + ANDROID_JoystickSetLED, + ANDROID_JoystickSendEffect, + ANDROID_JoystickSetSensorsEnabled, + ANDROID_JoystickUpdate, + ANDROID_JoystickClose, + ANDROID_JoystickQuit, + ANDROID_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_ANDROID diff --git a/contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick_c.h new file mode 100644 index 0000000..febb228 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick_c.h @@ -0,0 +1,57 @@ +/* + 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_JOYSTICK_ANDROID + +#ifndef SDL_sysjoystick_c_h_ +#define SDL_sysjoystick_c_h_ + +#include "../SDL_sysjoystick.h" + +extern bool Android_OnPadDown(int device_id, int keycode); +extern bool Android_OnPadUp(int device_id, int keycode); +extern bool Android_OnJoy(int device_id, int axisnum, float value); +extern bool Android_OnHat(int device_id, int hat_id, int x, int y); +extern void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble); +extern void Android_RemoveJoystick(int device_id); + +// A linked list of available joysticks +typedef struct SDL_joylist_item +{ + int device_instance; + int device_id; // Android's device id + char *name; // "SideWinder 3D Pro" or whatever + SDL_GUID guid; + SDL_Joystick *joystick; + int nbuttons, naxes, nhats; + int dpad_state; + bool can_rumble; + + struct SDL_joylist_item *next; +} SDL_joylist_item; + +typedef SDL_joylist_item joystick_hwdata; + +#endif // SDL_sysjoystick_c_h_ + +#endif // SDL_JOYSTICK_ANDROID diff --git a/contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick.m b/contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick.m new file mode 100644 index 0000000..811a9f1 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick.m @@ -0,0 +1,1946 @@ +/* + 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" + +// This is the iOS implementation of the SDL joystick API +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" +#include "../hidapi/SDL_hidapijoystick_c.h" +#include "../usb_ids.h" +#include "../../events/SDL_events_c.h" + +#include "SDL_mfijoystick_c.h" + + +#if defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_TVOS) +#import +#endif + +#ifdef SDL_PLATFORM_MACOS +#include +#include +#ifndef NSAppKitVersionNumber10_15 +#define NSAppKitVersionNumber10_15 1894 +#endif +#endif // SDL_PLATFORM_MACOS + +#import + +#ifdef SDL_JOYSTICK_MFI +static id connectObserver = nil; +static id disconnectObserver = nil; + +#include + +// Fix build errors when using an older SDK by defining these selectors +@interface GCController (SDL) +#if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 140500) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140500) || (__MAC_OS_X_VERSION_MAX_ALLOWED >= 110300)) +@property(class, nonatomic, readwrite) BOOL shouldMonitorBackgroundEvents; +#endif +@end + +#import + +#endif // SDL_JOYSTICK_MFI + +static SDL_JoystickDeviceItem *deviceList = NULL; + +static int numjoysticks = 0; +int SDL_AppleTVRemoteOpenedAsJoystick = 0; + +static SDL_JoystickDeviceItem *GetDeviceForIndex(int device_index) +{ + SDL_JoystickDeviceItem *device = deviceList; + int i = 0; + + while (i < device_index) { + if (device == NULL) { + return NULL; + } + device = device->next; + i++; + } + + return device; +} + +#ifdef SDL_JOYSTICK_MFI +static bool IsControllerPS4(GCController *controller) +{ + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if ([controller.productCategory isEqualToString:@"DualShock 4"]) { + return true; + } + } else { + if ([controller.vendorName containsString:@"DUALSHOCK"]) { + return true; + } + } + return false; +} +static bool IsControllerPS5(GCController *controller) +{ + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if ([controller.productCategory isEqualToString:@"DualSense"]) { + return true; + } + } else { + if ([controller.vendorName containsString:@"DualSense"]) { + return true; + } + } + return false; +} +static bool IsControllerXbox(GCController *controller) +{ + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if ([controller.productCategory isEqualToString:@"Xbox One"]) { + return true; + } + } else { + if ([controller.vendorName containsString:@"Xbox"]) { + return true; + } + } + return false; +} +static bool IsControllerSwitchPro(GCController *controller) +{ + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if ([controller.productCategory isEqualToString:@"Switch Pro Controller"]) { + return true; + } + } + return false; +} +static bool IsControllerSwitchJoyConL(GCController *controller) +{ + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L)"]) { + return true; + } + } + return false; +} +static bool IsControllerSwitchJoyConR(GCController *controller) +{ + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (R)"]) { + return true; + } + } + return false; +} +static bool IsControllerSwitchJoyConPair(GCController *controller) +{ + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L/R)"]) { + return true; + } + } + return false; +} +static bool IsControllerStadia(GCController *controller) +{ + if ([controller.vendorName hasPrefix:@"Stadia"]) { + return true; + } + return false; +} +static bool IsControllerBackboneOne(GCController *controller) +{ + if ([controller.vendorName hasPrefix:@"Backbone One"]) { + return true; + } + return false; +} +static void CheckControllerSiriRemote(GCController *controller, int *is_siri_remote) +{ + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if ([controller.productCategory hasPrefix:@"Siri Remote"]) { + *is_siri_remote = 1; + SDL_sscanf(controller.productCategory.UTF8String, "Siri Remote (%i%*s Generation)", is_siri_remote); + return; + } + } + *is_siri_remote = 0; +} + +static bool ElementAlreadyHandled(SDL_JoystickDeviceItem *device, NSString *element, NSDictionary *elements) +{ + if ([element isEqualToString:@"Left Thumbstick Left"] || + [element isEqualToString:@"Left Thumbstick Right"]) { + if (elements[@"Left Thumbstick X Axis"]) { + return true; + } + } + if ([element isEqualToString:@"Left Thumbstick Up"] || + [element isEqualToString:@"Left Thumbstick Down"]) { + if (elements[@"Left Thumbstick Y Axis"]) { + return true; + } + } + if ([element isEqualToString:@"Right Thumbstick Left"] || + [element isEqualToString:@"Right Thumbstick Right"]) { + if (elements[@"Right Thumbstick X Axis"]) { + return true; + } + } + if ([element isEqualToString:@"Right Thumbstick Up"] || + [element isEqualToString:@"Right Thumbstick Down"]) { + if (elements[@"Right Thumbstick Y Axis"]) { + return true; + } + } + if (device->is_siri_remote) { + if ([element isEqualToString:@"Direction Pad Left"] || + [element isEqualToString:@"Direction Pad Right"]) { + if (elements[@"Direction Pad X Axis"]) { + return true; + } + } + if ([element isEqualToString:@"Direction Pad Up"] || + [element isEqualToString:@"Direction Pad Down"]) { + if (elements[@"Direction Pad Y Axis"]) { + return true; + } + } + } else { + if ([element isEqualToString:@"Direction Pad X Axis"]) { + if (elements[@"Direction Pad Left"] && + elements[@"Direction Pad Right"]) { + return true; + } + } + if ([element isEqualToString:@"Direction Pad Y Axis"]) { + if (elements[@"Direction Pad Up"] && + elements[@"Direction Pad Down"]) { + return true; + } + } + } + if ([element isEqualToString:@"Cardinal Direction Pad X Axis"]) { + if (elements[@"Cardinal Direction Pad Left"] && + elements[@"Cardinal Direction Pad Right"]) { + return true; + } + } + if ([element isEqualToString:@"Cardinal Direction Pad Y Axis"]) { + if (elements[@"Cardinal Direction Pad Up"] && + elements[@"Cardinal Direction Pad Down"]) { + return true; + } + } + if ([element isEqualToString:@"Touchpad 1 X Axis"] || + [element isEqualToString:@"Touchpad 1 Y Axis"] || + [element isEqualToString:@"Touchpad 1 Left"] || + [element isEqualToString:@"Touchpad 1 Right"] || + [element isEqualToString:@"Touchpad 1 Up"] || + [element isEqualToString:@"Touchpad 1 Down"] || + [element isEqualToString:@"Touchpad 2 X Axis"] || + [element isEqualToString:@"Touchpad 2 Y Axis"] || + [element isEqualToString:@"Touchpad 2 Left"] || + [element isEqualToString:@"Touchpad 2 Right"] || + [element isEqualToString:@"Touchpad 2 Up"] || + [element isEqualToString:@"Touchpad 2 Down"]) { + // The touchpad is handled separately + return true; + } + if ([element isEqualToString:@"Button Home"]) { + if (device->is_switch_joycon_pair) { + // The Nintendo Switch JoyCon home button doesn't ever show as being held down + return true; + } +#ifdef SDL_PLATFORM_TVOS + // The OS uses the home button, it's not available to apps + return true; +#endif + } + if ([element isEqualToString:@"Button Share"]) { + if (device->is_backbone_one) { + // The Backbone app uses share button + return true; + } + } + return false; +} + +static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller) +{ + Uint16 vendor = 0; + Uint16 product = 0; + Uint8 subtype = 0; + const char *name = NULL; + + if (@available(macOS 11.3, iOS 14.5, tvOS 14.5, *)) { + if (!GCController.shouldMonitorBackgroundEvents) { + GCController.shouldMonitorBackgroundEvents = YES; + } + } + + /* Explicitly retain the controller because SDL_JoystickDeviceItem is a + * struct, and ARC doesn't work with structs. */ + device->controller = (__bridge GCController *)CFBridgingRetain(controller); + + if (controller.vendorName) { + name = controller.vendorName.UTF8String; + } + + if (!name) { + name = "MFi Gamepad"; + } + + device->name = SDL_CreateJoystickName(0, 0, NULL, name); + +#ifdef DEBUG_CONTROLLER_PROFILE + NSLog(@"Product name: %@\n", controller.vendorName); + NSLog(@"Product category: %@\n", controller.productCategory); + NSLog(@"Elements available:\n"); + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + NSDictionary *elements = controller.physicalInputProfile.elements; + for (id key in controller.physicalInputProfile.buttons) { + NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital"); + } + for (id key in controller.physicalInputProfile.axes) { + NSLog(@"\tAxis: %@\n", key); + } + for (id key in controller.physicalInputProfile.dpads) { + NSLog(@"\tHat: %@\n", key); + } + } +#endif // DEBUG_CONTROLLER_PROFILE + + device->is_xbox = IsControllerXbox(controller); + device->is_ps4 = IsControllerPS4(controller); + device->is_ps5 = IsControllerPS5(controller); + device->is_switch_pro = IsControllerSwitchPro(controller); + device->is_switch_joycon_pair = IsControllerSwitchJoyConPair(controller); + device->is_stadia = IsControllerStadia(controller); + device->is_backbone_one = IsControllerBackboneOne(controller); + device->is_switch_joyconL = IsControllerSwitchJoyConL(controller); + device->is_switch_joyconR = IsControllerSwitchJoyConR(controller); +#ifdef SDL_JOYSTICK_HIDAPI + if ((device->is_xbox && (HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_XBOXONE) || + HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_XBOX360))) || + (device->is_ps4 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS4)) || + (device->is_ps5 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS5)) || + (device->is_switch_pro && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO)) || + (device->is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) || + (device->is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, "")) || + (device->is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) || + (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, ""))) { + // The HIDAPI driver is taking care of this device + return false; + } +#endif + if (device->is_xbox && SDL_strncmp(name, "GamePad-", 8) == 0) { + // This is a Steam Virtual Gamepad, which isn't supported by GCController + return false; + } + CheckControllerSiriRemote(controller, &device->is_siri_remote); + + if (device->is_siri_remote && !SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, true)) { + // Ignore remotes, they'll be handled as keyboard input + return false; + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) { + device->has_dualshock_touchpad = TRUE; + } + if (controller.physicalInputProfile.buttons[GCInputXboxPaddleOne] != nil) { + device->has_xbox_paddles = TRUE; + } + if (controller.physicalInputProfile.buttons[@"Button Share"] != nil) { + device->has_xbox_share_button = TRUE; + } + } + + if (device->is_backbone_one) { + vendor = USB_VENDOR_BACKBONE; + if (device->is_ps5) { + product = USB_PRODUCT_BACKBONE_ONE_IOS_PS5; + } else { + product = USB_PRODUCT_BACKBONE_ONE_IOS; + } + } else if (device->is_xbox) { + vendor = USB_VENDOR_MICROSOFT; + if (device->has_xbox_paddles) { + // Assume Xbox One Elite Series 2 Controller unless/until GCController flows VID/PID + product = USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH; + } else if (device->has_xbox_share_button) { + // Assume Xbox Series X Controller unless/until GCController flows VID/PID + product = USB_PRODUCT_XBOX_SERIES_X_BLE; + } else { + // Assume Xbox One S Bluetooth Controller unless/until GCController flows VID/PID + product = USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH; + } + } else if (device->is_ps4) { + // Assume DS4 Slim unless/until GCController flows VID/PID + vendor = USB_VENDOR_SONY; + product = USB_PRODUCT_SONY_DS4_SLIM; + if (device->has_dualshock_touchpad) { + subtype = 1; + } + } else if (device->is_ps5) { + vendor = USB_VENDOR_SONY; + product = USB_PRODUCT_SONY_DS5; + } else if (device->is_switch_pro) { + vendor = USB_VENDOR_NINTENDO; + product = USB_PRODUCT_NINTENDO_SWITCH_PRO; + device->has_nintendo_buttons = TRUE; + } else if (device->is_switch_joycon_pair) { + vendor = USB_VENDOR_NINTENDO; + product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; + device->has_nintendo_buttons = TRUE; + } else if (device->is_switch_joyconL) { + vendor = USB_VENDOR_NINTENDO; + product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT; + } else if (device->is_switch_joyconR) { + vendor = USB_VENDOR_NINTENDO; + product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT; + } else if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + vendor = USB_VENDOR_APPLE; + product = 4; + subtype = 4; + } else if (controller.extendedGamepad) { + vendor = USB_VENDOR_APPLE; + product = 1; + subtype = 1; +#ifdef SDL_PLATFORM_TVOS + } else if (controller.microGamepad) { + vendor = USB_VENDOR_APPLE; + product = 3; + subtype = 3; +#endif + } else { + // We don't know how to get input events from this device + return false; + } + + if (SDL_ShouldIgnoreJoystick(vendor, product, 0, name)) { + return false; + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + NSDictionary *elements = controller.physicalInputProfile.elements; + + // Provide both axes and analog buttons as SDL axes + NSArray *axes = [[[elements allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] + filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) { + if (ElementAlreadyHandled(device, (NSString *)object, elements)) { + return false; + } + + GCControllerElement *element = elements[object]; + if (element.analog) { + if ([element isKindOfClass:[GCControllerAxisInput class]] || + [element isKindOfClass:[GCControllerButtonInput class]]) { + return true; + } + } + return false; + }]]; + NSArray *buttons = [[[elements allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] + filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) { + if (ElementAlreadyHandled(device, (NSString *)object, elements)) { + return false; + } + + GCControllerElement *element = elements[object]; + if ([element isKindOfClass:[GCControllerButtonInput class]]) { + return true; + } + return false; + }]]; + /* Explicitly retain the arrays because SDL_JoystickDeviceItem is a + * struct, and ARC doesn't work with structs. */ + device->naxes = (int)axes.count; + device->axes = (__bridge NSArray *)CFBridgingRetain(axes); + device->nbuttons = (int)buttons.count; + device->buttons = (__bridge NSArray *)CFBridgingRetain(buttons); + subtype = 4; + +#ifdef DEBUG_CONTROLLER_PROFILE + NSLog(@"Elements used:\n", controller.vendorName); + for (id key in device->buttons) { + NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital"); + } + for (id key in device->axes) { + NSLog(@"\tAxis: %@\n", key); + } +#endif // DEBUG_CONTROLLER_PROFILE + +#ifdef SDL_PLATFORM_TVOS + // tvOS turns the menu button into a system gesture, so we grab it here instead + if (elements[GCInputButtonMenu] && !elements[@"Button Home"]) { + device->pause_button_index = (int)[device->buttons indexOfObject:GCInputButtonMenu]; + } +#endif + } else if (controller.extendedGamepad) { + GCExtendedGamepad *gamepad = controller.extendedGamepad; + int nbuttons = 0; + BOOL has_direct_menu = FALSE; + + // These buttons are part of the original MFi spec + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_WEST); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_NORTH); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER); + nbuttons += 6; + + // These buttons are available on some newer controllers + if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) { + if (gamepad.leftThumbstickButton) { + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK); + ++nbuttons; + } + if (gamepad.rightThumbstickButton) { + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK); + ++nbuttons; + } + } + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if (gamepad.buttonOptions) { + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_BACK); + ++nbuttons; + } + } + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_START); + ++nbuttons; + + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if (gamepad.buttonMenu) { + has_direct_menu = TRUE; + } + } +#ifdef SDL_PLATFORM_TVOS + // The single menu button isn't very reliable, at least as of tvOS 16.1 + if ((device->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) == 0) { + has_direct_menu = FALSE; + } +#endif + if (!has_direct_menu) { + device->pause_button_index = (nbuttons - 1); + } + + device->naxes = 6; // 2 thumbsticks and 2 triggers + device->nhats = 1; // d-pad + device->nbuttons = nbuttons; + } +#ifdef SDL_PLATFORM_TVOS + else if (controller.microGamepad) { + int nbuttons = 0; + + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH); + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_WEST); // Button X on microGamepad + device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST); + nbuttons += 3; + device->pause_button_index = (nbuttons - 1); + + device->naxes = 2; // treat the touch surface as two axes + device->nhats = 0; // apparently the touch surface-as-dpad is buggy + device->nbuttons = nbuttons; + + controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, false); + } +#endif + else { + // We don't know how to get input events from this device + return false; + } + + Uint16 signature; + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + signature = 0; + signature = SDL_crc16(signature, device->name, SDL_strlen(device->name)); + for (id key in device->axes) { + const char *string = ((NSString *)key).UTF8String; + signature = SDL_crc16(signature, string, SDL_strlen(string)); + } + for (id key in device->buttons) { + const char *string = ((NSString *)key).UTF8String; + signature = SDL_crc16(signature, string, SDL_strlen(string)); + } + } else { + signature = device->button_mask; + } + device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, signature, NULL, name, 'm', subtype); + + /* This will be set when the first button press of the controller is + * detected. */ + controller.playerIndex = -1; + return true; +} +#endif // SDL_JOYSTICK_MFI + +#ifdef SDL_JOYSTICK_MFI +static void IOS_AddJoystickDevice(GCController *controller) +{ + SDL_JoystickDeviceItem *device = deviceList; + + while (device != NULL) { + if (device->controller == controller) { + return; + } + device = device->next; + } + + device = (SDL_JoystickDeviceItem *)SDL_calloc(1, sizeof(SDL_JoystickDeviceItem)); + if (device == NULL) { + return; + } + + device->instance_id = SDL_GetNextObjectID(); + device->pause_button_index = -1; + + if (controller) { +#ifdef SDL_JOYSTICK_MFI + if (!IOS_AddMFIJoystickDevice(device, controller)) { + SDL_free(device->name); + SDL_free(device); + return; + } +#else + SDL_free(device); + return; +#endif // SDL_JOYSTICK_MFI + } + + if (deviceList == NULL) { + deviceList = device; + } else { + SDL_JoystickDeviceItem *lastdevice = deviceList; + while (lastdevice->next != NULL) { + lastdevice = lastdevice->next; + } + lastdevice->next = device; + } + + ++numjoysticks; + + SDL_PrivateJoystickAdded(device->instance_id); +} +#endif // SDL_JOYSTICK_MFI + +static SDL_JoystickDeviceItem *IOS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device) +{ + SDL_JoystickDeviceItem *prev = NULL; + SDL_JoystickDeviceItem *next = NULL; + SDL_JoystickDeviceItem *item = deviceList; + + if (device == NULL) { + return NULL; + } + + next = device->next; + + while (item != NULL) { + if (item == device) { + break; + } + prev = item; + item = item->next; + } + + // Unlink the device item from the device list. + if (prev) { + prev->next = device->next; + } else if (device == deviceList) { + deviceList = device->next; + } + + if (device->joystick) { + device->joystick->hwdata = NULL; + } + +#ifdef SDL_JOYSTICK_MFI + @autoreleasepool { + // These were explicitly retained in the struct, so they should be explicitly released before freeing the struct. + if (device->controller) { + GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller)); + controller.controllerPausedHandler = nil; + device->controller = nil; + } + if (device->axes) { + CFRelease((__bridge CFTypeRef)device->axes); + device->axes = nil; + } + if (device->buttons) { + CFRelease((__bridge CFTypeRef)device->buttons); + device->buttons = nil; + } + } +#endif // SDL_JOYSTICK_MFI + + --numjoysticks; + + SDL_PrivateJoystickRemoved(device->instance_id); + + SDL_free(device->name); + SDL_free(device); + + return next; +} + +#ifdef SDL_PLATFORM_TVOS +static void SDLCALL SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue) +{ + BOOL allowRotation = newValue != NULL && *newValue != '0'; + + @autoreleasepool { + for (GCController *controller in [GCController controllers]) { + if (controller.microGamepad) { + controller.microGamepad.allowsRotation = allowRotation; + } + } + } +} +#endif // SDL_PLATFORM_TVOS + +static bool IOS_JoystickInit(void) +{ + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_MFI, true)) { + return true; + } + +#ifdef SDL_PLATFORM_MACOS + if (@available(macOS 10.16, *)) { + // Continue with initialization on macOS 11+ + } else { + return true; + } +#endif + + @autoreleasepool { +#ifdef SDL_JOYSTICK_MFI + NSNotificationCenter *center; +#endif + +#ifdef SDL_JOYSTICK_MFI + // GameController.framework was added in iOS 7. + if (![GCController class]) { + return true; + } + + /* For whatever reason, this always returns an empty array on + macOS 11.0.1 */ + for (GCController *controller in [GCController controllers]) { + IOS_AddJoystickDevice(controller); + } + +#ifdef SDL_PLATFORM_TVOS + SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, + SDL_AppleTVRemoteRotationHintChanged, NULL); +#endif // SDL_PLATFORM_TVOS + + center = [NSNotificationCenter defaultCenter]; + + connectObserver = [center addObserverForName:GCControllerDidConnectNotification + object:nil + queue:nil + usingBlock:^(NSNotification *note) { + GCController *controller = note.object; + SDL_LockJoysticks(); + IOS_AddJoystickDevice(controller); + SDL_UnlockJoysticks(); + }]; + + disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification + object:nil + queue:nil + usingBlock:^(NSNotification *note) { + GCController *controller = note.object; + SDL_JoystickDeviceItem *device; + SDL_LockJoysticks(); + for (device = deviceList; device != NULL; device = device->next) { + if (device->controller == controller) { + IOS_RemoveJoystickDevice(device); + break; + } + } + SDL_UnlockJoysticks(); + }]; +#endif // SDL_JOYSTICK_MFI + } + + return true; +} + +static int IOS_JoystickGetCount(void) +{ + return numjoysticks; +} + +static void IOS_JoystickDetect(void) +{ +} + +static bool IOS_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers through this method + return false; +} + +static const char *IOS_JoystickGetDeviceName(int device_index) +{ + SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); + return device ? device->name : "Unknown"; +} + +static const char *IOS_JoystickGetDevicePath(int device_index) +{ + return NULL; +} + +static int IOS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + +static int IOS_JoystickGetDevicePlayerIndex(int device_index) +{ +#ifdef SDL_JOYSTICK_MFI + SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); + if (device && device->controller) { + return (int)device->controller.playerIndex; + } +#endif + return -1; +} + +static void IOS_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +#ifdef SDL_JOYSTICK_MFI + SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); + if (device && device->controller) { + device->controller.playerIndex = player_index; + } +#endif +} + +static SDL_GUID IOS_JoystickGetDeviceGUID(int device_index) +{ + SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); + SDL_GUID guid; + if (device) { + guid = device->guid; + } else { + SDL_zero(guid); + } + return guid; +} + +static SDL_JoystickID IOS_JoystickGetDeviceInstanceID(int device_index) +{ + SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); + return device ? device->instance_id : 0; +} + +static bool IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); + if (device == NULL) { + return SDL_SetError("Could not open Joystick: no hardware device for the specified index"); + } + + joystick->hwdata = device; + + joystick->naxes = device->naxes; + joystick->nhats = device->nhats; + joystick->nbuttons = device->nbuttons; + + if (device->has_dualshock_touchpad) { + SDL_PrivateJoystickAddTouchpad(joystick, 2); + } + + device->joystick = joystick; + + @autoreleasepool { +#ifdef SDL_JOYSTICK_MFI + if (device->pause_button_index >= 0) { + GCController *controller = device->controller; + controller.controllerPausedHandler = ^(GCController *c) { + if (joystick->hwdata) { + joystick->hwdata->pause_button_pressed = SDL_GetTicks(); + } + }; + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + GCController *controller = joystick->hwdata->controller; + GCMotion *motion = controller.motion; + if (motion && motion.hasRotationRate) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f); + } + if (motion && motion.hasGravityAndUserAcceleration) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f); + } + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + GCController *controller = joystick->hwdata->controller; + for (id key in controller.physicalInputProfile.buttons) { + GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key]; + if ([button isBoundToSystemGesture]) { + button.preferredSystemGestureState = GCSystemGestureStateDisabled; + } + } + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + GCController *controller = device->controller; + if (controller.light) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true); + } + + if (controller.haptics) { + for (GCHapticsLocality locality in controller.haptics.supportedLocalities) { + if ([locality isEqualToString:GCHapticsLocalityHandles]) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } else if ([locality isEqualToString:GCHapticsLocalityTriggers]) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); + } + } + } + } +#endif // SDL_JOYSTICK_MFI + } + if (device->is_siri_remote) { + ++SDL_AppleTVRemoteOpenedAsJoystick; + } + + return true; +} + +#ifdef SDL_JOYSTICK_MFI +static Uint8 IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad) +{ + Uint8 hat = 0; + + if (dpad.up.isPressed) { + hat |= SDL_HAT_UP; + } else if (dpad.down.isPressed) { + hat |= SDL_HAT_DOWN; + } + + if (dpad.left.isPressed) { + hat |= SDL_HAT_LEFT; + } else if (dpad.right.isPressed) { + hat |= SDL_HAT_RIGHT; + } + + if (hat == 0) { + return SDL_HAT_CENTERED; + } + + return hat; +} +#endif + +static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick) +{ +#ifdef SDL_JOYSTICK_MFI + @autoreleasepool { + SDL_JoystickDeviceItem *device = joystick->hwdata; + GCController *controller = device->controller; + Uint8 hatstate = SDL_HAT_CENTERED; + int i; + Uint64 timestamp = SDL_GetTicksNS(); + +#ifdef DEBUG_CONTROLLER_STATE + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (controller.physicalInputProfile) { + for (id key in controller.physicalInputProfile.buttons) { + GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key]; + if (button.isPressed) + NSLog(@"Button %@ = %s\n", key, button.isPressed ? "pressed" : "released"); + } + for (id key in controller.physicalInputProfile.axes) { + GCControllerAxisInput *axis = controller.physicalInputProfile.axes[key]; + if (axis.value != 0.0f) + NSLog(@"Axis %@ = %g\n", key, axis.value); + } + for (id key in controller.physicalInputProfile.dpads) { + GCControllerDirectionPad *dpad = controller.physicalInputProfile.dpads[key]; + if (dpad.up.isPressed || dpad.down.isPressed || dpad.left.isPressed || dpad.right.isPressed) { + NSLog(@"Hat %@ =%s%s%s%s\n", key, + dpad.up.isPressed ? " UP" : "", + dpad.down.isPressed ? " DOWN" : "", + dpad.left.isPressed ? " LEFT" : "", + dpad.right.isPressed ? " RIGHT" : ""); + } + } + } + } +#endif // DEBUG_CONTROLLER_STATE + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + NSDictionary *elements = controller.physicalInputProfile.elements; + NSDictionary *buttons = controller.physicalInputProfile.buttons; + + int axis = 0; + for (id key in device->axes) { + Sint16 value; + GCControllerElement *element = elements[key]; + if ([element isKindOfClass:[GCControllerAxisInput class]]) { + value = (Sint16)([(GCControllerAxisInput *)element value] * 32767); + } else { + value = (Sint16)([(GCControllerButtonInput *)element value] * 32767); + } + SDL_SendJoystickAxis(timestamp, joystick, axis++, value); + } + + int button = 0; + for (id key in device->buttons) { + bool down; + if (button == device->pause_button_index) { + down = (device->pause_button_pressed > 0); + } else { + down = buttons[key].isPressed; + } + SDL_SendJoystickButton(timestamp, joystick, button++, down); + } + } else if (controller.extendedGamepad) { + bool isstack; + GCExtendedGamepad *gamepad = controller.extendedGamepad; + + // Axis order matches the XInput Windows mappings. + Sint16 axes[] = { + (Sint16)(gamepad.leftThumbstick.xAxis.value * 32767), + (Sint16)(gamepad.leftThumbstick.yAxis.value * -32767), + (Sint16)((gamepad.leftTrigger.value * 65535) - 32768), + (Sint16)(gamepad.rightThumbstick.xAxis.value * 32767), + (Sint16)(gamepad.rightThumbstick.yAxis.value * -32767), + (Sint16)((gamepad.rightTrigger.value * 65535) - 32768), + }; + + // Button order matches the XInput Windows mappings. + bool *buttons = SDL_small_alloc(bool, joystick->nbuttons, &isstack); + int button_count = 0; + + if (buttons == NULL) { + return; + } + + // These buttons are part of the original MFi spec + buttons[button_count++] = gamepad.buttonA.isPressed; + buttons[button_count++] = gamepad.buttonB.isPressed; + buttons[button_count++] = gamepad.buttonX.isPressed; + buttons[button_count++] = gamepad.buttonY.isPressed; + buttons[button_count++] = gamepad.leftShoulder.isPressed; + buttons[button_count++] = gamepad.rightShoulder.isPressed; + + // These buttons are available on some newer controllers + if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) { + if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) { + buttons[button_count++] = gamepad.leftThumbstickButton.isPressed; + } + if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) { + buttons[button_count++] = gamepad.rightThumbstickButton.isPressed; + } + } + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) { + buttons[button_count++] = gamepad.buttonOptions.isPressed; + } + } + if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) { + if (device->pause_button_index >= 0) { + // Guaranteed if buttonMenu is not supported on this OS + buttons[button_count++] = (device->pause_button_pressed > 0); + } else { + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + buttons[button_count++] = gamepad.buttonMenu.isPressed; + } + } + } + + hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad); + + for (i = 0; i < SDL_arraysize(axes); i++) { + SDL_SendJoystickAxis(timestamp, joystick, i, axes[i]); + } + + for (i = 0; i < button_count; i++) { + SDL_SendJoystickButton(timestamp, joystick, i, buttons[i]); + } + + SDL_small_free(buttons, isstack); + } +#ifdef SDL_PLATFORM_TVOS + else if (controller.microGamepad) { + GCMicroGamepad *gamepad = controller.microGamepad; + + Sint16 axes[] = { + (Sint16)(gamepad.dpad.xAxis.value * 32767), + (Sint16)(gamepad.dpad.yAxis.value * -32767), + }; + + for (i = 0; i < SDL_arraysize(axes); i++) { + SDL_SendJoystickAxis(timestamp, joystick, i, axes[i]); + } + + bool buttons[joystick->nbuttons]; + int button_count = 0; + buttons[button_count++] = gamepad.buttonA.isPressed; + buttons[button_count++] = gamepad.buttonX.isPressed; + buttons[button_count++] = (device->pause_button_pressed > 0); + + for (i = 0; i < button_count; i++) { + SDL_SendJoystickButton(timestamp, joystick, i, buttons[i]); + } + } +#endif // SDL_PLATFORM_TVOS + + if (joystick->nhats > 0) { + SDL_SendJoystickHat(timestamp, joystick, 0, hatstate); + } + + if (device->pause_button_pressed) { + // The pause callback is instantaneous, so we extend the duration to allow "holding down" by pressing it repeatedly + const int PAUSE_BUTTON_PRESS_DURATION_MS = 250; + if (SDL_GetTicks() >= device->pause_button_pressed + PAUSE_BUTTON_PRESS_DURATION_MS) { + device->pause_button_pressed = 0; + } + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (device->has_dualshock_touchpad) { + GCControllerDirectionPad *dpad; + + dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]; + if (dpad.xAxis.value != 0.f || dpad.yAxis.value != 0.f) { + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, true, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f); + } else { + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, false, 0.0f, 0.0f, 1.0f); + } + + dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]; + if (dpad.xAxis.value != 0.f || dpad.yAxis.value != 0.f) { + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, true, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f); + } else { + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, false, 0.0f, 0.0f, 1.0f); + } + } + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + GCMotion *motion = controller.motion; + if (motion && motion.sensorsActive) { + float data[3]; + + if (motion.hasRotationRate) { + GCRotationRate rate = motion.rotationRate; + data[0] = rate.x; + data[1] = rate.z; + data[2] = -rate.y; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, 3); + } + if (motion.hasGravityAndUserAcceleration) { + GCAcceleration accel = motion.acceleration; + data[0] = -accel.x * SDL_STANDARD_GRAVITY; + data[1] = -accel.y * SDL_STANDARD_GRAVITY; + data[2] = -accel.z * SDL_STANDARD_GRAVITY; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, 3); + } + } + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + GCDeviceBattery *battery = controller.battery; + if (battery) { + SDL_PowerState state = SDL_POWERSTATE_UNKNOWN; + int percent = (int)SDL_roundf(battery.batteryLevel * 100.0f); + + switch (battery.batteryState) { + case GCDeviceBatteryStateDischarging: + state = SDL_POWERSTATE_ON_BATTERY; + break; + case GCDeviceBatteryStateCharging: + state = SDL_POWERSTATE_CHARGING; + break; + case GCDeviceBatteryStateFull: + state = SDL_POWERSTATE_CHARGED; + break; + default: + break; + } + + SDL_SendJoystickPowerInfo(joystick, state, percent); + } + } + } +#endif // SDL_JOYSTICK_MFI +} + +#ifdef SDL_JOYSTICK_MFI +@interface SDL3_RumbleMotor : NSObject +@property(nonatomic, strong) CHHapticEngine *engine API_AVAILABLE(macos(10.16), ios(13.0), tvos(14.0)); +@property(nonatomic, strong) id player API_AVAILABLE(macos(10.16), ios(13.0), tvos(14.0)); +@property bool active; +@end + +@implementation SDL3_RumbleMotor +{ +} + +- (void)cleanup +{ + @autoreleasepool { + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (self.player != nil) { + [self.player cancelAndReturnError:nil]; + self.player = nil; + } + if (self.engine != nil) { + [self.engine stopWithCompletionHandler:nil]; + self.engine = nil; + } + } + } +} + +- (bool)setIntensity:(float)intensity +{ + @autoreleasepool { + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + NSError *error = nil; + CHHapticDynamicParameter *param; + + if (self.engine == nil) { + return SDL_SetError("Haptics engine was stopped"); + } + + if (intensity == 0.0f) { + if (self.player && self.active) { + [self.player stopAtTime:0 error:&error]; + } + self.active = false; + return true; + } + + if (self.player == nil) { + CHHapticEventParameter *event_param = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f]; + CHHapticEvent *event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObjects:event_param, nil] relativeTime:0 duration:GCHapticDurationInfinite]; + CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithEvents:[NSArray arrayWithObject:event] parameters:[[NSArray alloc] init] error:&error]; + if (error != nil) { + return SDL_SetError("Couldn't create haptic pattern: %s", [error.localizedDescription UTF8String]); + } + + self.player = [self.engine createPlayerWithPattern:pattern error:&error]; + if (error != nil) { + return SDL_SetError("Couldn't create haptic player: %s", [error.localizedDescription UTF8String]); + } + self.active = false; + } + + param = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:intensity relativeTime:0]; + [self.player sendParameters:[NSArray arrayWithObject:param] atTime:0 error:&error]; + if (error != nil) { + return SDL_SetError("Couldn't update haptic player: %s", [error.localizedDescription UTF8String]); + } + + if (!self.active) { + [self.player startAtTime:0 error:&error]; + self.active = true; + } + } + + return true; + } +} + +- (id)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(10.16), ios(14.0), tvos(14.0)) +{ + @autoreleasepool { + NSError *error; + __weak __typeof(self) weakSelf; + self = [super init]; + weakSelf = self; + + self.engine = [controller.haptics createEngineWithLocality:locality]; + if (self.engine == nil) { + SDL_SetError("Couldn't create haptics engine"); + return nil; + } + + [self.engine startAndReturnError:&error]; + if (error != nil) { + SDL_SetError("Couldn't start haptics engine"); + return nil; + } + + self.engine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) { + SDL3_RumbleMotor *_this = weakSelf; + if (_this == nil) { + return; + } + + _this.player = nil; + _this.engine = nil; + }; + self.engine.resetHandler = ^{ + SDL3_RumbleMotor *_this = weakSelf; + if (_this == nil) { + return; + } + + _this.player = nil; + [_this.engine startAndReturnError:nil]; + }; + + return self; + } +} + +@end + +@interface SDL3_RumbleContext : NSObject +@property(nonatomic, strong) SDL3_RumbleMotor *lowFrequencyMotor; +@property(nonatomic, strong) SDL3_RumbleMotor *highFrequencyMotor; +@property(nonatomic, strong) SDL3_RumbleMotor *leftTriggerMotor; +@property(nonatomic, strong) SDL3_RumbleMotor *rightTriggerMotor; +@end + +@implementation SDL3_RumbleContext +{ +} + +- (id)initWithLowFrequencyMotor:(SDL3_RumbleMotor *)low_frequency_motor + HighFrequencyMotor:(SDL3_RumbleMotor *)high_frequency_motor + LeftTriggerMotor:(SDL3_RumbleMotor *)left_trigger_motor + RightTriggerMotor:(SDL3_RumbleMotor *)right_trigger_motor +{ + self = [super init]; + self.lowFrequencyMotor = low_frequency_motor; + self.highFrequencyMotor = high_frequency_motor; + self.leftTriggerMotor = left_trigger_motor; + self.rightTriggerMotor = right_trigger_motor; + return self; +} + +- (bool)rumbleWithLowFrequency:(Uint16)low_frequency_rumble andHighFrequency:(Uint16)high_frequency_rumble +{ + bool result = true; + + result &= [self.lowFrequencyMotor setIntensity:((float)low_frequency_rumble / 65535.0f)]; + result &= [self.highFrequencyMotor setIntensity:((float)high_frequency_rumble / 65535.0f)]; + return result; +} + +- (bool)rumbleLeftTrigger:(Uint16)left_rumble andRightTrigger:(Uint16)right_rumble +{ + bool result = false; + + if (self.leftTriggerMotor && self.rightTriggerMotor) { + result &= [self.leftTriggerMotor setIntensity:((float)left_rumble / 65535.0f)]; + result &= [self.rightTriggerMotor setIntensity:((float)right_rumble / 65535.0f)]; + } else { + result = SDL_Unsupported(); + } + return result; +} + +- (void)cleanup +{ + [self.lowFrequencyMotor cleanup]; + [self.highFrequencyMotor cleanup]; +} + +@end + +static SDL3_RumbleContext *IOS_JoystickInitRumble(GCController *controller) +{ + @autoreleasepool { + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + SDL3_RumbleMotor *low_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle]; + SDL3_RumbleMotor *high_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle]; + SDL3_RumbleMotor *left_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftTrigger]; + SDL3_RumbleMotor *right_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightTrigger]; + if (low_frequency_motor && high_frequency_motor) { + return [[SDL3_RumbleContext alloc] initWithLowFrequencyMotor:low_frequency_motor + HighFrequencyMotor:high_frequency_motor + LeftTriggerMotor:left_trigger_motor + RightTriggerMotor:right_trigger_motor]; + } + } + } + return nil; +} + +#endif // SDL_JOYSTICK_MFI + +static bool IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ +#ifdef SDL_JOYSTICK_MFI + SDL_JoystickDeviceItem *device = joystick->hwdata; + + if (device == NULL) { + return SDL_SetError("Controller is no longer connected"); + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (!device->rumble && device->controller && device->controller.haptics) { + SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller); + if (rumble) { + device->rumble = (void *)CFBridgingRetain(rumble); + } + } + } + + if (device->rumble) { + SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble; + return [rumble rumbleWithLowFrequency:low_frequency_rumble andHighFrequency:high_frequency_rumble]; + } +#endif + return SDL_Unsupported(); +} + +static bool IOS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ +#ifdef SDL_JOYSTICK_MFI + SDL_JoystickDeviceItem *device = joystick->hwdata; + + if (device == NULL) { + return SDL_SetError("Controller is no longer connected"); + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (!device->rumble && device->controller && device->controller.haptics) { + SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller); + if (rumble) { + device->rumble = (void *)CFBridgingRetain(rumble); + } + } + } + + if (device->rumble) { + SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble; + return [rumble rumbleLeftTrigger:left_rumble andRightTrigger:right_rumble]; + } +#endif + return SDL_Unsupported(); +} + +static bool IOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + @autoreleasepool { + SDL_JoystickDeviceItem *device = joystick->hwdata; + + if (device == NULL) { + return SDL_SetError("Controller is no longer connected"); + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + GCController *controller = device->controller; + GCDeviceLight *light = controller.light; + if (light) { + light.color = [[GCColor alloc] initWithRed:(float)red / 255.0f + green:(float)green / 255.0f + blue:(float)blue / 255.0f]; + return true; + } + } + } + return SDL_Unsupported(); +} + +static bool IOS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool IOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + @autoreleasepool { + SDL_JoystickDeviceItem *device = joystick->hwdata; + + if (device == NULL) { + return SDL_SetError("Controller is no longer connected"); + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + GCController *controller = device->controller; + GCMotion *motion = controller.motion; + if (motion) { + motion.sensorsActive = enabled ? YES : NO; + return true; + } + } + } + + return SDL_Unsupported(); +} + +static void IOS_JoystickUpdate(SDL_Joystick *joystick) +{ + SDL_JoystickDeviceItem *device = joystick->hwdata; + + if (device == NULL) { + return; + } + + if (device->controller) { + IOS_MFIJoystickUpdate(joystick); + } +} + +static void IOS_JoystickClose(SDL_Joystick *joystick) +{ + SDL_JoystickDeviceItem *device = joystick->hwdata; + + if (device == NULL) { + return; + } + + device->joystick = NULL; + +#ifdef SDL_JOYSTICK_MFI + @autoreleasepool { + if (device->rumble) { + SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble; + + [rumble cleanup]; + CFRelease(device->rumble); + device->rumble = NULL; + } + + if (device->controller) { + GCController *controller = device->controller; + controller.controllerPausedHandler = nil; + controller.playerIndex = -1; + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + for (id key in controller.physicalInputProfile.buttons) { + GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key]; + if ([button isBoundToSystemGesture]) { + button.preferredSystemGestureState = GCSystemGestureStateEnabled; + } + } + } + } + } +#endif // SDL_JOYSTICK_MFI + + if (device->is_siri_remote) { + --SDL_AppleTVRemoteOpenedAsJoystick; + } +} + +static void IOS_JoystickQuit(void) +{ + @autoreleasepool { +#ifdef SDL_JOYSTICK_MFI + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + + if (connectObserver) { + [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil]; + connectObserver = nil; + } + + if (disconnectObserver) { + [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil]; + disconnectObserver = nil; + } + +#ifdef SDL_PLATFORM_TVOS + SDL_RemoveHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, + SDL_AppleTVRemoteRotationHintChanged, NULL); +#endif // SDL_PLATFORM_TVOS +#endif // SDL_JOYSTICK_MFI + + while (deviceList != NULL) { + IOS_RemoveJoystickDevice(deviceList); + } + } + + numjoysticks = 0; +} + +static bool IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); + if (device == NULL) { + return false; + } + + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + int axis = 0; + for (id key in device->axes) { + if ([(NSString *)key isEqualToString:@"Left Thumbstick X Axis"] || + [(NSString *)key isEqualToString:@"Direction Pad X Axis"]) { + out->leftx.kind = EMappingKind_Axis; + out->leftx.target = axis; + } else if ([(NSString *)key isEqualToString:@"Left Thumbstick Y Axis"] || + [(NSString *)key isEqualToString:@"Direction Pad Y Axis"]) { + out->lefty.kind = EMappingKind_Axis; + out->lefty.target = axis; + out->lefty.axis_reversed = true; + } else if ([(NSString *)key isEqualToString:@"Right Thumbstick X Axis"]) { + out->rightx.kind = EMappingKind_Axis; + out->rightx.target = axis; + } else if ([(NSString *)key isEqualToString:@"Right Thumbstick Y Axis"]) { + out->righty.kind = EMappingKind_Axis; + out->righty.target = axis; + out->righty.axis_reversed = true; + } else if ([(NSString *)key isEqualToString:GCInputLeftTrigger]) { + out->lefttrigger.kind = EMappingKind_Axis; + out->lefttrigger.target = axis; + out->lefttrigger.half_axis_positive = true; + } else if ([(NSString *)key isEqualToString:GCInputRightTrigger]) { + out->righttrigger.kind = EMappingKind_Axis; + out->righttrigger.target = axis; + out->righttrigger.half_axis_positive = true; + } + ++axis; + } + + int button = 0; + for (id key in device->buttons) { + SDL_InputMapping *mapping = NULL; + + if ([(NSString *)key isEqualToString:GCInputButtonA]) { + if (device->is_siri_remote > 1) { + // GCInputButtonA is triggered for any D-Pad press, ignore it in favor of "Button Center" + } else if (device->has_nintendo_buttons) { + mapping = &out->b; + } else { + mapping = &out->a; + } + } else if ([(NSString *)key isEqualToString:GCInputButtonB]) { + if (device->has_nintendo_buttons) { + mapping = &out->a; + } else if (device->is_switch_joyconL || device->is_switch_joyconR) { + mapping = &out->x; + } else { + mapping = &out->b; + } + } else if ([(NSString *)key isEqualToString:GCInputButtonX]) { + if (device->has_nintendo_buttons) { + mapping = &out->y; + } else if (device->is_switch_joyconL || device->is_switch_joyconR) { + mapping = &out->b; + } else { + mapping = &out->x; + } + } else if ([(NSString *)key isEqualToString:GCInputButtonY]) { + if (device->has_nintendo_buttons) { + mapping = &out->x; + } else { + mapping = &out->y; + } + } else if ([(NSString *)key isEqualToString:@"Direction Pad Left"]) { + mapping = &out->dpleft; + } else if ([(NSString *)key isEqualToString:@"Direction Pad Right"]) { + mapping = &out->dpright; + } else if ([(NSString *)key isEqualToString:@"Direction Pad Up"]) { + mapping = &out->dpup; + } else if ([(NSString *)key isEqualToString:@"Direction Pad Down"]) { + mapping = &out->dpdown; + } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Left"]) { + mapping = &out->dpleft; + } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Right"]) { + mapping = &out->dpright; + } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Up"]) { + mapping = &out->dpup; + } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Down"]) { + mapping = &out->dpdown; + } else if ([(NSString *)key isEqualToString:GCInputLeftShoulder]) { + mapping = &out->leftshoulder; + } else if ([(NSString *)key isEqualToString:GCInputRightShoulder]) { + mapping = &out->rightshoulder; + } else if ([(NSString *)key isEqualToString:GCInputLeftThumbstickButton]) { + mapping = &out->leftstick; + } else if ([(NSString *)key isEqualToString:GCInputRightThumbstickButton]) { + mapping = &out->rightstick; + } else if ([(NSString *)key isEqualToString:@"Button Home"]) { + mapping = &out->guide; + } else if ([(NSString *)key isEqualToString:GCInputButtonMenu]) { + if (device->is_siri_remote) { + mapping = &out->b; + } else { + mapping = &out->start; + } + } else if ([(NSString *)key isEqualToString:GCInputButtonOptions]) { + mapping = &out->back; + } else if ([(NSString *)key isEqualToString:@"Button Share"]) { + mapping = &out->misc1; + } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleOne]) { + mapping = &out->right_paddle1; + } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleTwo]) { + mapping = &out->right_paddle2; + } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleThree]) { + mapping = &out->left_paddle1; + } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleFour]) { + mapping = &out->left_paddle2; + } else if ([(NSString *)key isEqualToString:GCInputLeftTrigger]) { + mapping = &out->lefttrigger; + } else if ([(NSString *)key isEqualToString:GCInputRightTrigger]) { + mapping = &out->righttrigger; + } else if ([(NSString *)key isEqualToString:GCInputDualShockTouchpadButton]) { + mapping = &out->touchpad; + } else if ([(NSString *)key isEqualToString:@"Button Center"]) { + mapping = &out->a; + } + if (mapping && mapping->kind == EMappingKind_None) { + mapping->kind = EMappingKind_Button; + mapping->target = button; + } + ++button; + } + + return true; + } + return false; +} + +#if defined(SDL_JOYSTICK_MFI) && defined(SDL_PLATFORM_MACOS) +bool IOS_SupportedHIDDevice(IOHIDDeviceRef device) +{ + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_MFI, true)) { + return false; + } + + if (@available(macOS 10.16, *)) { + const int MAX_ATTEMPTS = 3; + for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { + if ([GCController supportsHIDDevice:device]) { + return true; + } + + // The framework may not have seen the device yet + SDL_Delay(10); + } + } + return false; +} +#endif + +#ifdef SDL_JOYSTICK_MFI +/* NOLINTNEXTLINE(readability-non-const-parameter): getCString takes a non-const char* */ +static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char *name) +{ + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + if (element) { + [element.sfSymbolsName getCString:name maxLength:255 encoding:NSASCIIStringEncoding]; + } + } +} + +static GCControllerDirectionPad *GetDirectionalPadForController(GCController *controller) +{ + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + return controller.physicalInputProfile.dpads[GCInputDirectionPad]; + } + + if (controller.extendedGamepad) { + return controller.extendedGamepad.dpad; + } + + if (controller.microGamepad) { + return controller.microGamepad.dpad; + } + + return nil; +} +#endif // SDL_JOYSTICK_MFI + +const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button) +{ + char elementName[256]; + elementName[0] = '\0'; + +#ifdef SDL_JOYSTICK_MFI + if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) { + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller; + NSDictionary *elements = controller.physicalInputProfile.elements; + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + GetAppleSFSymbolsNameForElement(elements[GCInputButtonA], elementName); + break; + case SDL_GAMEPAD_BUTTON_EAST: + GetAppleSFSymbolsNameForElement(elements[GCInputButtonB], elementName); + break; + case SDL_GAMEPAD_BUTTON_WEST: + GetAppleSFSymbolsNameForElement(elements[GCInputButtonX], elementName); + break; + case SDL_GAMEPAD_BUTTON_NORTH: + GetAppleSFSymbolsNameForElement(elements[GCInputButtonY], elementName); + break; + case SDL_GAMEPAD_BUTTON_BACK: + GetAppleSFSymbolsNameForElement(elements[GCInputButtonOptions], elementName); + break; + case SDL_GAMEPAD_BUTTON_GUIDE: + GetAppleSFSymbolsNameForElement(elements[@"Button Home"], elementName); + break; + case SDL_GAMEPAD_BUTTON_START: + GetAppleSFSymbolsNameForElement(elements[GCInputButtonMenu], elementName); + break; + case SDL_GAMEPAD_BUTTON_LEFT_STICK: + GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstickButton], elementName); + break; + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: + GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstickButton], elementName); + break; + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: + GetAppleSFSymbolsNameForElement(elements[GCInputLeftShoulder], elementName); + break; + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: + GetAppleSFSymbolsNameForElement(elements[GCInputRightShoulder], elementName); + break; + case SDL_GAMEPAD_BUTTON_DPAD_UP: + { + GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller); + if (dpad) { + GetAppleSFSymbolsNameForElement(dpad.up, elementName); + if (SDL_strlen(elementName) == 0) { + SDL_strlcpy(elementName, "dpad.up.fill", sizeof(elementName)); + } + } + break; + } + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + { + GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller); + if (dpad) { + GetAppleSFSymbolsNameForElement(dpad.down, elementName); + if (SDL_strlen(elementName) == 0) { + SDL_strlcpy(elementName, "dpad.down.fill", sizeof(elementName)); + } + } + break; + } + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + { + GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller); + if (dpad) { + GetAppleSFSymbolsNameForElement(dpad.left, elementName); + if (SDL_strlen(elementName) == 0) { + SDL_strlcpy(elementName, "dpad.left.fill", sizeof(elementName)); + } + } + break; + } + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + { + GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller); + if (dpad) { + GetAppleSFSymbolsNameForElement(dpad.right, elementName); + if (SDL_strlen(elementName) == 0) { + SDL_strlcpy(elementName, "dpad.right.fill", sizeof(elementName)); + } + } + break; + } + case SDL_GAMEPAD_BUTTON_MISC1: + GetAppleSFSymbolsNameForElement(elements[GCInputDualShockTouchpadButton], elementName); + break; + case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: + GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleOne], elementName); + break; + case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: + GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleThree], elementName); + break; + case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: + GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleTwo], elementName); + break; + case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: + GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleFour], elementName); + break; + case SDL_GAMEPAD_BUTTON_TOUCHPAD: + GetAppleSFSymbolsNameForElement(elements[GCInputDualShockTouchpadButton], elementName); + break; + default: + break; + } + } + } +#endif // SDL_JOYSTICK_MFI + + return *elementName ? SDL_GetPersistentString(elementName) : NULL; +} + +const char *IOS_GetAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis) +{ + char elementName[256]; + elementName[0] = '\0'; + +#ifdef SDL_JOYSTICK_MFI + if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) { + if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) { + GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller; + NSDictionary *elements = controller.physicalInputProfile.elements; + switch (axis) { + case SDL_GAMEPAD_AXIS_LEFTX: + GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstick], elementName); + break; + case SDL_GAMEPAD_AXIS_LEFTY: + GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstick], elementName); + break; + case SDL_GAMEPAD_AXIS_RIGHTX: + GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstick], elementName); + break; + case SDL_GAMEPAD_AXIS_RIGHTY: + GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstick], elementName); + break; + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + GetAppleSFSymbolsNameForElement(elements[GCInputLeftTrigger], elementName); + break; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + GetAppleSFSymbolsNameForElement(elements[GCInputRightTrigger], elementName); + break; + default: + break; + } + } + } +#endif // SDL_JOYSTICK_MFI + + return *elementName ? SDL_GetPersistentString(elementName) : NULL; +} + +SDL_JoystickDriver SDL_IOS_JoystickDriver = { + IOS_JoystickInit, + IOS_JoystickGetCount, + IOS_JoystickDetect, + IOS_JoystickIsDevicePresent, + IOS_JoystickGetDeviceName, + IOS_JoystickGetDevicePath, + IOS_JoystickGetDeviceSteamVirtualGamepadSlot, + IOS_JoystickGetDevicePlayerIndex, + IOS_JoystickSetDevicePlayerIndex, + IOS_JoystickGetDeviceGUID, + IOS_JoystickGetDeviceInstanceID, + IOS_JoystickOpen, + IOS_JoystickRumble, + IOS_JoystickRumbleTriggers, + IOS_JoystickSetLED, + IOS_JoystickSendEffect, + IOS_JoystickSetSensorsEnabled, + IOS_JoystickUpdate, + IOS_JoystickClose, + IOS_JoystickQuit, + IOS_JoystickGetGamepadMapping +}; diff --git a/contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick_c.h b/contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick_c.h new file mode 100644 index 0000000..783b3f4 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick_c.h @@ -0,0 +1,73 @@ +/* + 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" + +#ifndef SDL_JOYSTICK_IOS_H +#define SDL_JOYSTICK_IOS_H + +#include "../SDL_sysjoystick.h" + +#import +#import + +@class GCController; + +typedef struct joystick_hwdata +{ + GCController __unsafe_unretained *controller; + void *rumble; + int pause_button_index; + Uint64 pause_button_pressed; + + char *name; + SDL_Joystick *joystick; + SDL_JoystickID instance_id; + SDL_GUID guid; + + int naxes; + int nbuttons; + int nhats; + Uint32 button_mask; + bool is_xbox; + bool is_ps4; + bool is_ps5; + bool is_switch_pro; + bool is_switch_joycon_pair; + bool is_switch_joyconL; + bool is_switch_joyconR; + bool is_stadia; + bool is_backbone_one; + int is_siri_remote; + + NSArray __unsafe_unretained *axes; + NSArray __unsafe_unretained *buttons; + + bool has_dualshock_touchpad; + bool has_xbox_paddles; + bool has_xbox_share_button; + bool has_nintendo_buttons; + + struct joystick_hwdata *next; +} joystick_hwdata; + +typedef joystick_hwdata SDL_JoystickDeviceItem; + +#endif // SDL_JOYSTICK_IOS_H diff --git a/contrib/SDL-3.2.8/src/joystick/bsd/SDL_bsdjoystick.c b/contrib/SDL-3.2.8/src/joystick/bsd/SDL_bsdjoystick.c new file mode 100644 index 0000000..b3fd3e9 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/bsd/SDL_bsdjoystick.c @@ -0,0 +1,868 @@ +/* + 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_JOYSTICK_USBHID + +/* + * Joystick driver for the uhid(4) / ujoy(4) interface found in OpenBSD, + * NetBSD and FreeBSD. + * + * Maintainer: + */ + +#include +#include + +#include +#include +#include + +#ifndef __FreeBSD_kernel_version +#define __FreeBSD_kernel_version __FreeBSD_version +#endif + +#ifdef HAVE_USB_H +#include +#endif +#ifdef __DragonFly__ +#include +#include +#else +#include +#include +#endif + +#ifdef HAVE_USBHID_H +#include +#elif defined(HAVE_LIBUSB_H) +#include +#elif defined(HAVE_LIBUSBHID_H) +#include +#endif + +#if defined(SDL_PLATFORM_FREEBSD) +#include +#if __FreeBSD_kernel_version > 800063 +#include +#elif defined(__DragonFly__) +#include +#endif +#include +#endif + +#ifdef SDL_HAVE_MACHINE_JOYSTICK_H +#include +#endif + +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" +#include "../hidapi/SDL_hidapijoystick_c.h" + +#if defined(SDL_PLATFORM_FREEBSD) || defined(SDL_HAVE_MACHINE_JOYSTICK_H) || defined(__FreeBSD_kernel__) || defined(__DragonFly_) +#define SUPPORT_JOY_GAMEPORT +#endif + +#define MAX_UHID_JOYS 64 +#define MAX_JOY_JOYS 2 +#define MAX_JOYS (MAX_UHID_JOYS + MAX_JOY_JOYS) + +#ifdef SDL_PLATFORM_OPENBSD + +#define HUG_DPAD_UP 0x90 +#define HUG_DPAD_DOWN 0x91 +#define HUG_DPAD_RIGHT 0x92 +#define HUG_DPAD_LEFT 0x93 + +#define HAT_UP 0x01 +#define HAT_RIGHT 0x02 +#define HAT_DOWN 0x04 +#define HAT_LEFT 0x08 + +#endif + +struct report +{ +#if defined(SDL_PLATFORM_FREEBSD) && (__FreeBSD_kernel_version > 900000) || \ + defined(__DragonFly__) + void *buf; // Buffer +#elif defined(SDL_PLATFORM_FREEBSD) && (__FreeBSD_kernel_version > 800063) + struct usb_gen_descriptor *buf; // Buffer +#else + struct usb_ctl_report *buf; // Buffer +#endif + size_t size; // Buffer size + int rid; // Report ID + enum + { + SREPORT_UNINIT, + SREPORT_CLEAN, + SREPORT_DIRTY + } status; +}; + +static struct +{ + int uhid_report; + hid_kind_t kind; + const char *name; +} const repinfo[] = { + { UHID_INPUT_REPORT, hid_input, "input" }, + { UHID_OUTPUT_REPORT, hid_output, "output" }, + { UHID_FEATURE_REPORT, hid_feature, "feature" } +}; + +enum +{ + REPORT_INPUT = 0, + REPORT_OUTPUT = 1, + REPORT_FEATURE = 2 +}; + +enum +{ + JOYAXE_X, + JOYAXE_Y, + JOYAXE_Z, + JOYAXE_SLIDER, + JOYAXE_WHEEL, + JOYAXE_RX, + JOYAXE_RY, + JOYAXE_RZ, + JOYAXE_count +}; + +struct joystick_hwdata +{ + int fd; + enum + { + BSDJOY_UHID, // uhid(4) + BSDJOY_JOY // joy(4) + } type; + + int naxes; + int nbuttons; + int nhats; + struct report_desc *repdesc; + struct report inreport; + int axis_map[JOYAXE_count]; /* map present JOYAXE_* to 0,1,.. */ +}; + +// A linked list of available joysticks +typedef struct SDL_joylist_item +{ + SDL_JoystickID device_instance; + char *path; // "/dev/uhid0" or whatever + char *name; // "SideWinder 3D Pro" or whatever + SDL_GUID guid; + dev_t devnum; + struct SDL_joylist_item *next; +} SDL_joylist_item; + +static SDL_joylist_item *SDL_joylist = NULL; +static SDL_joylist_item *SDL_joylist_tail = NULL; +static int numjoysticks = 0; + +static bool report_alloc(struct report *, struct report_desc *, int); +static void report_free(struct report *); + +#if defined(USBHID_UCR_DATA) || (defined(__FreeBSD_kernel__) && __FreeBSD_kernel_version <= 800063) +#define REP_BUF_DATA(rep) ((rep)->buf->ucr_data) +#elif (defined(SDL_PLATFORM_FREEBSD) && (__FreeBSD_kernel_version > 900000)) || \ + defined(__DragonFly__) +#define REP_BUF_DATA(rep) ((rep)->buf) +#elif (defined(SDL_PLATFORM_FREEBSD) && (__FreeBSD_kernel_version > 800063)) +#define REP_BUF_DATA(rep) ((rep)->buf->ugd_data) +#else +#define REP_BUF_DATA(rep) ((rep)->buf->data) +#endif + +static int usage_to_joyaxe(int usage) +{ + int joyaxe; + switch (usage) { + case HUG_X: + joyaxe = JOYAXE_X; + break; + case HUG_Y: + joyaxe = JOYAXE_Y; + break; + case HUG_Z: + joyaxe = JOYAXE_Z; + break; + case HUG_SLIDER: + joyaxe = JOYAXE_SLIDER; + break; + case HUG_WHEEL: + joyaxe = JOYAXE_WHEEL; + break; + case HUG_RX: + joyaxe = JOYAXE_RX; + break; + case HUG_RY: + joyaxe = JOYAXE_RY; + break; + case HUG_RZ: + joyaxe = JOYAXE_RZ; + break; + default: + joyaxe = -1; + } + return joyaxe; +} + +static void FreeJoylistItem(SDL_joylist_item *item) +{ + SDL_free(item->path); + SDL_free(item->name); + SDL_free(item); +} + +static void FreeHwData(struct joystick_hwdata *hw) +{ + if (hw->type == BSDJOY_UHID) { + report_free(&hw->inreport); + + if (hw->repdesc) { + hid_dispose_report_desc(hw->repdesc); + } + } + close(hw->fd); + SDL_free(hw); +} + +static struct joystick_hwdata *CreateHwData(const char *path) +{ + struct joystick_hwdata *hw; + struct hid_item hitem; + struct hid_data *hdata; + struct report *rep = NULL; + int fd; + int i; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd == -1) { + SDL_SetError("%s: %s", path, strerror(errno)); + return NULL; + } + + hw = (struct joystick_hwdata *) + SDL_calloc(1, sizeof(struct joystick_hwdata)); + if (!hw) { + close(fd); + return NULL; + } + hw->fd = fd; + +#ifdef SUPPORT_JOY_GAMEPORT + if (SDL_strncmp(path, "/dev/joy", 8) == 0) { + hw->type = BSDJOY_JOY; + hw->naxes = 2; + hw->nbuttons = 2; + } else +#endif + { + hw->type = BSDJOY_UHID; + { + int ax; + for (ax = 0; ax < JOYAXE_count; ax++) { + hw->axis_map[ax] = -1; + } + } + hw->repdesc = hid_get_report_desc(fd); + if (!hw->repdesc) { + SDL_SetError("%s: USB_GET_REPORT_DESC: %s", path, + strerror(errno)); + goto usberr; + } + rep = &hw->inreport; +#if defined(SDL_PLATFORM_FREEBSD) && (__FreeBSD_kernel_version > 800063) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) + rep->rid = hid_get_report_id(fd); + if (rep->rid < 0) { +#else + if (ioctl(fd, USB_GET_REPORT_ID, &rep->rid) < 0) { +#endif + rep->rid = -1; // XXX + } + if (!report_alloc(rep, hw->repdesc, REPORT_INPUT)) { + goto usberr; + } + if (rep->size <= 0) { + SDL_SetError("%s: Input report descriptor has invalid length", + path); + goto usberr; + } +#if defined(USBHID_NEW) || (defined(SDL_PLATFORM_FREEBSD) && __FreeBSD_kernel_version >= 500111) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) + hdata = hid_start_parse(hw->repdesc, 1 << hid_input, rep->rid); +#else + hdata = hid_start_parse(hw->repdesc, 1 << hid_input); +#endif + if (!hdata) { + SDL_SetError("%s: Cannot start HID parser", path); + goto usberr; + } + for (i = 0; i < JOYAXE_count; i++) { + hw->axis_map[i] = -1; + } + + while (hid_get_item(hdata, &hitem) > 0) { + switch (hitem.kind) { + case hid_input: + switch (HID_PAGE(hitem.usage)) { + case HUP_GENERIC_DESKTOP: + { + int usage = HID_USAGE(hitem.usage); + int joyaxe = usage_to_joyaxe(usage); + if (joyaxe >= 0) { + hw->axis_map[joyaxe] = 1; + } else if (usage == HUG_HAT_SWITCH +#ifdef SDL_PLATFORM_OPENBSD + || usage == HUG_DPAD_UP +#endif + ) { + hw->nhats++; + } + break; + } + case HUP_BUTTON: + { + int usage = HID_USAGE(hitem.usage); + if (usage > hw->nbuttons) { + hw->nbuttons = usage; + } + } break; + default: + break; + } + break; + default: + break; + } + } + hid_end_parse(hdata); + for (i = 0; i < JOYAXE_count; i++) { + if (hw->axis_map[i] > 0) { + hw->axis_map[i] = hw->naxes++; + } + } + + if (hw->naxes == 0 && hw->nbuttons == 0 && hw->nhats == 0) { + SDL_SetError("%s: Not a joystick, ignoring", path); + goto usberr; + } + } + + // The poll blocks the event thread. + fcntl(fd, F_SETFL, O_NONBLOCK); +#ifdef SDL_PLATFORM_NETBSD + // Flush pending events + if (rep) { + while (read(fd, REP_BUF_DATA(rep), rep->size) == rep->size) + ; + } +#endif + + return hw; + +usberr: + FreeHwData(hw); + return NULL; +} + +static bool MaybeAddDevice(const char *path) +{ + struct stat sb; + char *name = NULL; + SDL_GUID guid; + SDL_joylist_item *item; + struct joystick_hwdata *hw; + + if (!path) { + return false; + } + + if (stat(path, &sb) == -1) { + return false; + } + + // Check to make sure it's not already in list. + for (item = SDL_joylist; item; item = item->next) { + if (sb.st_rdev == item->devnum) { + return false; // already have this one + } + } + + hw = CreateHwData(path); + if (!hw) { + return false; + } + + if (hw->type == BSDJOY_JOY) { + name = SDL_strdup("Gameport joystick"); + guid = SDL_CreateJoystickGUIDForName(name); + } else { +#ifdef USB_GET_DEVICEINFO + struct usb_device_info di; + if (ioctl(hw->fd, USB_GET_DEVICEINFO, &di) != -1) { + name = SDL_CreateJoystickName(di.udi_vendorNo, di.udi_productNo, di.udi_vendor, di.udi_product); + guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, di.udi_vendorNo, di.udi_productNo, di.udi_releaseNo, di.udi_vendor, di.udi_product, 0, 0); + + if (SDL_ShouldIgnoreJoystick(di.udi_vendorNo, di.udi_productNo, di.udi_releaseNo, name) || + SDL_JoystickHandledByAnotherDriver(&SDL_BSD_JoystickDriver, di.udi_vendorNo, di.udi_productNo, di.udi_releaseNo, name)) { + SDL_free(name); + FreeHwData(hw); + return false; + } + } +#endif // USB_GET_DEVICEINFO + } + if (!name) { + name = SDL_strdup(path); + guid = SDL_CreateJoystickGUIDForName(name); + } + FreeHwData(hw); + + item = (SDL_joylist_item *)SDL_calloc(1, sizeof(SDL_joylist_item)); + if (!item) { + SDL_free(name); + return false; + } + + item->devnum = sb.st_rdev; + item->path = SDL_strdup(path); + item->name = name; + item->guid = guid; + + if ((!item->path) || (!item->name)) { + FreeJoylistItem(item); + return false; + } + + item->device_instance = SDL_GetNextObjectID(); + if (!SDL_joylist_tail) { + SDL_joylist = SDL_joylist_tail = item; + } else { + SDL_joylist_tail->next = item; + SDL_joylist_tail = item; + } + + // Need to increment the joystick count before we post the event + ++numjoysticks; + + SDL_PrivateJoystickAdded(item->device_instance); + + return true; +} + +static bool BSD_JoystickInit(void) +{ + char s[16]; + int i; + + for (i = 0; i < MAX_UHID_JOYS; i++) { +#if defined(SDL_PLATFORM_OPENBSD) && (OpenBSD >= 202105) + SDL_snprintf(s, SDL_arraysize(s), "/dev/ujoy/%d", i); +#else + SDL_snprintf(s, SDL_arraysize(s), "/dev/uhid%d", i); +#endif + MaybeAddDevice(s); + } +#ifdef SUPPORT_JOY_GAMEPORT + for (i = 0; i < MAX_JOY_JOYS; i++) { + SDL_snprintf(s, SDL_arraysize(s), "/dev/joy%d", i); + MaybeAddDevice(s); + } +#endif // SUPPORT_JOY_GAMEPORT + + // Read the default USB HID usage table. + hid_init(NULL); + + return true; +} + +static int BSD_JoystickGetCount(void) +{ + return numjoysticks; +} + +static void BSD_JoystickDetect(void) +{ +} + +static bool BSD_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers + return false; +} + +static SDL_joylist_item *GetJoystickByDevIndex(int device_index) +{ + SDL_joylist_item *item = SDL_joylist; + + if ((device_index < 0) || (device_index >= numjoysticks)) { + return NULL; + } + + while (device_index > 0) { + SDL_assert(item != NULL); + device_index--; + item = item->next; + } + + return item; +} + +static const char *BSD_JoystickGetDeviceName(int device_index) +{ + return GetJoystickByDevIndex(device_index)->name; +} + +static const char *BSD_JoystickGetDevicePath(int device_index) +{ + return GetJoystickByDevIndex(device_index)->path; +} + +static int BSD_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + +static int BSD_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void BSD_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static SDL_GUID BSD_JoystickGetDeviceGUID(int device_index) +{ + return GetJoystickByDevIndex(device_index)->guid; +} + +// Function to perform the mapping from device index to the instance id for this index +static SDL_JoystickID BSD_JoystickGetDeviceInstanceID(int device_index) +{ + return GetJoystickByDevIndex(device_index)->device_instance; +} + +static unsigned hatval_to_sdl(Sint32 hatval) +{ + static const unsigned hat_dir_map[8] = { + SDL_HAT_UP, SDL_HAT_RIGHTUP, SDL_HAT_RIGHT, SDL_HAT_RIGHTDOWN, + SDL_HAT_DOWN, SDL_HAT_LEFTDOWN, SDL_HAT_LEFT, SDL_HAT_LEFTUP + }; + unsigned result; + if ((hatval & 7) == hatval) + result = hat_dir_map[hatval]; + else + result = SDL_HAT_CENTERED; + return result; +} + +static bool BSD_JoystickOpen(SDL_Joystick *joy, int device_index) +{ + SDL_joylist_item *item = GetJoystickByDevIndex(device_index); + struct joystick_hwdata *hw; + + if (!item) { + return SDL_SetError("No such device"); + } + + hw = CreateHwData(item->path); + if (!hw) { + return false; + } + + joy->hwdata = hw; + joy->naxes = hw->naxes; + joy->nbuttons = hw->nbuttons; + joy->nhats = hw->nhats; + + return true; +} + +static void BSD_JoystickUpdate(SDL_Joystick *joy) +{ + struct hid_item hitem; + struct hid_data *hdata; + struct report *rep; + int nbutton, naxe = -1; + Sint32 v; +#ifdef SDL_PLATFORM_OPENBSD + Sint32 dpad[4] = { 0, 0, 0, 0 }; +#endif + Uint64 timestamp = SDL_GetTicksNS(); + +#ifdef SUPPORT_JOY_GAMEPORT + struct joystick gameport; + static int x, y, xmin = 0xffff, ymin = 0xffff, xmax = 0, ymax = 0; + + if (joy->hwdata->type == BSDJOY_JOY) { + while (read(joy->hwdata->fd, &gameport, sizeof(gameport)) == sizeof(gameport)) { + if (SDL_abs(x - gameport.x) > 8) { + x = gameport.x; + if (x < xmin) { + xmin = x; + } + if (x > xmax) { + xmax = x; + } + if (xmin == xmax) { + xmin--; + xmax++; + } + v = (((SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN) * ((Sint32)x - xmin)) / (xmax - xmin)) + SDL_JOYSTICK_AXIS_MIN; + SDL_SendJoystickAxis(timestamp, joy, 0, v); + } + if (SDL_abs(y - gameport.y) > 8) { + y = gameport.y; + if (y < ymin) { + ymin = y; + } + if (y > ymax) { + ymax = y; + } + if (ymin == ymax) { + ymin--; + ymax++; + } + v = (((SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN) * ((Sint32)y - ymin)) / (ymax - ymin)) + SDL_JOYSTICK_AXIS_MIN; + SDL_SendJoystickAxis(timestamp, joy, 1, v); + } + SDL_SendJoystickButton(timestamp, joy, 0, (gameport.b1 != 0)); + SDL_SendJoystickButton(timestamp, joy, 1, (gameport.b2 != 0)); + } + return; + } +#endif // SUPPORT_JOY_GAMEPORT + + rep = &joy->hwdata->inreport; + + while (read(joy->hwdata->fd, REP_BUF_DATA(rep), rep->size) == rep->size) { +#if defined(USBHID_NEW) || (defined(SDL_PLATFORM_FREEBSD) && __FreeBSD_kernel_version >= 500111) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) + hdata = hid_start_parse(joy->hwdata->repdesc, 1 << hid_input, rep->rid); +#else + hdata = hid_start_parse(joy->hwdata->repdesc, 1 << hid_input); +#endif + if (!hdata) { + // fprintf(stderr, "%s: Cannot start HID parser\n", joy->hwdata->path); + continue; + } + + while (hid_get_item(hdata, &hitem) > 0) { + switch (hitem.kind) { + case hid_input: + switch (HID_PAGE(hitem.usage)) { + case HUP_GENERIC_DESKTOP: + { + int usage = HID_USAGE(hitem.usage); + int joyaxe = usage_to_joyaxe(usage); + if (joyaxe >= 0) { + naxe = joy->hwdata->axis_map[joyaxe]; + // scaleaxe + v = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem); + v = (((SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN) * (v - hitem.logical_minimum)) / (hitem.logical_maximum - hitem.logical_minimum)) + SDL_JOYSTICK_AXIS_MIN; + SDL_SendJoystickAxis(timestamp, joy, naxe, v); + } else if (usage == HUG_HAT_SWITCH) { + v = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem); + SDL_SendJoystickHat(timestamp, joy, 0, + hatval_to_sdl(v) - + hitem.logical_minimum); + } +#ifdef SDL_PLATFORM_OPENBSD + /* here D-pad directions are reported like separate buttons. + * calculate the SDL hat value from the 4 separate values. + */ + switch (usage) { + case HUG_DPAD_UP: + dpad[0] = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem); + break; + case HUG_DPAD_DOWN: + dpad[1] = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem); + break; + case HUG_DPAD_RIGHT: + dpad[2] = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem); + break; + case HUG_DPAD_LEFT: + dpad[3] = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem); + break; + //default: + // no-op + } + SDL_PrivateJoystickHat(joy, 0, (dpad[0] * HAT_UP) | + (dpad[1] * HAT_DOWN) | + (dpad[2] * HAT_RIGHT) | + (dpad[3] * HAT_LEFT) ); +#endif + break; + } + case HUP_BUTTON: + v = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem); + nbutton = HID_USAGE(hitem.usage) - 1; // SDL buttons are zero-based + SDL_SendJoystickButton(timestamp, joy, nbutton, (v != 0)); + break; + default: + continue; + } + break; + default: + break; + } + } + hid_end_parse(hdata); + } +} + +// Function to close a joystick after use +static void BSD_JoystickClose(SDL_Joystick *joy) +{ + if (joy->hwdata) { + FreeHwData(joy->hwdata); + joy->hwdata = NULL; + } +} + +static void BSD_JoystickQuit(void) +{ + SDL_joylist_item *item = NULL; + SDL_joylist_item *next = NULL; + + for (item = SDL_joylist; item; item = next) { + next = item->next; + FreeJoylistItem(item); + } + + SDL_joylist = SDL_joylist_tail = NULL; + + numjoysticks = 0; +} + +static bool report_alloc(struct report *r, struct report_desc *rd, int repind) +{ + int len; + +#ifdef __DragonFly__ + len = hid_report_size(rd, repinfo[repind].kind, r->rid); +#elif defined(SDL_PLATFORM_FREEBSD) +#if (__FreeBSD_kernel_version >= 460000) || defined(__FreeBSD_kernel__) +#if (__FreeBSD_kernel_version <= 500111) + len = hid_report_size(rd, r->rid, repinfo[repind].kind); +#else + len = hid_report_size(rd, repinfo[repind].kind, r->rid); +#endif +#else + len = hid_report_size(rd, repinfo[repind].kind, &r->rid); +#endif +#else +#ifdef USBHID_NEW + len = hid_report_size(rd, repinfo[repind].kind, r->rid); +#else + len = hid_report_size(rd, repinfo[repind].kind, &r->rid); +#endif +#endif + + if (len < 0) { + return SDL_SetError("Negative HID report size"); + } + r->size = len; + + if (r->size > 0) { +#if defined(SDL_PLATFORM_FREEBSD) && (__FreeBSD_kernel_version > 900000) || defined(__DragonFly__) + r->buf = SDL_malloc(r->size); +#else + r->buf = SDL_malloc(sizeof(*r->buf) - sizeof(REP_BUF_DATA(r)) + + r->size); +#endif + if (!r->buf) { + return false; + } + } else { + r->buf = NULL; + } + + r->status = SREPORT_CLEAN; + return true; +} + +static void report_free(struct report *r) +{ + SDL_free(r->buf); + r->status = SREPORT_UNINIT; +} + +static bool BSD_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +static bool BSD_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static bool BSD_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +static bool BSD_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool BSD_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool BSD_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +SDL_JoystickDriver SDL_BSD_JoystickDriver = { + BSD_JoystickInit, + BSD_JoystickGetCount, + BSD_JoystickDetect, + BSD_JoystickIsDevicePresent, + BSD_JoystickGetDeviceName, + BSD_JoystickGetDevicePath, + BSD_JoystickGetDeviceSteamVirtualGamepadSlot, + BSD_JoystickGetDevicePlayerIndex, + BSD_JoystickSetDevicePlayerIndex, + BSD_JoystickGetDeviceGUID, + BSD_JoystickGetDeviceInstanceID, + BSD_JoystickOpen, + BSD_JoystickRumble, + BSD_JoystickRumbleTriggers, + BSD_JoystickSetLED, + BSD_JoystickSendEffect, + BSD_JoystickSetSensorsEnabled, + BSD_JoystickUpdate, + BSD_JoystickClose, + BSD_JoystickQuit, + BSD_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_USBHID diff --git a/contrib/SDL-3.2.8/src/joystick/check_8bitdo.sh b/contrib/SDL-3.2.8/src/joystick/check_8bitdo.sh new file mode 100755 index 0000000..7f6f742 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/check_8bitdo.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# +# Check to make sure 8BitDo controller configurations are correct + +echo "Expected output:" +cat <<__EOF__ + "050000003512000020ab000000780f00,8BitDo SNES30 Gamepad,a:b20,b:b21,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b23,y:b24,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "050000003512000020ab000000780f00,8BitDo SNES30 Gamepad,a:b21,b:b20,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b24,y:b23,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + +__EOF__ + +echo "Actual output:" +${FGREP:-grep -F} 8BitDo SDL_gamepad_db.h | ${FGREP:-grep -F} -v hint +${EGREP:-grep -E} "hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1" SDL_gamepad_db.h | ${FGREP:-grep -F} -i 8bit | ${FGREP:-grep -F} -v x:b2,y:b3 | ${FGREP:-grep -F} -v x:b3,y:b4 +${EGREP:-grep -E} "hint:.SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1" SDL_gamepad_db.h | ${FGREP:-grep -F} -i 8bit | ${FGREP:-grep -F} -v x:b3,y:b2 | ${FGREP:-grep -F} -v x:b4,y:b3 diff --git a/contrib/SDL-3.2.8/src/joystick/controller_list.h b/contrib/SDL-3.2.8/src/joystick/controller_list.h new file mode 100644 index 0000000..5a62ece --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/controller_list.h @@ -0,0 +1,609 @@ +/* + Copyright (C) Valve Corporation + + 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. +*/ +#define MAKE_CONTROLLER_ID( nVID, nPID ) (unsigned int)( (unsigned int)nVID << 16 | (unsigned int)nPID ) + +static const ControllerDescription_t arrControllers[] = { + { MAKE_CONTROLLER_ID( 0x0079, 0x181a ), k_eControllerType_PS3Controller, NULL }, // Venom Arcade Stick + { MAKE_CONTROLLER_ID( 0x0079, 0x1844 ), k_eControllerType_PS3Controller, NULL }, // From SDL + { MAKE_CONTROLLER_ID( 0x044f, 0xb315 ), k_eControllerType_PS3Controller, NULL }, // Firestorm Dual Analog 3 + { MAKE_CONTROLLER_ID( 0x044f, 0xd007 ), k_eControllerType_PS3Controller, NULL }, // Thrustmaster wireless 3-1 + { MAKE_CONTROLLER_ID( 0x046d, 0xcad1 ), k_eControllerType_PS3Controller, NULL }, // Logitech Chillstream + //{ MAKE_CONTROLLER_ID( 0x046d, 0xc24f ), k_eControllerType_PS3Controller, NULL }, // Logitech G29 (PS3) + { MAKE_CONTROLLER_ID( 0x054c, 0x0268 ), k_eControllerType_PS3Controller, NULL }, // Sony PS3 Controller + { MAKE_CONTROLLER_ID( 0x056e, 0x200f ), k_eControllerType_PS3Controller, NULL }, // From SDL + { MAKE_CONTROLLER_ID( 0x056e, 0x2013 ), k_eControllerType_PS3Controller, NULL }, // JC-U4113SBK + { MAKE_CONTROLLER_ID( 0x05b8, 0x1004 ), k_eControllerType_PS3Controller, NULL }, // From SDL + { MAKE_CONTROLLER_ID( 0x05b8, 0x1006 ), k_eControllerType_PS3Controller, NULL }, // JC-U3412SBK + { MAKE_CONTROLLER_ID( 0x06a3, 0xf622 ), k_eControllerType_PS3Controller, NULL }, // Cyborg V3 + { MAKE_CONTROLLER_ID( 0x0738, 0x3180 ), k_eControllerType_PS3Controller, NULL }, // Mad Catz Alpha PS3 mode + { MAKE_CONTROLLER_ID( 0x0738, 0x3250 ), k_eControllerType_PS3Controller, NULL }, // madcats fightpad pro ps3 + { MAKE_CONTROLLER_ID( 0x0738, 0x3481 ), k_eControllerType_PS3Controller, NULL }, // Mad Catz FightStick TE 2+ PS3 + { MAKE_CONTROLLER_ID( 0x0738, 0x8180 ), k_eControllerType_PS3Controller, NULL }, // Mad Catz Alpha PS4 mode (no touchpad on device) + { MAKE_CONTROLLER_ID( 0x0738, 0x8838 ), k_eControllerType_PS3Controller, NULL }, // Madcatz Fightstick Pro + { MAKE_CONTROLLER_ID( 0x0810, 0x0001 ), k_eControllerType_PS3Controller, NULL }, // actually ps2 - maybe break out later + { MAKE_CONTROLLER_ID( 0x0810, 0x0003 ), k_eControllerType_PS3Controller, NULL }, // actually ps2 - maybe break out later + { MAKE_CONTROLLER_ID( 0x0925, 0x0005 ), k_eControllerType_PS3Controller, NULL }, // Sony PS3 Controller + { MAKE_CONTROLLER_ID( 0x0925, 0x8866 ), k_eControllerType_PS3Controller, NULL }, // PS2 maybe break out later + { MAKE_CONTROLLER_ID( 0x0925, 0x8888 ), k_eControllerType_PS3Controller, NULL }, // Actually ps2 -maybe break out later Lakeview Research WiseGroup Ltd, MP-8866 Dual Joypad + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0109 ), k_eControllerType_PS3Controller, NULL }, // PDP Versus Fighting Pad + { MAKE_CONTROLLER_ID( 0x0e6f, 0x011e ), k_eControllerType_PS3Controller, NULL }, // Rock Candy PS4 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0128 ), k_eControllerType_PS3Controller, NULL }, // Rock Candy PS3 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0214 ), k_eControllerType_PS3Controller, NULL }, // afterglow ps3 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x1314 ), k_eControllerType_PS3Controller, NULL }, // PDP Afterglow Wireless PS3 controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x6302 ), k_eControllerType_PS3Controller, NULL }, // From SDL + { MAKE_CONTROLLER_ID( 0x0e8f, 0x0008 ), k_eControllerType_PS3Controller, NULL }, // Green Asia + { MAKE_CONTROLLER_ID( 0x0e8f, 0x3075 ), k_eControllerType_PS3Controller, NULL }, // SpeedLink Strike FX + { MAKE_CONTROLLER_ID( 0x0e8f, 0x310d ), k_eControllerType_PS3Controller, NULL }, // From SDL + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0009 ), k_eControllerType_PS3Controller, NULL }, // HORI BDA GP1 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x004d ), k_eControllerType_PS3Controller, NULL }, // Horipad 3 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x005f ), k_eControllerType_PS3Controller, NULL }, // HORI Fighting Commander 4 PS3 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x006a ), k_eControllerType_PS3Controller, NULL }, // Real Arcade Pro 4 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x006e ), k_eControllerType_PS3Controller, NULL }, // HORI horipad4 ps3 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0085 ), k_eControllerType_PS3Controller, NULL }, // HORI Fighting Commander PS3 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0086 ), k_eControllerType_PS3Controller, NULL }, // HORI Fighting Commander PC (Uses the Xbox 360 protocol, but has PS3 buttons) + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0088 ), k_eControllerType_PS3Controller, NULL }, // HORI Fighting Stick mini 4 + { MAKE_CONTROLLER_ID( 0x0f30, 0x1100 ), k_eControllerType_PS3Controller, NULL }, // Qanba Q1 fight stick + { MAKE_CONTROLLER_ID( 0x11ff, 0x3331 ), k_eControllerType_PS3Controller, NULL }, // SRXJ-PH2400 + { MAKE_CONTROLLER_ID( 0x1345, 0x1000 ), k_eControllerType_PS3Controller, NULL }, // PS2 ACME GA-D5 + { MAKE_CONTROLLER_ID( 0x1345, 0x6005 ), k_eControllerType_PS3Controller, NULL }, // ps2 maybe break out later + { MAKE_CONTROLLER_ID( 0x146b, 0x5500 ), k_eControllerType_PS3Controller, NULL }, // From SDL + { MAKE_CONTROLLER_ID( 0x1a34, 0x0836 ), k_eControllerType_PS3Controller, NULL }, // Afterglow PS3 + { MAKE_CONTROLLER_ID( 0x20bc, 0x5500 ), k_eControllerType_PS3Controller, NULL }, // ShanWan PS3 + { MAKE_CONTROLLER_ID( 0x20d6, 0x576d ), k_eControllerType_PS3Controller, NULL }, // Power A PS3 + { MAKE_CONTROLLER_ID( 0x20d6, 0xca6d ), k_eControllerType_PS3Controller, NULL }, // BDA Pro Ex + { MAKE_CONTROLLER_ID( 0x2563, 0x0523 ), k_eControllerType_PS3Controller, NULL }, // Digiflip GP006 + { MAKE_CONTROLLER_ID( 0x2563, 0x0575 ), k_eControllerType_PS3Controller, "Retro-bit Controller" }, // SWITCH CO., LTD. Retro-bit Controller + { MAKE_CONTROLLER_ID( 0x25f0, 0x83c3 ), k_eControllerType_PS3Controller, NULL }, // gioteck vx2 + { MAKE_CONTROLLER_ID( 0x25f0, 0xc121 ), k_eControllerType_PS3Controller, NULL }, // + { MAKE_CONTROLLER_ID( 0x2c22, 0x2003 ), k_eControllerType_PS3Controller, NULL }, // Qanba Drone + { MAKE_CONTROLLER_ID( 0x2c22, 0x2302 ), k_eControllerType_PS3Controller, NULL }, // Qanba Obsidian + { MAKE_CONTROLLER_ID( 0x2c22, 0x2502 ), k_eControllerType_PS3Controller, NULL }, // Qanba Dragon + { MAKE_CONTROLLER_ID( 0x8380, 0x0003 ), k_eControllerType_PS3Controller, NULL }, // BTP 2163 + { MAKE_CONTROLLER_ID( 0x8888, 0x0308 ), k_eControllerType_PS3Controller, NULL }, // Sony PS3 Controller + + { MAKE_CONTROLLER_ID( 0x0079, 0x181b ), k_eControllerType_PS4Controller, NULL }, // Venom Arcade Stick - XXX:this may not work and may need to be called a ps3 controller + //{ MAKE_CONTROLLER_ID( 0x046d, 0xc260 ), k_eControllerType_PS4Controller, NULL }, // Logitech G29 (PS4) + { MAKE_CONTROLLER_ID( 0x044f, 0xd00e ), k_eControllerType_PS4Controller, NULL }, // Thrustmaster Eswap Pro - No gyro and lightbar doesn't change color. Works otherwise + { MAKE_CONTROLLER_ID( 0x054c, 0x05c4 ), k_eControllerType_PS4Controller, NULL }, // Sony PS4 Controller + { MAKE_CONTROLLER_ID( 0x054c, 0x05c5 ), k_eControllerType_PS4Controller, NULL }, // STRIKEPAD PS4 Grip Add-on + { MAKE_CONTROLLER_ID( 0x054c, 0x09cc ), k_eControllerType_PS4Controller, NULL }, // Sony PS4 Slim Controller + { MAKE_CONTROLLER_ID( 0x054c, 0x0ba0 ), k_eControllerType_PS4Controller, NULL }, // Sony PS4 Controller (Wireless dongle) + { MAKE_CONTROLLER_ID( 0x0738, 0x8250 ), k_eControllerType_PS4Controller, NULL }, // Mad Catz FightPad Pro PS4 + { MAKE_CONTROLLER_ID( 0x0738, 0x8384 ), k_eControllerType_PS4Controller, NULL }, // Mad Catz FightStick TE S+ PS4 + { MAKE_CONTROLLER_ID( 0x0738, 0x8480 ), k_eControllerType_PS4Controller, NULL }, // Mad Catz FightStick TE 2 PS4 + { MAKE_CONTROLLER_ID( 0x0738, 0x8481 ), k_eControllerType_PS4Controller, NULL }, // Mad Catz FightStick TE 2+ PS4 + { MAKE_CONTROLLER_ID( 0x0c12, 0x0e10 ), k_eControllerType_PS4Controller, NULL }, // Armor Armor 3 Pad PS4 + { MAKE_CONTROLLER_ID( 0x0c12, 0x0e13 ), k_eControllerType_PS4Controller, NULL }, // ZEROPLUS P4 Wired Gamepad + { MAKE_CONTROLLER_ID( 0x0c12, 0x0e15 ), k_eControllerType_PS4Controller, NULL }, // Game:Pad 4 + { MAKE_CONTROLLER_ID( 0x0c12, 0x0e20 ), k_eControllerType_PS4Controller, NULL }, // Brook Mars Controller - needs FW update to show up as Ps4 controller on PC. Has Gyro but touchpad is a single button. + { MAKE_CONTROLLER_ID( 0x0c12, 0x0ef6 ), k_eControllerType_PS4Controller, NULL }, // Hitbox Arcade Stick + { MAKE_CONTROLLER_ID( 0x0c12, 0x1cf6 ), k_eControllerType_PS4Controller, NULL }, // EMIO PS4 Elite Controller + { MAKE_CONTROLLER_ID( 0x0c12, 0x1e10 ), k_eControllerType_PS4Controller, NULL }, // P4 Wired Gamepad generic knock off - lightbar but not trackpad or gyro + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0203 ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS (PS4 peripheral but no trackpad/lightbar) + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0207 ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS V2 w/ Touchpad for PS4 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x020a ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS PS4/PS5 (PS4 mode) + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0055 ), k_eControllerType_PS4Controller, NULL }, // HORIPAD 4 FPS + { MAKE_CONTROLLER_ID( 0x0f0d, 0x005e ), k_eControllerType_PS4Controller, NULL }, // HORI Fighting Commander 4 PS4 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0066 ), k_eControllerType_PS4Controller, NULL }, // HORIPAD 4 FPS Plus + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0084 ), k_eControllerType_PS4Controller, NULL }, // HORI Fighting Commander PS4 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0087 ), k_eControllerType_PS4Controller, NULL }, // HORI Fighting Stick mini 4 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x008a ), k_eControllerType_PS4Controller, NULL }, // HORI Real Arcade Pro 4 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x009c ), k_eControllerType_PS4Controller, NULL }, // HORI TAC PRO mousething + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00a0 ), k_eControllerType_PS4Controller, NULL }, // HORI TAC4 mousething + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00ed ), k_eControllerType_XInputPS4Controller, NULL }, // Hori Fighting Stick mini 4 kai - becomes an Xbox 360 controller on PC + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00ee ), k_eControllerType_PS4Controller, NULL }, // Hori mini wired https://www.playstation.com/en-us/explore/accessories/gaming-controllers/mini-wired-gamepad/ + { MAKE_CONTROLLER_ID( 0x0f0d, 0x011c ), k_eControllerType_PS4Controller, NULL }, // Hori Fighting Stick α + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0123 ), k_eControllerType_PS4Controller, NULL }, // HORI Wireless Controller Light (Japan only) - only over bt- over usb is xbox and pid 0x0124 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0162 ), k_eControllerType_PS4Controller, NULL }, // HORI Fighting Commander OCTA + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0164 ), k_eControllerType_XInputPS4Controller, NULL }, // HORI Fighting Commander OCTA + { MAKE_CONTROLLER_ID( 0x11c0, 0x4001 ), k_eControllerType_PS4Controller, NULL }, // "PS4 Fun Controller" added from user log + { MAKE_CONTROLLER_ID( 0x146b, 0x0603 ), k_eControllerType_XInputPS4Controller, NULL }, // Nacon PS4 Compact Controller + { MAKE_CONTROLLER_ID( 0x146b, 0x0604 ), k_eControllerType_XInputPS4Controller, NULL }, // NACON Daija Arcade Stick + { MAKE_CONTROLLER_ID( 0x146b, 0x0605 ), k_eControllerType_XInputPS4Controller, NULL }, // NACON PS4 controller in Xbox mode - might also be other bigben brand xbox controllers + { MAKE_CONTROLLER_ID( 0x146b, 0x0606 ), k_eControllerType_XInputPS4Controller, NULL }, // NACON Unknown Controller + { MAKE_CONTROLLER_ID( 0x146b, 0x0609 ), k_eControllerType_XInputPS4Controller, NULL }, // NACON Wireless Controller for PS4 + { MAKE_CONTROLLER_ID( 0x146b, 0x0d01 ), k_eControllerType_PS4Controller, NULL }, // Nacon Revolution Pro Controller - has gyro + { MAKE_CONTROLLER_ID( 0x146b, 0x0d02 ), k_eControllerType_PS4Controller, NULL }, // Nacon Revolution Pro Controller v2 - has gyro + { MAKE_CONTROLLER_ID( 0x146b, 0x0d06 ), k_eControllerType_PS4Controller, NULL }, // NACON Asymmetric Controller Wireless Dongle -- show up as ps4 until you connect controller to it then it reboots into Xbox controller with different vvid/pid + { MAKE_CONTROLLER_ID( 0x146b, 0x0d08 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution Unlimited Wireless Dongle + { MAKE_CONTROLLER_ID( 0x146b, 0x0d09 ), k_eControllerType_PS4Controller, NULL }, // NACON Daija Fight Stick - touchpad but no gyro/rumble + { MAKE_CONTROLLER_ID( 0x146b, 0x0d10 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution Infinite - has gyro + { MAKE_CONTROLLER_ID( 0x146b, 0x0d10 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution Unlimited + { MAKE_CONTROLLER_ID( 0x146b, 0x0d13 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution Pro Controller 3 + { MAKE_CONTROLLER_ID( 0x146b, 0x1103 ), k_eControllerType_PS4Controller, NULL }, // NACON Asymmetric Controller -- on windows this doesn't enumerate + { MAKE_CONTROLLER_ID( 0x1532, 0X0401 ), k_eControllerType_PS4Controller, NULL }, // Razer Panthera PS4 Controller + { MAKE_CONTROLLER_ID( 0x1532, 0x1000 ), k_eControllerType_PS4Controller, NULL }, // Razer Raiju PS4 Controller + { MAKE_CONTROLLER_ID( 0x1532, 0x1004 ), k_eControllerType_PS4Controller, NULL }, // Razer Raiju 2 Ultimate USB + { MAKE_CONTROLLER_ID( 0x1532, 0x1007 ), k_eControllerType_PS4Controller, NULL }, // Razer Raiju 2 Tournament edition USB + { MAKE_CONTROLLER_ID( 0x1532, 0x1008 ), k_eControllerType_PS4Controller, NULL }, // Razer Panthera Evo Fightstick + { MAKE_CONTROLLER_ID( 0x1532, 0x1009 ), k_eControllerType_PS4Controller, NULL }, // Razer Raiju 2 Ultimate BT + { MAKE_CONTROLLER_ID( 0x1532, 0x100A ), k_eControllerType_PS4Controller, NULL }, // Razer Raiju 2 Tournament edition BT + { MAKE_CONTROLLER_ID( 0x1532, 0x1100 ), k_eControllerType_PS4Controller, NULL }, // Razer RAION Fightpad - Trackpad, no gyro, lightbar hardcoded to green + { MAKE_CONTROLLER_ID( 0x20d6, 0x792a ), k_eControllerType_PS4Controller, NULL }, // PowerA Fusion Fight Pad + { MAKE_CONTROLLER_ID( 0x2c22, 0x2000 ), k_eControllerType_PS4Controller, NULL }, // Qanba Drone + { MAKE_CONTROLLER_ID( 0x2c22, 0x2300 ), k_eControllerType_PS4Controller, NULL }, // Qanba Obsidian + { MAKE_CONTROLLER_ID( 0x2c22, 0x2303 ), k_eControllerType_XInputPS4Controller, NULL }, // Qanba Obsidian Arcade Joystick + { MAKE_CONTROLLER_ID( 0x2c22, 0x2500 ), k_eControllerType_PS4Controller, NULL }, // Qanba Dragon + { MAKE_CONTROLLER_ID( 0x2c22, 0x2503 ), k_eControllerType_XInputPS4Controller, NULL }, // Qanba Dragon Arcade Joystick + { MAKE_CONTROLLER_ID( 0x3285, 0x0d16 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution 5 Pro (PS4 mode with dongle) + { MAKE_CONTROLLER_ID( 0x3285, 0x0d17 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution 5 Pro (PS4 mode wired) + { MAKE_CONTROLLER_ID( 0x7545, 0x0104 ), k_eControllerType_PS4Controller, NULL }, // Armor 3 or Level Up Cobra - At least one variant has gyro + { MAKE_CONTROLLER_ID (0x9886, 0x0024 ), k_eControllerType_XInputPS4Controller, NULL }, // Astro C40 in Xbox 360 mode + { MAKE_CONTROLLER_ID( 0x9886, 0x0025 ), k_eControllerType_PS4Controller, NULL }, // Astro C40 + // Removing the Giotek because there were a bunch of help tickets from users w/ issues including from non-PS4 controller users. This VID/PID is probably used in different FW's +// { MAKE_CONTROLLER_ID( 0x7545, 0x1122 ), k_eControllerType_PS4Controller, NULL }, // Giotek VX4 - trackpad/gyro don't work. Had to not filter on interface info. Light bar is flaky, but works. + + { MAKE_CONTROLLER_ID( 0x054c, 0x0ce6 ), k_eControllerType_PS5Controller, NULL }, // Sony DualSense Controller + { MAKE_CONTROLLER_ID( 0x054c, 0x0df2 ), k_eControllerType_PS5Controller, NULL }, // Sony DualSense Edge Controller + { MAKE_CONTROLLER_ID( 0x054c, 0x0e5f ), k_eControllerType_PS5Controller, NULL }, // Access Controller for PS5 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0209 ), k_eControllerType_PS5Controller, NULL }, // Victrix Pro FS PS4/PS5 (PS5 mode) + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0163 ), k_eControllerType_PS5Controller, NULL }, // HORI Fighting Commander OCTA + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0184 ), k_eControllerType_PS5Controller, NULL }, // Hori Fighting Stick α + { MAKE_CONTROLLER_ID( 0x1532, 0x100b ), k_eControllerType_PS5Controller, NULL }, // Razer Wolverine V2 Pro (Wired) + { MAKE_CONTROLLER_ID( 0x1532, 0x100c ), k_eControllerType_PS5Controller, NULL }, // Razer Wolverine V2 Pro (Wireless) + { MAKE_CONTROLLER_ID( 0x1532, 0x1012 ), k_eControllerType_PS5Controller, NULL }, // Razer Kitsune + { MAKE_CONTROLLER_ID( 0x3285, 0x0d18 ), k_eControllerType_PS5Controller, NULL }, // NACON Revolution 5 Pro (PS5 mode with dongle) + { MAKE_CONTROLLER_ID( 0x3285, 0x0d19 ), k_eControllerType_PS5Controller, NULL }, // NACON Revolution 5 Pro (PS5 mode wired) + { MAKE_CONTROLLER_ID( 0x358a, 0x0104 ), k_eControllerType_PS5Controller, NULL }, // Backbone One PlayStation Edition for iOS + + { MAKE_CONTROLLER_ID( 0x0079, 0x0006 ), k_eControllerType_UnknownNonSteamController, NULL }, // DragonRise Generic USB PCB, sometimes configured as a PC Twin Shock Controller - looks like a DS3 but the face buttons are 1-4 instead of symbols + + { MAKE_CONTROLLER_ID( 0x0079, 0x18d4 ), k_eControllerType_XBox360Controller, NULL }, // GPD Win 2 X-Box Controller + { MAKE_CONTROLLER_ID( 0x03eb, 0xff02 ), k_eControllerType_XBox360Controller, NULL }, // Wooting Two + { MAKE_CONTROLLER_ID( 0x044f, 0xb326 ), k_eControllerType_XBox360Controller, NULL }, // Thrustmaster Gamepad GP XID + { MAKE_CONTROLLER_ID( 0x045e, 0x028e ), k_eControllerType_XBox360Controller, "Xbox 360 Controller" }, // Microsoft Xbox 360 Wired Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x028f ), k_eControllerType_XBox360Controller, "Xbox 360 Controller" }, // Microsoft Xbox 360 Play and Charge Cable + { MAKE_CONTROLLER_ID( 0x045e, 0x0291 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // X-box 360 Wireless Receiver (third party knockoff) + { MAKE_CONTROLLER_ID( 0x045e, 0x02a0 ), k_eControllerType_XBox360Controller, NULL }, // Microsoft Xbox 360 Big Button IR + { MAKE_CONTROLLER_ID( 0x045e, 0x02a1 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // Microsoft Xbox 360 Wireless Controller with XUSB driver on Windows + { MAKE_CONTROLLER_ID( 0x045e, 0x02a9 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // X-box 360 Wireless Receiver (third party knockoff) + { MAKE_CONTROLLER_ID( 0x045e, 0x0719 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // Microsoft Xbox 360 Wireless Receiver + { MAKE_CONTROLLER_ID( 0x046d, 0xc21d ), k_eControllerType_XBox360Controller, NULL }, // Logitech Gamepad F310 + { MAKE_CONTROLLER_ID( 0x046d, 0xc21e ), k_eControllerType_XBox360Controller, NULL }, // Logitech Gamepad F510 + { MAKE_CONTROLLER_ID( 0x046d, 0xc21f ), k_eControllerType_XBox360Controller, NULL }, // Logitech Gamepad F710 + { MAKE_CONTROLLER_ID( 0x046d, 0xc242 ), k_eControllerType_XBox360Controller, NULL }, // Logitech Chillstream Controller + { MAKE_CONTROLLER_ID( 0x056e, 0x2004 ), k_eControllerType_XBox360Controller, NULL }, // Elecom JC-U3613M +// This isn't actually an Xbox 360 controller, it just looks like one +// { MAKE_CONTROLLER_ID( 0x06a3, 0xf51a ), k_eControllerType_XBox360Controller, NULL }, // Saitek P3600 + { MAKE_CONTROLLER_ID( 0x0738, 0x4716 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Wired Xbox 360 Controller + { MAKE_CONTROLLER_ID( 0x0738, 0x4718 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Street Fighter IV FightStick SE + { MAKE_CONTROLLER_ID( 0x0738, 0x4726 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Xbox 360 Controller + { MAKE_CONTROLLER_ID( 0x0738, 0x4728 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Street Fighter IV FightPad + { MAKE_CONTROLLER_ID( 0x0738, 0x4736 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz MicroCon Gamepad + { MAKE_CONTROLLER_ID( 0x0738, 0x4738 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Wired Xbox 360 Controller (SFIV) + { MAKE_CONTROLLER_ID( 0x0738, 0x4740 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Beat Pad + { MAKE_CONTROLLER_ID( 0x0738, 0xb726 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Xbox controller - MW2 + { MAKE_CONTROLLER_ID( 0x0738, 0xbeef ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz JOYTECH NEO SE Advanced GamePad + { MAKE_CONTROLLER_ID( 0x0738, 0xcb02 ), k_eControllerType_XBox360Controller, NULL }, // Saitek Cyborg Rumble Pad - PC/Xbox 360 + { MAKE_CONTROLLER_ID( 0x0738, 0xcb03 ), k_eControllerType_XBox360Controller, NULL }, // Saitek P3200 Rumble Pad - PC/Xbox 360 + { MAKE_CONTROLLER_ID( 0x0738, 0xf738 ), k_eControllerType_XBox360Controller, NULL }, // Super SFIV FightStick TE S + { MAKE_CONTROLLER_ID( 0x0955, 0x7210 ), k_eControllerType_XBox360Controller, NULL }, // Nvidia Shield local controller + { MAKE_CONTROLLER_ID( 0x0955, 0xb400 ), k_eControllerType_XBox360Controller, NULL }, // NVIDIA Shield streaming controller + { MAKE_CONTROLLER_ID( 0x0b05, 0x1b4c ), k_eControllerType_XBox360Controller, NULL }, // ASUS ROG Ally X built-in controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0105 ), k_eControllerType_XBox360Controller, NULL }, // HSM3 Xbox360 dancepad + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0113 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Afterglow" }, // PDP Afterglow Gamepad for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x011f ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Rock Candy" }, // PDP Rock Candy Gamepad for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0125 ), k_eControllerType_XBox360Controller, "PDP INJUSTICE FightStick" }, // PDP INJUSTICE FightStick for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0127 ), k_eControllerType_XBox360Controller, "PDP INJUSTICE FightPad" }, // PDP INJUSTICE FightPad for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0131 ), k_eControllerType_XBox360Controller, "PDP EA Soccer Controller" }, // PDP EA Soccer Gamepad + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0133 ), k_eControllerType_XBox360Controller, "PDP Battlefield 4 Controller" }, // PDP Battlefield 4 Gamepad + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0143 ), k_eControllerType_XBox360Controller, "PDP MK X Fight Stick" }, // PDP MK X Fight Stick for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0147 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Marvel Controller" }, // PDP Marvel Controller for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0201 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Controller" }, // PDP Gamepad for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0213 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Afterglow" }, // PDP Afterglow Gamepad for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x021f ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Rock Candy" }, // PDP Rock Candy Gamepad for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0301 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Controller" }, // PDP Gamepad for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0313 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Afterglow" }, // PDP Afterglow Gamepad for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0314 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Afterglow" }, // PDP Afterglow Gamepad for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0401 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Controller" }, // PDP Gamepad for Xbox 360 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0413 ), k_eControllerType_XBox360Controller, NULL }, // PDP Afterglow AX.1 (unlisted) + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0501 ), k_eControllerType_XBox360Controller, NULL }, // PDP Xbox 360 Controller (unlisted) + { MAKE_CONTROLLER_ID( 0x0e6f, 0xf900 ), k_eControllerType_XBox360Controller, NULL }, // PDP Afterglow AX.1 (unlisted) + { MAKE_CONTROLLER_ID( 0x0f0d, 0x000a ), k_eControllerType_XBox360Controller, NULL }, // Hori Co. DOA4 FightStick + { MAKE_CONTROLLER_ID( 0x0f0d, 0x000c ), k_eControllerType_XBox360Controller, NULL }, // Hori PadEX Turbo + { MAKE_CONTROLLER_ID( 0x0f0d, 0x000d ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Stick EX2 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0016 ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro.EX + { MAKE_CONTROLLER_ID( 0x0f0d, 0x001b ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro VX + { MAKE_CONTROLLER_ID( 0x0f0d, 0x008c ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro 4 + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00db ), k_eControllerType_XBox360Controller, "HORI Slime Controller" }, // Hori Dragon Quest Slime Controller + { MAKE_CONTROLLER_ID( 0x0f0d, 0x011e ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Stick α + { MAKE_CONTROLLER_ID( 0x1038, 0x1430 ), k_eControllerType_XBox360Controller, "SteelSeries Stratus Duo" }, // SteelSeries Stratus Duo + { MAKE_CONTROLLER_ID( 0x1038, 0x1431 ), k_eControllerType_XBox360Controller, "SteelSeries Stratus Duo" }, // SteelSeries Stratus Duo + { MAKE_CONTROLLER_ID( 0x1038, 0xb360 ), k_eControllerType_XBox360Controller, NULL }, // SteelSeries Nimbus/Stratus XL + { MAKE_CONTROLLER_ID( 0x11c9, 0x55f0 ), k_eControllerType_XBox360Controller, NULL }, // Nacon GC-100XF + { MAKE_CONTROLLER_ID( 0x12ab, 0x0004 ), k_eControllerType_XBox360Controller, NULL }, // Honey Bee Xbox360 dancepad + { MAKE_CONTROLLER_ID( 0x12ab, 0x0301 ), k_eControllerType_XBox360Controller, NULL }, // PDP AFTERGLOW AX.1 + { MAKE_CONTROLLER_ID( 0x12ab, 0x0303 ), k_eControllerType_XBox360Controller, NULL }, // Mortal Kombat Klassic FightStick + { MAKE_CONTROLLER_ID( 0x1430, 0x02a0 ), k_eControllerType_XBox360Controller, NULL }, // RedOctane Controller Adapter + { MAKE_CONTROLLER_ID( 0x1430, 0x4748 ), k_eControllerType_XBox360Controller, NULL }, // RedOctane Guitar Hero X-plorer + { MAKE_CONTROLLER_ID( 0x1430, 0xf801 ), k_eControllerType_XBox360Controller, NULL }, // RedOctane Controller + { MAKE_CONTROLLER_ID( 0x146b, 0x0601 ), k_eControllerType_XBox360Controller, NULL }, // BigBen Interactive XBOX 360 Controller +// { MAKE_CONTROLLER_ID( 0x1532, 0x0037 ), k_eControllerType_XBox360Controller, NULL }, // Razer Sabertooth + { MAKE_CONTROLLER_ID( 0x15e4, 0x3f00 ), k_eControllerType_XBox360Controller, NULL }, // Power A Mini Pro Elite + { MAKE_CONTROLLER_ID( 0x15e4, 0x3f0a ), k_eControllerType_XBox360Controller, NULL }, // Xbox Airflo wired controller + { MAKE_CONTROLLER_ID( 0x15e4, 0x3f10 ), k_eControllerType_XBox360Controller, NULL }, // Batarang Xbox 360 controller + { MAKE_CONTROLLER_ID( 0x162e, 0xbeef ), k_eControllerType_XBox360Controller, NULL }, // Joytech Neo-Se Take2 + { MAKE_CONTROLLER_ID( 0x1689, 0xfd00 ), k_eControllerType_XBox360Controller, NULL }, // Razer Onza Tournament Edition + { MAKE_CONTROLLER_ID( 0x1689, 0xfd01 ), k_eControllerType_XBox360Controller, NULL }, // Razer Onza Classic Edition + { MAKE_CONTROLLER_ID( 0x1689, 0xfe00 ), k_eControllerType_XBox360Controller, NULL }, // Razer Sabertooth + { MAKE_CONTROLLER_ID( 0x1949, 0x041a ), k_eControllerType_XBox360Controller, "Amazon Luna Controller" }, // Amazon Luna Controller + { MAKE_CONTROLLER_ID( 0x1bad, 0x0002 ), k_eControllerType_XBox360Controller, NULL }, // Harmonix Rock Band Guitar + { MAKE_CONTROLLER_ID( 0x1bad, 0x0003 ), k_eControllerType_XBox360Controller, NULL }, // Harmonix Rock Band Drumkit + { MAKE_CONTROLLER_ID( 0x1bad, 0xf016 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Xbox 360 Controller + { MAKE_CONTROLLER_ID( 0x1bad, 0xf018 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Street Fighter IV SE Fighting Stick + { MAKE_CONTROLLER_ID( 0x1bad, 0xf019 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Brawlstick for Xbox 360 + { MAKE_CONTROLLER_ID( 0x1bad, 0xf021 ), k_eControllerType_XBox360Controller, NULL }, // Mad Cats Ghost Recon FS GamePad + { MAKE_CONTROLLER_ID( 0x1bad, 0xf023 ), k_eControllerType_XBox360Controller, NULL }, // MLG Pro Circuit Controller (Xbox) + { MAKE_CONTROLLER_ID( 0x1bad, 0xf025 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Call Of Duty + { MAKE_CONTROLLER_ID( 0x1bad, 0xf027 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz FPS Pro + { MAKE_CONTROLLER_ID( 0x1bad, 0xf028 ), k_eControllerType_XBox360Controller, NULL }, // Street Fighter IV FightPad + { MAKE_CONTROLLER_ID( 0x1bad, 0xf02e ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Fightpad + { MAKE_CONTROLLER_ID( 0x1bad, 0xf036 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz MicroCon GamePad Pro + { MAKE_CONTROLLER_ID( 0x1bad, 0xf038 ), k_eControllerType_XBox360Controller, NULL }, // Street Fighter IV FightStick TE + { MAKE_CONTROLLER_ID( 0x1bad, 0xf039 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz MvC2 TE + { MAKE_CONTROLLER_ID( 0x1bad, 0xf03a ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz SFxT Fightstick Pro + { MAKE_CONTROLLER_ID( 0x1bad, 0xf03d ), k_eControllerType_XBox360Controller, NULL }, // Street Fighter IV Arcade Stick TE - Chun Li + { MAKE_CONTROLLER_ID( 0x1bad, 0xf03e ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz MLG FightStick TE + { MAKE_CONTROLLER_ID( 0x1bad, 0xf03f ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz FightStick SoulCaliber + { MAKE_CONTROLLER_ID( 0x1bad, 0xf042 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz FightStick TES+ + { MAKE_CONTROLLER_ID( 0x1bad, 0xf080 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz FightStick TE2 + { MAKE_CONTROLLER_ID( 0x1bad, 0xf501 ), k_eControllerType_XBox360Controller, NULL }, // HoriPad EX2 Turbo + { MAKE_CONTROLLER_ID( 0x1bad, 0xf502 ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro.VX SA + { MAKE_CONTROLLER_ID( 0x1bad, 0xf503 ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Stick VX + { MAKE_CONTROLLER_ID( 0x1bad, 0xf504 ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro. EX + { MAKE_CONTROLLER_ID( 0x1bad, 0xf505 ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Stick EX2B + { MAKE_CONTROLLER_ID( 0x1bad, 0xf506 ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro.EX Premium VLX + { MAKE_CONTROLLER_ID( 0x1bad, 0xf900 ), k_eControllerType_XBox360Controller, NULL }, // Harmonix Xbox 360 Controller + { MAKE_CONTROLLER_ID( 0x1bad, 0xf901 ), k_eControllerType_XBox360Controller, NULL }, // Gamestop Xbox 360 Controller + { MAKE_CONTROLLER_ID( 0x1bad, 0xf902 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Gamepad2 + { MAKE_CONTROLLER_ID( 0x1bad, 0xf903 ), k_eControllerType_XBox360Controller, NULL }, // Tron Xbox 360 controller + { MAKE_CONTROLLER_ID( 0x1bad, 0xf904 ), k_eControllerType_XBox360Controller, NULL }, // PDP Versus Fighting Pad + { MAKE_CONTROLLER_ID( 0x1bad, 0xf906 ), k_eControllerType_XBox360Controller, NULL }, // MortalKombat FightStick + { MAKE_CONTROLLER_ID( 0x1bad, 0xfa01 ), k_eControllerType_XBox360Controller, NULL }, // MadCatz GamePad + { MAKE_CONTROLLER_ID( 0x1bad, 0xfd00 ), k_eControllerType_XBox360Controller, NULL }, // Razer Onza TE + { MAKE_CONTROLLER_ID( 0x1bad, 0xfd01 ), k_eControllerType_XBox360Controller, NULL }, // Razer Onza + { MAKE_CONTROLLER_ID( 0x24c6, 0x5000 ), k_eControllerType_XBox360Controller, NULL }, // Razer Atrox Arcade Stick + { MAKE_CONTROLLER_ID( 0x24c6, 0x5300 ), k_eControllerType_XBox360Controller, NULL }, // PowerA MINI PROEX Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x5303 ), k_eControllerType_XBox360Controller, NULL }, // Xbox Airflo wired controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x530a ), k_eControllerType_XBox360Controller, NULL }, // Xbox 360 Pro EX Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x531a ), k_eControllerType_XBox360Controller, NULL }, // PowerA Pro Ex + { MAKE_CONTROLLER_ID( 0x24c6, 0x5397 ), k_eControllerType_XBox360Controller, NULL }, // FUS1ON Tournament Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x5500 ), k_eControllerType_XBox360Controller, NULL }, // Hori XBOX 360 EX 2 with Turbo + { MAKE_CONTROLLER_ID( 0x24c6, 0x5501 ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro VX-SA + { MAKE_CONTROLLER_ID( 0x24c6, 0x5502 ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Stick VX Alt + { MAKE_CONTROLLER_ID( 0x24c6, 0x5503 ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Edge + { MAKE_CONTROLLER_ID( 0x24c6, 0x5506 ), k_eControllerType_XBox360Controller, NULL }, // Hori SOULCALIBUR V Stick + { MAKE_CONTROLLER_ID( 0x24c6, 0x550d ), k_eControllerType_XBox360Controller, NULL }, // Hori GEM Xbox controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x550e ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro V Kai 360 + { MAKE_CONTROLLER_ID( 0x24c6, 0x5508 ), k_eControllerType_XBox360Controller, NULL }, // Hori PAD A + { MAKE_CONTROLLER_ID( 0x24c6, 0x5510 ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Commander ONE + { MAKE_CONTROLLER_ID( 0x24c6, 0x5b00 ), k_eControllerType_XBox360Controller, NULL }, // ThrustMaster Ferrari Italia 458 Racing Wheel + { MAKE_CONTROLLER_ID( 0x24c6, 0x5b02 ), k_eControllerType_XBox360Controller, NULL }, // Thrustmaster, Inc. GPX Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x5b03 ), k_eControllerType_XBox360Controller, NULL }, // Thrustmaster Ferrari 458 Racing Wheel + { MAKE_CONTROLLER_ID( 0x24c6, 0x5d04 ), k_eControllerType_XBox360Controller, NULL }, // Razer Sabertooth + { MAKE_CONTROLLER_ID( 0x24c6, 0xfafa ), k_eControllerType_XBox360Controller, NULL }, // Aplay Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0xfafb ), k_eControllerType_XBox360Controller, NULL }, // Aplay Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0xfafc ), k_eControllerType_XBox360Controller, NULL }, // Afterglow Gamepad 1 + { MAKE_CONTROLLER_ID( 0x24c6, 0xfafd ), k_eControllerType_XBox360Controller, NULL }, // Afterglow Gamepad 3 + { MAKE_CONTROLLER_ID( 0x24c6, 0xfafe ), k_eControllerType_XBox360Controller, NULL }, // Rock Candy Gamepad for Xbox 360 + + { MAKE_CONTROLLER_ID( 0x03f0, 0x0495 ), k_eControllerType_XBoxOneController, NULL }, // HP HyperX Clutch Gladiate + { MAKE_CONTROLLER_ID( 0x044f, 0xd012 ), k_eControllerType_XBoxOneController, NULL }, // ThrustMaster eSwap PRO Controller Xbox + { MAKE_CONTROLLER_ID( 0x045e, 0x02d1 ), k_eControllerType_XBoxOneController, "Xbox One Controller" }, // Microsoft Xbox One Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x02dd ), k_eControllerType_XBoxOneController, "Xbox One Controller" }, // Microsoft Xbox One Controller (Firmware 2015) + { MAKE_CONTROLLER_ID( 0x045e, 0x02e0 ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft Xbox One S Controller (Bluetooth) + { MAKE_CONTROLLER_ID( 0x045e, 0x02e3 ), k_eControllerType_XBoxOneController, "Xbox One Elite Controller" }, // Microsoft Xbox One Elite Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x02ea ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft Xbox One S Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x02fd ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft Xbox One S Controller (Bluetooth) + { MAKE_CONTROLLER_ID( 0x045e, 0x02ff ), k_eControllerType_XBoxOneController, "Xbox One Controller" }, // Microsoft Xbox One Controller with XBOXGIP driver on Windows + { MAKE_CONTROLLER_ID( 0x045e, 0x0b00 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // Microsoft Xbox One Elite Series 2 Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x0b05 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // Microsoft Xbox One Elite Series 2 Controller (Bluetooth) + { MAKE_CONTROLLER_ID( 0x045e, 0x0b0a ), k_eControllerType_XBoxOneController, "Xbox Adaptive Controller" }, // Microsoft Xbox Adaptive Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x0b0c ), k_eControllerType_XBoxOneController, "Xbox Adaptive Controller" }, // Microsoft Xbox Adaptive Controller (Bluetooth) + { MAKE_CONTROLLER_ID( 0x045e, 0x0b12 ), k_eControllerType_XBoxOneController, "Xbox Series X Controller" }, // Microsoft Xbox Series X Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x0b13 ), k_eControllerType_XBoxOneController, "Xbox Series X Controller" }, // Microsoft Xbox Series X Controller (BLE) + { MAKE_CONTROLLER_ID( 0x045e, 0x0b20 ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft Xbox One S Controller (BLE) + { MAKE_CONTROLLER_ID( 0x045e, 0x0b21 ), k_eControllerType_XBoxOneController, "Xbox Adaptive Controller" }, // Microsoft Xbox Adaptive Controller (BLE) + { MAKE_CONTROLLER_ID( 0x045e, 0x0b22 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // Microsoft Xbox One Elite Series 2 Controller (BLE) + { MAKE_CONTROLLER_ID( 0x0738, 0x4a01 ), k_eControllerType_XBoxOneController, NULL }, // Mad Catz FightStick TE 2 + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0139 ), k_eControllerType_XBoxOneController, "PDP Xbox One Afterglow" }, // PDP Afterglow Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x013B ), k_eControllerType_XBoxOneController, "PDP Xbox One Face-Off Controller" }, // PDP Face-Off Gamepad for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x013a ), k_eControllerType_XBoxOneController, NULL }, // PDP Xbox One Controller (unlisted) + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0145 ), k_eControllerType_XBoxOneController, "PDP MK X Fight Pad" }, // PDP MK X Fight Pad for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0146 ), k_eControllerType_XBoxOneController, "PDP Xbox One Rock Candy" }, // PDP Rock Candy Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x015b ), k_eControllerType_XBoxOneController, "PDP Fallout 4 Vault Boy Controller" }, // PDP Fallout 4 Vault Boy Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x015c ), k_eControllerType_XBoxOneController, "PDP Xbox One @Play Controller" }, // PDP @Play Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x015d ), k_eControllerType_XBoxOneController, "PDP Mirror's Edge Controller" }, // PDP Mirror's Edge Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x015f ), k_eControllerType_XBoxOneController, "PDP Metallic Controller" }, // PDP Metallic Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0160 ), k_eControllerType_XBoxOneController, "PDP NFL Face-Off Controller" }, // PDP NFL Official Face-Off Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0161 ), k_eControllerType_XBoxOneController, "PDP Xbox One Camo" }, // PDP Camo Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0162 ), k_eControllerType_XBoxOneController, "PDP Xbox One Controller" }, // PDP Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0163 ), k_eControllerType_XBoxOneController, "PDP Deliverer of Truth" }, // PDP Legendary Collection: Deliverer of Truth + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0164 ), k_eControllerType_XBoxOneController, "PDP Battlefield 1 Controller" }, // PDP Battlefield 1 Official Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0165 ), k_eControllerType_XBoxOneController, "PDP Titanfall 2 Controller" }, // PDP Titanfall 2 Official Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0166 ), k_eControllerType_XBoxOneController, "PDP Mass Effect: Andromeda Controller" }, // PDP Mass Effect: Andromeda Official Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0167 ), k_eControllerType_XBoxOneController, "PDP Halo Wars 2 Face-Off Controller" }, // PDP Halo Wars 2 Official Face-Off Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0205 ), k_eControllerType_XBoxOneController, "PDP Victrix Pro Fight Stick" }, // PDP Victrix Pro Fight Stick + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0206 ), k_eControllerType_XBoxOneController, "PDP Mortal Kombat Controller" }, // PDP Mortal Kombat 25 Anniversary Edition Stick (Xbox One) + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0246 ), k_eControllerType_XBoxOneController, "PDP Xbox One Rock Candy" }, // PDP Rock Candy Wired Controller for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0261 ), k_eControllerType_XBoxOneController, "PDP Xbox One Camo" }, // PDP Camo Wired Controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0262 ), k_eControllerType_XBoxOneController, "PDP Xbox One Controller" }, // PDP Wired Controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a0 ), k_eControllerType_XBoxOneController, "PDP Xbox One Midnight Blue" }, // PDP Wired Controller for Xbox One - Midnight Blue + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a1 ), k_eControllerType_XBoxOneController, "PDP Xbox One Verdant Green" }, // PDP Wired Controller for Xbox One - Verdant Green + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a2 ), k_eControllerType_XBoxOneController, "PDP Xbox One Crimson Red" }, // PDP Wired Controller for Xbox One - Crimson Red + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a3 ), k_eControllerType_XBoxOneController, "PDP Xbox One Arctic White" }, // PDP Wired Controller for Xbox One - Arctic White + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a4 ), k_eControllerType_XBoxOneController, "PDP Xbox One Phantom Black" }, // PDP Wired Controller for Xbox One - Stealth Series | Phantom Black + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a5 ), k_eControllerType_XBoxOneController, "PDP Xbox One Ghost White" }, // PDP Wired Controller for Xbox One - Stealth Series | Ghost White + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a6 ), k_eControllerType_XBoxOneController, "PDP Xbox One Revenant Blue" }, // PDP Wired Controller for Xbox One - Stealth Series | Revenant Blue + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a7 ), k_eControllerType_XBoxOneController, "PDP Xbox One Raven Black" }, // PDP Wired Controller for Xbox One - Raven Black + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a8 ), k_eControllerType_XBoxOneController, "PDP Xbox One Arctic White" }, // PDP Wired Controller for Xbox One - Arctic White + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a9 ), k_eControllerType_XBoxOneController, "PDP Xbox One Midnight Blue" }, // PDP Wired Controller for Xbox One - Midnight Blue + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02aa ), k_eControllerType_XBoxOneController, "PDP Xbox One Verdant Green" }, // PDP Wired Controller for Xbox One - Verdant Green + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02ab ), k_eControllerType_XBoxOneController, "PDP Xbox One Crimson Red" }, // PDP Wired Controller for Xbox One - Crimson Red + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02ac ), k_eControllerType_XBoxOneController, "PDP Xbox One Ember Orange" }, // PDP Wired Controller for Xbox One - Ember Orange + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02ad ), k_eControllerType_XBoxOneController, "PDP Xbox One Phantom Black" }, // PDP Wired Controller for Xbox One - Stealth Series | Phantom Black + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02ae ), k_eControllerType_XBoxOneController, "PDP Xbox One Ghost White" }, // PDP Wired Controller for Xbox One - Stealth Series | Ghost White + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02af ), k_eControllerType_XBoxOneController, "PDP Xbox One Revenant Blue" }, // PDP Wired Controller for Xbox One - Stealth Series | Revenant Blue + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02b0 ), k_eControllerType_XBoxOneController, "PDP Xbox One Raven Black" }, // PDP Wired Controller for Xbox One - Raven Black + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02b1 ), k_eControllerType_XBoxOneController, "PDP Xbox One Arctic White" }, // PDP Wired Controller for Xbox One - Arctic White + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02b3 ), k_eControllerType_XBoxOneController, "PDP Xbox One Afterglow" }, // PDP Afterglow Prismatic Wired Controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02b5 ), k_eControllerType_XBoxOneController, "PDP Xbox One GAMEware Controller" }, // PDP GAMEware Wired Controller Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02b6 ), k_eControllerType_XBoxOneController, NULL }, // PDP One-Handed Joystick Adaptive Controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02bd ), k_eControllerType_XBoxOneController, "PDP Xbox One Royal Purple" }, // PDP Wired Controller for Xbox One - Royal Purple + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02be ), k_eControllerType_XBoxOneController, "PDP Xbox One Raven Black" }, // PDP Deluxe Wired Controller for Xbox One - Raven Black + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02bf ), k_eControllerType_XBoxOneController, "PDP Xbox One Midnight Blue" }, // PDP Deluxe Wired Controller for Xbox One - Midnight Blue + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c0 ), k_eControllerType_XBoxOneController, "PDP Xbox One Phantom Black" }, // PDP Deluxe Wired Controller for Xbox One - Stealth Series | Phantom Black + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c1 ), k_eControllerType_XBoxOneController, "PDP Xbox One Ghost White" }, // PDP Deluxe Wired Controller for Xbox One - Stealth Series | Ghost White + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c2 ), k_eControllerType_XBoxOneController, "PDP Xbox One Revenant Blue" }, // PDP Deluxe Wired Controller for Xbox One - Stealth Series | Revenant Blue + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c3 ), k_eControllerType_XBoxOneController, "PDP Xbox One Verdant Green" }, // PDP Deluxe Wired Controller for Xbox One - Verdant Green + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c4 ), k_eControllerType_XBoxOneController, "PDP Xbox One Ember Orange" }, // PDP Deluxe Wired Controller for Xbox One - Ember Orange + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c5 ), k_eControllerType_XBoxOneController, "PDP Xbox One Royal Purple" }, // PDP Deluxe Wired Controller for Xbox One - Royal Purple + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c6 ), k_eControllerType_XBoxOneController, "PDP Xbox One Crimson Red" }, // PDP Deluxe Wired Controller for Xbox One - Crimson Red + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c7 ), k_eControllerType_XBoxOneController, "PDP Xbox One Arctic White" }, // PDP Deluxe Wired Controller for Xbox One - Arctic White + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c8 ), k_eControllerType_XBoxOneController, "PDP Kingdom Hearts Controller" }, // PDP Kingdom Hearts Wired Controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c9 ), k_eControllerType_XBoxOneController, "PDP Xbox One Phantasm Red" }, // PDP Deluxe Wired Controller for Xbox One - Stealth Series | Phantasm Red + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02ca ), k_eControllerType_XBoxOneController, "PDP Xbox One Specter Violet" }, // PDP Deluxe Wired Controller for Xbox One - Stealth Series | Specter Violet + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02cb ), k_eControllerType_XBoxOneController, "PDP Xbox One Specter Violet" }, // PDP Wired Controller for Xbox One - Stealth Series | Specter Violet + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02cd ), k_eControllerType_XBoxOneController, "PDP Xbox One Blu-merang" }, // PDP Rock Candy Wired Controller for Xbox One - Blu-merang + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02ce ), k_eControllerType_XBoxOneController, "PDP Xbox One Cranblast" }, // PDP Rock Candy Wired Controller for Xbox One - Cranblast + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02cf ), k_eControllerType_XBoxOneController, "PDP Xbox One Aqualime" }, // PDP Rock Candy Wired Controller for Xbox One - Aqualime + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02d5 ), k_eControllerType_XBoxOneController, "PDP Xbox One Red Camo" }, // PDP Wired Controller for Xbox One - Red Camo + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0346 ), k_eControllerType_XBoxOneController, "PDP Xbox One RC Gamepad" }, // PDP RC Gamepad for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0446 ), k_eControllerType_XBoxOneController, "PDP Xbox One RC Gamepad" }, // PDP RC Gamepad for Xbox One + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02da ), k_eControllerType_XBoxOneController, "PDP Xbox Series X Afterglow" }, // PDP Xbox Series X Afterglow + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02d6 ), k_eControllerType_XBoxOneController, "Victrix Gambit Tournament Controller" }, // Victrix Gambit Tournament Controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x02d9 ), k_eControllerType_XBoxOneController, "PDP Xbox Series X Midnight Blue" }, // PDP Xbox Series X Midnight Blue + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0063 ), k_eControllerType_XBoxOneController, NULL }, // Hori Real Arcade Pro Hayabusa (USA) Xbox One + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0067 ), k_eControllerType_XBoxOneController, NULL }, // HORIPAD ONE + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0078 ), k_eControllerType_XBoxOneController, NULL }, // Hori Real Arcade Pro V Kai Xbox One + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00c5 ), k_eControllerType_XBoxOneController, NULL }, // HORI Fighting Commander + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0150 ), k_eControllerType_XBoxOneController, NULL }, // HORI Fighting Commander OCTA for Xbox Series X + { MAKE_CONTROLLER_ID( 0x10f5, 0x7009 ), k_eControllerType_XBoxOneController, NULL }, // Turtle Beach Recon Controller + { MAKE_CONTROLLER_ID( 0x10f5, 0x7013 ), k_eControllerType_XBoxOneController, NULL }, // Turtle Beach REACT-R + { MAKE_CONTROLLER_ID( 0x1532, 0x0a00 ), k_eControllerType_XBoxOneController, NULL }, // Razer Atrox Arcade Stick + { MAKE_CONTROLLER_ID( 0x1532, 0x0a03 ), k_eControllerType_XBoxOneController, NULL }, // Razer Wildcat + { MAKE_CONTROLLER_ID( 0x1532, 0x0a14 ), k_eControllerType_XBoxOneController, NULL }, // Razer Wolverine Ultimate + { MAKE_CONTROLLER_ID( 0x1532, 0x0a15 ), k_eControllerType_XBoxOneController, NULL }, // Razer Wolverine Tournament Edition + { MAKE_CONTROLLER_ID( 0x20d6, 0x2001 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Black Inline + { MAKE_CONTROLLER_ID( 0x20d6, 0x2002 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Gray/White Inline + { MAKE_CONTROLLER_ID( 0x20d6, 0x2003 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Green Inline + { MAKE_CONTROLLER_ID( 0x20d6, 0x2004 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Pink inline + { MAKE_CONTROLLER_ID( 0x20d6, 0x2005 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X Wired Controller Core - Black + { MAKE_CONTROLLER_ID( 0x20d6, 0x2006 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X Wired Controller Core - White + { MAKE_CONTROLLER_ID( 0x20d6, 0x2009 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Red inline + { MAKE_CONTROLLER_ID( 0x20d6, 0x200a ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Blue inline + { MAKE_CONTROLLER_ID( 0x20d6, 0x200b ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Camo Metallic Red + { MAKE_CONTROLLER_ID( 0x20d6, 0x200c ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Camo Metallic Blue + { MAKE_CONTROLLER_ID( 0x20d6, 0x200d ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Seafoam Fade + { MAKE_CONTROLLER_ID( 0x20d6, 0x200e ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Midnight Blue + { MAKE_CONTROLLER_ID( 0x20d6, 0x200f ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Soldier Green + { MAKE_CONTROLLER_ID( 0x20d6, 0x2011 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired - Metallic Ice + { MAKE_CONTROLLER_ID( 0x20d6, 0x2012 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X Cuphead EnWired Controller - Mugman + { MAKE_CONTROLLER_ID( 0x20d6, 0x2015 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Blue Hint + { MAKE_CONTROLLER_ID( 0x20d6, 0x2016 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Green Hint + { MAKE_CONTROLLER_ID( 0x20d6, 0x2017 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Cntroller - Arctic Camo + { MAKE_CONTROLLER_ID( 0x20d6, 0x2018 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Arc Lightning + { MAKE_CONTROLLER_ID( 0x20d6, 0x2019 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Royal Purple + { MAKE_CONTROLLER_ID( 0x20d6, 0x201a ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Nebula + { MAKE_CONTROLLER_ID( 0x20d6, 0x4001 ), k_eControllerType_XBoxOneController, "PowerA Fusion Pro 2 Controller" }, // PowerA Fusion Pro 2 Wired Controller (Xbox Series X style) + { MAKE_CONTROLLER_ID( 0x20d6, 0x4002 ), k_eControllerType_XBoxOneController, "PowerA Spectra Infinity Controller" }, // PowerA Spectra Infinity Wired Controller (Xbox Series X style) + { MAKE_CONTROLLER_ID( 0x20d6, 0x890b ), k_eControllerType_XBoxOneController, NULL }, // PowerA MOGA XP-Ultra Controller (Xbox Series X style) + { MAKE_CONTROLLER_ID( 0x24c6, 0x541a ), k_eControllerType_XBoxOneController, NULL }, // PowerA Xbox One Mini Wired Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x542a ), k_eControllerType_XBoxOneController, NULL }, // Xbox ONE spectra + { MAKE_CONTROLLER_ID( 0x24c6, 0x543a ), k_eControllerType_XBoxOneController, "PowerA Xbox One Controller" }, // PowerA Xbox ONE liquid metal controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x551a ), k_eControllerType_XBoxOneController, NULL }, // PowerA FUSION Pro Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x561a ), k_eControllerType_XBoxOneController, NULL }, // PowerA FUSION Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x581a ), k_eControllerType_XBoxOneController, NULL }, // BDA XB1 Classic Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x591a ), k_eControllerType_XBoxOneController, NULL }, // PowerA FUSION Pro Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x592a ), k_eControllerType_XBoxOneController, NULL }, // BDA XB1 Spectra Pro + { MAKE_CONTROLLER_ID( 0x24c6, 0x791a ), k_eControllerType_XBoxOneController, NULL }, // PowerA Fusion Fight Pad + { MAKE_CONTROLLER_ID( 0x2dc8, 0x2002 ), k_eControllerType_XBoxOneController, NULL }, // 8BitDo Ultimate Wired Controller for Xbox + { MAKE_CONTROLLER_ID( 0x2dc8, 0x3106 ), k_eControllerType_XBoxOneController, NULL }, // 8Bitdo Ultimate Wired Controller. Windows, Android, Switch. + { MAKE_CONTROLLER_ID( 0x2e24, 0x0652 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin Duke + { MAKE_CONTROLLER_ID( 0x2e24, 0x1618 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin Duke + { MAKE_CONTROLLER_ID( 0x2e24, 0x1688 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin X91 + { MAKE_CONTROLLER_ID( 0x146b, 0x0611 ), k_eControllerType_XBoxOneController, NULL }, // Xbox Controller Mode for NACON Revolution 3 + + // These have been added via Minidump for unrecognized Xinput controller assert + { MAKE_CONTROLLER_ID( 0x0000, 0x0000 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x045e, 0x02a2 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller - Microsoft VID + { MAKE_CONTROLLER_ID( 0x0e6f, 0x1414 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0159 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0xfaff ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0f0d, 0x006d ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00a4 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0079, 0x1832 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0079, 0x187f ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0079, 0x1883 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x03eb, 0xff01 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0c12, 0x0ef8 ), k_eControllerType_XBox360Controller, NULL }, // Homemade fightstick based on brook pcb (with XInput driver??) + { MAKE_CONTROLLER_ID( 0x046d, 0x1000 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x11ff, 0x0511 ), k_eControllerType_XBox360Controller, NULL }, // PXN V900 + { MAKE_CONTROLLER_ID( 0x1345, 0x6006 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + + { MAKE_CONTROLLER_ID( 0x056e, 0x2012 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x146b, 0x0602 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00ae ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x046d, 0x0401 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput + { MAKE_CONTROLLER_ID( 0x046d, 0x0301 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput + { MAKE_CONTROLLER_ID( 0x046d, 0xcaa3 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput + { MAKE_CONTROLLER_ID( 0x046d, 0xc261 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput + { MAKE_CONTROLLER_ID( 0x046d, 0x0291 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput + { MAKE_CONTROLLER_ID( 0x0079, 0x18d3 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00b1 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0001, 0x0001 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0079, 0x188e ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0079, 0x187c ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0079, 0x189c ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x0079, 0x1874 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller + + { MAKE_CONTROLLER_ID( 0x2f24, 0x0050 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x2f24, 0x2e ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x2f24, 0x91 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x1430, 0x719 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xf0d, 0xed ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xf0d, 0xc0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x152 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x2a7 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x46d, 0x1007 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x2b8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x2a8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x79, 0x18a1 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + + // Added from Minidumps 10-9-19 + { MAKE_CONTROLLER_ID( 0x0, 0x6686 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x12ab, 0x304 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x1430, 0x291 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x1430, 0x2a9 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x1430, 0x70b ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x1bad, 0x28e ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x1bad, 0x2a0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x1bad, 0x5500 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x20ab, 0x55ef ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x24c6, 0x5509 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x2516, 0x69 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x25b1, 0x360 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x2c22, 0x2203 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x2f24, 0x11 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x2f24, 0x53 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x2f24, 0xb7 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x46d, 0x0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x46d, 0x1004 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x46d, 0x1008 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x46d, 0xf301 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x738, 0x2a0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x738, 0x7263 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x738, 0xb738 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x738, 0xcb29 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x738, 0xf401 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x79, 0x18c2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x79, 0x18c8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x79, 0x18cf ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xc12, 0xe17 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xc12, 0xe1c ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xc12, 0xe22 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xc12, 0xe30 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xd2d2, 0xd2d2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xd62, 0x9a1a ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xd62, 0x9a1b ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe00, 0xe00 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x12a ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x2a1 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x2a2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x2a5 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x2b2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x2bd ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x2bf ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x2c0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0x2c6 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xf0d, 0x97 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xf0d, 0xba ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xf0d, 0xd8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xfff, 0x2a1 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x45e, 0x867 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + // Added 12-17-2020 + { MAKE_CONTROLLER_ID( 0x16d0, 0xf3f ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0x2f24, 0x8f ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + { MAKE_CONTROLLER_ID( 0xe6f, 0xf501 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller + + //{ MAKE_CONTROLLER_ID( 0x1949, 0x0402 ), /*android*/, NULL }, // Unknown Controller + + { MAKE_CONTROLLER_ID( 0x05ac, 0x0001 ), k_eControllerType_AppleController, NULL }, // MFI Extended Gamepad (generic entry for iOS/tvOS) + { MAKE_CONTROLLER_ID( 0x05ac, 0x0002 ), k_eControllerType_AppleController, NULL }, // MFI Standard Gamepad (generic entry for iOS/tvOS) + + { MAKE_CONTROLLER_ID( 0x057e, 0x2006 ), k_eControllerType_SwitchJoyConLeft, NULL }, // Nintendo Switch Joy-Con (Left) + { MAKE_CONTROLLER_ID( 0x057e, 0x2007 ), k_eControllerType_SwitchJoyConRight, NULL }, // Nintendo Switch Joy-Con (Right) + { MAKE_CONTROLLER_ID( 0x057e, 0x2008 ), k_eControllerType_SwitchJoyConPair, NULL }, // Nintendo Switch Joy-Con (Left+Right Combined) + + // This same controller ID is spoofed by many 3rd-party Switch controllers. + // The ones we currently know of are: + // * Any 8bitdo controller with Switch support + // * ORTZ Gaming Wireless Pro Controller + // * ZhiXu Gamepad Wireless + // * Sunwaytek Wireless Motion Controller for Nintendo Switch + { MAKE_CONTROLLER_ID( 0x057e, 0x2009 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Switch Pro Controller + //{ MAKE_CONTROLLER_ID( 0x057e, 0x2017 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online SNES Controller + //{ MAKE_CONTROLLER_ID( 0x057e, 0x2019 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online N64 Controller + //{ MAKE_CONTROLLER_ID( 0x057e, 0x201e ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online SEGA Genesis Controller + + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00c1 ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORIPAD for Nintendo Switch + { MAKE_CONTROLLER_ID( 0x0f0d, 0x0092 ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORI Pokken Tournament DX Pro Pad + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00f6 ), k_eControllerType_SwitchProController, NULL }, // HORI Wireless Switch Pad + // The HORIPAD S, which comes in multiple styles: + // - NSW-108, classic GameCube controller + // - NSW-244, Fighting Commander arcade pad + // - NSW-278, Hori Pad Mini gamepad + // - NSW-326, HORIPAD FPS for Nintendo Switch + // + // The first two, at least, shouldn't have their buttons remapped, and since we + // can't tell which model we're actually using, we won't do any button remapping + // for any of them. + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00dc ), k_eControllerType_XInputSwitchController, NULL }, // HORIPAD S - Looks like a Switch controller but uses the Xbox 360 controller protocol, there is also a version of this that looks like a GameCube controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0180 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Faceoff Wired Pro Controller for Nintendo Switch + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0181 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Faceoff Deluxe Wired Pro Controller for Nintendo Switch + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0184 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Faceoff Wired Deluxe+ Audio Controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0185 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Wired Fight Pad Pro for Nintendo Switch + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0186 ), k_eControllerType_SwitchProController, NULL }, // PDP Afterglow Wireless Switch Controller - working gyro. USB is for charging only. Many later "Wireless" line devices w/ gyro also use this vid/pid + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0187 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Rockcandy Wired Controller + { MAKE_CONTROLLER_ID( 0x0e6f, 0x0188 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Afterglow Wired Deluxe+ Audio Controller + { MAKE_CONTROLLER_ID( 0x0f0d, 0x00aa ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORI Real Arcade Pro V Hayabusa in Switch Mode + { MAKE_CONTROLLER_ID( 0x20d6, 0xa711 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Wired Controller Plus/PowerA Wired Controller Nintendo GameCube Style + { MAKE_CONTROLLER_ID( 0x20d6, 0xa712 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Fight Pad + { MAKE_CONTROLLER_ID( 0x20d6, 0xa713 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Super Mario Controller + { MAKE_CONTROLLER_ID( 0x20d6, 0xa714 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Spectra Controller + { MAKE_CONTROLLER_ID( 0x20d6, 0xa715 ), k_eControllerType_SwitchInputOnlyController, NULL }, // Power A Fusion Wireless Arcade Stick (USB Mode) Over BT is shows up as 057e 2009 + { MAKE_CONTROLLER_ID( 0x20d6, 0xa716 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Pro Controller - USB requires toggling switch on back of device + { MAKE_CONTROLLER_ID( 0x20d6, 0xa718 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Nano Wired Controller + { MAKE_CONTROLLER_ID( 0x33dd, 0x0001 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Black + { MAKE_CONTROLLER_ID( 0x33dd, 0x0002 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch ?? + { MAKE_CONTROLLER_ID( 0x33dd, 0x0003 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Red + + // Valve products + { MAKE_CONTROLLER_ID( 0x0000, 0x11fb ), k_eControllerType_MobileTouch, NULL }, // Streaming mobile touch virtual controls + { MAKE_CONTROLLER_ID( 0x28de, 0x1101 ), k_eControllerType_SteamController, NULL }, // Valve Legacy Steam Controller (CHELL) + { MAKE_CONTROLLER_ID( 0x28de, 0x1102 ), k_eControllerType_SteamController, NULL }, // Valve wired Steam Controller (D0G) + { MAKE_CONTROLLER_ID( 0x28de, 0x1105 ), k_eControllerType_SteamController, NULL }, // Valve Bluetooth Steam Controller (D0G) + { MAKE_CONTROLLER_ID( 0x28de, 0x1106 ), k_eControllerType_SteamController, NULL }, // Valve Bluetooth Steam Controller (D0G) + { MAKE_CONTROLLER_ID( 0x28de, 0x11ff ), k_eControllerType_UnknownNonSteamController, NULL }, // Steam Virtual Gamepad + { MAKE_CONTROLLER_ID( 0x28de, 0x1142 ), k_eControllerType_SteamController, NULL }, // Valve wireless Steam Controller + { MAKE_CONTROLLER_ID( 0x28de, 0x1201 ), k_eControllerType_SteamControllerV2, NULL }, // Valve wired Steam Controller (HEADCRAB) + { MAKE_CONTROLLER_ID( 0x28de, 0x1202 ), k_eControllerType_SteamControllerV2, NULL }, // Valve Bluetooth Steam Controller (HEADCRAB) + { MAKE_CONTROLLER_ID( 0x28de, 0x1205 ), k_eControllerType_SteamControllerNeptune, NULL }, // Valve Steam Deck Builtin Controller +}; diff --git a/contrib/SDL-3.2.8/src/joystick/controller_type.c b/contrib/SDL-3.2.8/src/joystick/controller_type.c new file mode 100644 index 0000000..f179d27 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/controller_type.c @@ -0,0 +1,140 @@ +/* + Copyright (C) Valve Corporation + + 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" + + +#include "controller_type.h" +#include "controller_list.h" + + +static const char *GetControllerTypeOverride( int nVID, int nPID ) +{ + const char *hint = SDL_GetHint(SDL_HINT_GAMECONTROLLERTYPE); + if (hint) { + char key[32]; + const char *spot = NULL; + + SDL_snprintf(key, sizeof(key), "0x%.4x/0x%.4x=", nVID, nPID); + spot = SDL_strstr(hint, key); + if (!spot) { + SDL_snprintf(key, sizeof(key), "0x%.4X/0x%.4X=", nVID, nPID); + spot = SDL_strstr(hint, key); + } + if (spot) { + spot += SDL_strlen(key); + if (SDL_strncmp(spot, "k_eControllerType_", 18) == 0) { + spot += 18; + } + return spot; + } + } + return NULL; +} + + +EControllerType GuessControllerType( int nVID, int nPID ) +{ +#if 0//def _DEBUG + // Verify that there are no duplicates in the controller list + // If the list were sorted, we could do this much more efficiently, as well as improve lookup speed. + static bool s_bCheckedForDuplicates; + if ( !s_bCheckedForDuplicates ) + { + s_bCheckedForDuplicates = true; + int i, j; + for ( i = 0; i < sizeof( arrControllers ) / sizeof( arrControllers[ 0 ] ); ++i ) + { + for ( j = i + 1; j < sizeof( arrControllers ) / sizeof( arrControllers[ 0 ] ); ++j ) + { + if ( arrControllers[ i ].m_unDeviceID == arrControllers[ j ].m_unDeviceID ) + { + Log( "Duplicate controller entry found for VID 0x%.4x PID 0x%.4x\n", ( arrControllers[ i ].m_unDeviceID >> 16 ), arrControllers[ i ].m_unDeviceID & 0xFFFF ); + } + } + } + } +#endif // _DEBUG + + unsigned int unDeviceID = MAKE_CONTROLLER_ID( nVID, nPID ); + int iIndex; + + const char *pszOverride = GetControllerTypeOverride( nVID, nPID ); + if ( pszOverride ) + { + if ( SDL_strncasecmp( pszOverride, "Xbox360", 7 ) == 0 ) + { + return k_eControllerType_XBox360Controller; + } + if ( SDL_strncasecmp( pszOverride, "XboxOne", 7 ) == 0 ) + { + return k_eControllerType_XBoxOneController; + } + if ( SDL_strncasecmp( pszOverride, "PS3", 3 ) == 0 ) + { + return k_eControllerType_PS3Controller; + } + if ( SDL_strncasecmp( pszOverride, "PS4", 3 ) == 0 ) + { + return k_eControllerType_PS4Controller; + } + if ( SDL_strncasecmp( pszOverride, "PS5", 3 ) == 0 ) + { + return k_eControllerType_PS5Controller; + } + if ( SDL_strncasecmp( pszOverride, "SwitchPro", 9 ) == 0 ) + { + return k_eControllerType_SwitchProController; + } + if ( SDL_strncasecmp( pszOverride, "Steam", 5 ) == 0 ) + { + return k_eControllerType_SteamController; + } + return k_eControllerType_UnknownNonSteamController; + } + + for ( iIndex = 0; iIndex < sizeof( arrControllers ) / sizeof( arrControllers[0] ); ++iIndex ) + { + if ( unDeviceID == arrControllers[ iIndex ].m_unDeviceID ) + { + return arrControllers[ iIndex ].m_eControllerType; + } + } + + return k_eControllerType_UnknownNonSteamController; + +} + +const char *GuessControllerName( int nVID, int nPID ) +{ + unsigned int unDeviceID = MAKE_CONTROLLER_ID( nVID, nPID ); + int iIndex; + for ( iIndex = 0; iIndex < sizeof( arrControllers ) / sizeof( arrControllers[0] ); ++iIndex ) + { + if ( unDeviceID == arrControllers[ iIndex ].m_unDeviceID ) + { + return arrControllers[ iIndex ].m_pszName; + } + } + + return NULL; + +} + +#undef MAKE_CONTROLLER_ID diff --git a/contrib/SDL-3.2.8/src/joystick/controller_type.h b/contrib/SDL-3.2.8/src/joystick/controller_type.h new file mode 100644 index 0000000..155c8ad --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/controller_type.h @@ -0,0 +1,78 @@ +/* + Copyright (C) Valve Corporation + + 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. +*/ + +#ifndef CONTROLLER_TYPE_H +#define CONTROLLER_TYPE_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Steam Controller models +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN A DATABASE +//----------------------------------------------------------------------------- +typedef enum +{ + k_eControllerType_None = -1, + k_eControllerType_Unknown = 0, + + // Steam Controllers + k_eControllerType_UnknownSteamController = 1, + k_eControllerType_SteamController = 2, + k_eControllerType_SteamControllerV2 = 3, + k_eControllerType_SteamControllerNeptune = 4, + + // Other Controllers + k_eControllerType_UnknownNonSteamController = 30, + k_eControllerType_XBox360Controller = 31, + k_eControllerType_XBoxOneController = 32, + k_eControllerType_PS3Controller = 33, + k_eControllerType_PS4Controller = 34, + k_eControllerType_WiiController = 35, + k_eControllerType_AppleController = 36, + k_eControllerType_AndroidController = 37, + k_eControllerType_SwitchProController = 38, + k_eControllerType_SwitchJoyConLeft = 39, + k_eControllerType_SwitchJoyConRight = 40, + k_eControllerType_SwitchJoyConPair = 41, + k_eControllerType_SwitchInputOnlyController = 42, + k_eControllerType_MobileTouch = 43, + k_eControllerType_XInputSwitchController = 44, // Client-side only, used to mark Nintendo Switch style controllers as using XInput instead of the Nintendo Switch protocol + k_eControllerType_PS5Controller = 45, + k_eControllerType_XInputPS4Controller = 46, // Client-side only, used to mark DualShock 4 style controllers using XInput instead of the DualShock 4 controller protocol + k_eControllerType_LastController, // Don't add game controllers below this enumeration - this enumeration can change value + + // Keyboards and Mice + k_eControllertype_GenericKeyboard = 400, + k_eControllertype_GenericMouse = 800, +} EControllerType; + +typedef struct +{ + unsigned int m_unDeviceID; + EControllerType m_eControllerType; + const char *m_pszName; +} ControllerDescription_t; + + +extern EControllerType GuessControllerType( int nVID, int nPID ); +extern const char *GuessControllerName( int nVID, int nPID ); + +#endif // CONTROLLER_TYPE_H diff --git a/contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick.c b/contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick.c new file mode 100644 index 0000000..9327276 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick.c @@ -0,0 +1,1089 @@ +/* + 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_JOYSTICK_IOKIT + +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" +#include "SDL_iokitjoystick_c.h" +#include "../hidapi/SDL_hidapijoystick_c.h" +#include "../../haptic/darwin/SDL_syshaptic_c.h" // For haptic hot plugging + +#define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick") + +#define CONVERT_MAGNITUDE(x) (((x)*10000) / 0x7FFF) + +// The base object of the HID Manager API +static IOHIDManagerRef hidman = NULL; + +// Linked list of all available devices +static recDevice *gpDeviceList = NULL; + +void FreeRumbleEffectData(FFEFFECT *effect) +{ + if (!effect) { + return; + } + SDL_free(effect->rgdwAxes); + SDL_free(effect->rglDirection); + SDL_free(effect->lpvTypeSpecificParams); + SDL_free(effect); +} + +FFEFFECT *CreateRumbleEffectData(Sint16 magnitude) +{ + FFEFFECT *effect; + FFPERIODIC *periodic; + + // Create the effect + effect = (FFEFFECT *)SDL_calloc(1, sizeof(*effect)); + if (!effect) { + return NULL; + } + effect->dwSize = sizeof(*effect); + effect->dwGain = 10000; + effect->dwFlags = FFEFF_OBJECTOFFSETS; + effect->dwDuration = SDL_MAX_RUMBLE_DURATION_MS * 1000; // In microseconds. + effect->dwTriggerButton = FFEB_NOTRIGGER; + + effect->cAxes = 2; + effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD)); + if (!effect->rgdwAxes) { + FreeRumbleEffectData(effect); + return NULL; + } + + effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG)); + if (!effect->rglDirection) { + FreeRumbleEffectData(effect); + return NULL; + } + effect->dwFlags |= FFEFF_CARTESIAN; + + periodic = (FFPERIODIC *)SDL_calloc(1, sizeof(*periodic)); + if (!periodic) { + FreeRumbleEffectData(effect); + return NULL; + } + periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude); + periodic->dwPeriod = 1000000; + + effect->cbTypeSpecificParams = sizeof(*periodic); + effect->lpvTypeSpecificParams = periodic; + + return effect; +} + +static recDevice *GetDeviceForIndex(int device_index) +{ + recDevice *device = gpDeviceList; + while (device) { + if (!device->removed) { + if (device_index == 0) { + break; + } + + --device_index; + } + device = device->pNext; + } + return device; +} + +static void FreeElementList(recElement *pElement) +{ + while (pElement) { + recElement *pElementNext = pElement->pNext; + SDL_free(pElement); + pElement = pElementNext; + } +} + +static recDevice *FreeDevice(recDevice *removeDevice) +{ + recDevice *pDeviceNext = NULL; + if (removeDevice) { + if (removeDevice->deviceRef) { + if (removeDevice->runLoopAttached) { + /* Calling IOHIDDeviceUnscheduleFromRunLoop without a prior, + * paired call to IOHIDDeviceScheduleWithRunLoop can lead + * to crashes in MacOS 10.14.x and earlier. This doesn't + * appear to be a problem in MacOS 10.15.x, but we'll + * do it anyways. (Part-of fix for Bug 5034) + */ + IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE); + } + CFRelease(removeDevice->deviceRef); + removeDevice->deviceRef = NULL; + } + + /* clear out any reference to removeDevice from an associated, + * live instance of SDL_Joystick (Part-of fix for Bug 5034) + */ + SDL_LockJoysticks(); + if (removeDevice->joystick) { + removeDevice->joystick->hwdata = NULL; + } + SDL_UnlockJoysticks(); + + // save next device prior to disposing of this device + pDeviceNext = removeDevice->pNext; + + if (gpDeviceList == removeDevice) { + gpDeviceList = pDeviceNext; + } else if (gpDeviceList) { + recDevice *device; + + for (device = gpDeviceList; device; device = device->pNext) { + if (device->pNext == removeDevice) { + device->pNext = pDeviceNext; + break; + } + } + } + removeDevice->pNext = NULL; + + // free element lists + FreeElementList(removeDevice->firstAxis); + FreeElementList(removeDevice->firstButton); + FreeElementList(removeDevice->firstHat); + + SDL_free(removeDevice); + } + return pDeviceNext; +} + +static bool GetHIDElementState(recDevice *pDevice, recElement *pElement, SInt32 *pValue) +{ + SInt32 value = 0; + bool result = false; + + if (pDevice && pDevice->deviceRef && pElement) { + IOHIDValueRef valueRef; + if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) { + value = (SInt32)IOHIDValueGetIntegerValue(valueRef); + + // record min and max for auto calibration + if (value < pElement->minReport) { + pElement->minReport = value; + } + if (value > pElement->maxReport) { + pElement->maxReport = value; + } + *pValue = value; + + result = true; + } + } + return result; +} + +static bool GetHIDScaledCalibratedState(recDevice *pDevice, recElement *pElement, SInt32 min, SInt32 max, SInt32 *pValue) +{ + const float deviceScale = max - min; + const float readScale = pElement->maxReport - pElement->minReport; + bool result = false; + if (GetHIDElementState(pDevice, pElement, pValue)) { + if (readScale == 0) { + result = true; // no scaling at all + } else { + *pValue = (Sint32)(((*pValue - pElement->minReport) * deviceScale / readScale) + min); + result = true; + } + } + return result; +} + +static void JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender) +{ + recDevice *device = (recDevice *)ctx; + device->removed = true; + if (device->deviceRef) { + // deviceRef was invalidated due to the remove + CFRelease(device->deviceRef); + device->deviceRef = NULL; + } + if (device->ffeffect_ref) { + FFDeviceReleaseEffect(device->ffdevice, device->ffeffect_ref); + device->ffeffect_ref = NULL; + } + if (device->ffeffect) { + FreeRumbleEffectData(device->ffeffect); + device->ffeffect = NULL; + } + if (device->ffdevice) { + FFReleaseDevice(device->ffdevice); + device->ffdevice = NULL; + device->ff_initialized = false; + } +#ifdef SDL_HAPTIC_IOKIT + MacHaptic_MaybeRemoveDevice(device->ffservice); +#endif + + SDL_PrivateJoystickRemoved(device->instance_id); +} + +static void AddHIDElement(const void *value, void *parameter); + +// Call AddHIDElement() on all elements in an array of IOHIDElementRefs +static void AddHIDElements(CFArrayRef array, recDevice *pDevice) +{ + const CFRange range = { 0, CFArrayGetCount(array) }; + CFArrayApplyFunction(array, range, AddHIDElement, pDevice); +} + +static bool ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) +{ + while (listitem) { + if (listitem->cookie == cookie) { + return true; + } + listitem = listitem->pNext; + } + return false; +} + +// See if we care about this HID element, and if so, note it in our recDevice. +static void AddHIDElement(const void *value, void *parameter) +{ + recDevice *pDevice = (recDevice *)parameter; + IOHIDElementRef refElement = (IOHIDElementRef)value; + const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0; + + if (refElement && (elementTypeID == IOHIDElementGetTypeID())) { + const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement); + const uint32_t usagePage = IOHIDElementGetUsagePage(refElement); + const uint32_t usage = IOHIDElementGetUsage(refElement); + recElement *element = NULL; + recElement **headElement = NULL; + + // look at types of interest + switch (IOHIDElementGetType(refElement)) { + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + { + switch (usagePage) { // only interested in kHIDPage_GenericDesktop and kHIDPage_Button + case kHIDPage_GenericDesktop: + switch (usage) { + case kHIDUsage_GD_X: + case kHIDUsage_GD_Y: + case kHIDUsage_GD_Z: + case kHIDUsage_GD_Rx: + case kHIDUsage_GD_Ry: + case kHIDUsage_GD_Rz: + case kHIDUsage_GD_Slider: + case kHIDUsage_GD_Dial: + case kHIDUsage_GD_Wheel: + if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) { + element = (recElement *)SDL_calloc(1, sizeof(recElement)); + if (element) { + pDevice->axes++; + headElement = &(pDevice->firstAxis); + } + } + break; + + case kHIDUsage_GD_Hatswitch: + if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) { + element = (recElement *)SDL_calloc(1, sizeof(recElement)); + if (element) { + pDevice->hats++; + headElement = &(pDevice->firstHat); + } + } + break; + case kHIDUsage_GD_DPadUp: + case kHIDUsage_GD_DPadDown: + case kHIDUsage_GD_DPadRight: + case kHIDUsage_GD_DPadLeft: + case kHIDUsage_GD_Start: + case kHIDUsage_GD_Select: + case kHIDUsage_GD_SystemMainMenu: + if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) { + element = (recElement *)SDL_calloc(1, sizeof(recElement)); + if (element) { + pDevice->buttons++; + headElement = &(pDevice->firstButton); + } + } + break; + } + break; + + case kHIDPage_Simulation: + switch (usage) { + case kHIDUsage_Sim_Rudder: + case kHIDUsage_Sim_Throttle: + case kHIDUsage_Sim_Accelerator: + case kHIDUsage_Sim_Brake: + if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) { + element = (recElement *)SDL_calloc(1, sizeof(recElement)); + if (element) { + pDevice->axes++; + headElement = &(pDevice->firstAxis); + } + } + break; + + default: + break; + } + break; + + case kHIDPage_Button: + case kHIDPage_Consumer: // e.g. 'pause' button on Steelseries MFi gamepads. + if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) { + element = (recElement *)SDL_calloc(1, sizeof(recElement)); + if (element) { + pDevice->buttons++; + headElement = &(pDevice->firstButton); + } + } + break; + + default: + break; + } + } break; + + case kIOHIDElementTypeCollection: + { + CFArrayRef array = IOHIDElementGetChildren(refElement); + if (array) { + AddHIDElements(array, pDevice); + } + } break; + + default: + break; + } + + if (element && headElement) { // add to list + recElement *elementPrevious = NULL; + recElement *elementCurrent = *headElement; + while (elementCurrent && usage >= elementCurrent->usage) { + elementPrevious = elementCurrent; + elementCurrent = elementCurrent->pNext; + } + if (elementPrevious) { + elementPrevious->pNext = element; + } else { + *headElement = element; + } + + element->elementRef = refElement; + element->usagePage = usagePage; + element->usage = usage; + element->pNext = elementCurrent; + + element->minReport = element->min = (SInt32)IOHIDElementGetLogicalMin(refElement); + element->maxReport = element->max = (SInt32)IOHIDElementGetLogicalMax(refElement); + element->cookie = IOHIDElementGetCookie(refElement); + + pDevice->elements++; + } + } +} + +static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *product_string) +{ + int slot = -1; + + if (vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX360_WIRED_CONTROLLER) { + // Gamepad name is "GamePad-N", where N is slot + 1 + if (SDL_sscanf(product_string, "GamePad-%d", &slot) == 1) { + slot -= 1; + } + } + return slot; +} + +static bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) +{ + Sint32 vendor = 0; + Sint32 product = 0; + Sint32 version = 0; + char *name; + char manufacturer_string[256]; + char product_string[256]; + CFTypeRef refCF = NULL; + CFArrayRef array = NULL; + + // get usage page and usage + refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey)); + if (refCF) { + CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage); + } + if (pDevice->usagePage != kHIDPage_GenericDesktop) { + return false; // Filter device list to non-keyboard/mouse stuff + } + + refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey)); + if (refCF) { + CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage); + } + + if ((pDevice->usage != kHIDUsage_GD_Joystick && + pDevice->usage != kHIDUsage_GD_GamePad && + pDevice->usage != kHIDUsage_GD_MultiAxisController)) { + return false; // Filter device list to non-keyboard/mouse stuff + } + + /* Make sure we retain the use of the IOKit-provided device-object, + lest the device get disconnected and we try to use it. (Fixes + SDL-Bugzilla #4961, aka. https://bugzilla.libsdl.org/show_bug.cgi?id=4961 ) + */ + CFRetain(hidDevice); + + /* Now that we've CFRetain'ed the device-object (for our use), we'll + save the reference to it. + */ + pDevice->deviceRef = hidDevice; + + refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey)); + if (refCF) { + CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor); + } + + refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey)); + if (refCF) { + CFNumberGetValue(refCF, kCFNumberSInt32Type, &product); + } + + refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey)); + if (refCF) { + CFNumberGetValue(refCF, kCFNumberSInt32Type, &version); + } + + if (SDL_IsJoystickXboxOne(vendor, product)) { + // We can't actually use this API for Xbox controllers + return false; + } + + // get device name + refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey)); + if ((!refCF) || (!CFStringGetCString(refCF, manufacturer_string, sizeof(manufacturer_string), kCFStringEncodingUTF8))) { + manufacturer_string[0] = '\0'; + } + refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey)); + if ((!refCF) || (!CFStringGetCString(refCF, product_string, sizeof(product_string), kCFStringEncodingUTF8))) { + product_string[0] = '\0'; + } + name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string); + if (name) { + SDL_strlcpy(pDevice->product, name, sizeof(pDevice->product)); + SDL_free(name); + } + + if (SDL_ShouldIgnoreJoystick(vendor, product, version, pDevice->product)) { + return false; + } + + if (SDL_JoystickHandledByAnotherDriver(&SDL_DARWIN_JoystickDriver, vendor, product, version, pDevice->product)) { + return false; + } + + pDevice->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, (Uint16)vendor, (Uint16)product, (Uint16)version, manufacturer_string, product_string, 0, 0); + pDevice->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot((Uint16)vendor, (Uint16)product, product_string); + + array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone); + if (array) { + AddHIDElements(array, pDevice); + CFRelease(array); + } + + return true; +} + +static bool JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject) +{ + recDevice *i; + +#ifdef SDL_JOYSTICK_MFI + extern bool IOS_SupportedHIDDevice(IOHIDDeviceRef device); + if (IOS_SupportedHIDDevice(ioHIDDeviceObject)) { + return true; + } +#endif + + for (i = gpDeviceList; i; i = i->pNext) { + if (i->deviceRef == ioHIDDeviceObject) { + return true; + } + } + return false; +} + +static void JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) +{ + recDevice *device; + io_service_t ioservice; + + if (res != kIOReturnSuccess) { + return; + } + + if (JoystickAlreadyKnown(ioHIDDeviceObject)) { + return; // IOKit sent us a duplicate. + } + + device = (recDevice *)SDL_calloc(1, sizeof(recDevice)); + if (!device) { + return; + } + + if (!GetDeviceInfo(ioHIDDeviceObject, device)) { + FreeDevice(device); + return; // not a device we care about, probably. + } + + // Get notified when this device is disconnected. + IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device); + IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE); + device->runLoopAttached = true; + + // Allocate an instance ID for this device + device->instance_id = SDL_GetNextObjectID(); + + // We have to do some storage of the io_service_t for SDL_OpenHapticFromJoystick + ioservice = IOHIDDeviceGetService(ioHIDDeviceObject); + if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) { + device->ffservice = ioservice; +#ifdef SDL_HAPTIC_IOKIT + MacHaptic_MaybeAddDevice(ioservice); +#endif + } + + // Add device to the end of the list + if (!gpDeviceList) { + gpDeviceList = device; + } else { + recDevice *curdevice; + + curdevice = gpDeviceList; + while (curdevice->pNext) { + curdevice = curdevice->pNext; + } + curdevice->pNext = device; + } + + SDL_PrivateJoystickAdded(device->instance_id); +} + +static bool ConfigHIDManager(CFArrayRef matchingArray) +{ + CFRunLoopRef runloop = CFRunLoopGetCurrent(); + + if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) { + return false; + } + + IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray); + IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL); + IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE); + + while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { + // no-op. Callback fires once per existing device. + } + + // future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. + + return true; // good to go. +} + +static CFDictionaryRef CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay) +{ + CFDictionaryRef result = NULL; + CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); + CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); + const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) }; + const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef }; + + if (pageNumRef && usageNumRef) { + result = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + } + + if (pageNumRef) { + CFRelease(pageNumRef); + } + if (usageNumRef) { + CFRelease(usageNumRef); + } + + if (!result) { + *okay = 0; + } + + return result; +} + +static bool CreateHIDManager(void) +{ + bool result = false; + int okay = 1; + const void *vals[] = { + (void *)CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay), + (void *)CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay), + (void *)CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay), + }; + const size_t numElements = SDL_arraysize(vals); + CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL; + size_t i; + + for (i = 0; i < numElements; i++) { + if (vals[i]) { + CFRelease((CFTypeRef)vals[i]); + } + } + + if (array) { + hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (hidman != NULL) { + result = ConfigHIDManager(array); + } + CFRelease(array); + } + + return result; +} + +static bool DARWIN_JoystickInit(void) +{ + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_IOKIT, true)) { + return true; + } + + if (!CreateHIDManager()) { + return SDL_SetError("Joystick: Couldn't initialize HID Manager"); + } + + return true; +} + +static int DARWIN_JoystickGetCount(void) +{ + recDevice *device = gpDeviceList; + int nJoySticks = 0; + + while (device) { + if (!device->removed) { + nJoySticks++; + } + device = device->pNext; + } + + return nJoySticks; +} + +static void DARWIN_JoystickDetect(void) +{ + recDevice *device = gpDeviceList; + while (device) { + if (device->removed) { + device = FreeDevice(device); + } else { + device = device->pNext; + } + } + + if (hidman) { + /* run this after the checks above so we don't set device->removed and delete the device before + DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */ + while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { + // no-op. Pending callbacks will fire in CFRunLoopRunInMode(). + } + } +} + +static bool DARWIN_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers + return false; +} + +static const char *DARWIN_JoystickGetDeviceName(int device_index) +{ + recDevice *device = GetDeviceForIndex(device_index); + return device ? device->product : "UNKNOWN"; +} + +static const char *DARWIN_JoystickGetDevicePath(int device_index) +{ + return NULL; +} + +static int DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + recDevice *device = GetDeviceForIndex(device_index); + return device ? device->steam_virtual_gamepad_slot : -1; +} + +static int DARWIN_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void DARWIN_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static SDL_GUID DARWIN_JoystickGetDeviceGUID(int device_index) +{ + recDevice *device = GetDeviceForIndex(device_index); + SDL_GUID guid; + if (device) { + guid = device->guid; + } else { + SDL_zero(guid); + } + return guid; +} + +static SDL_JoystickID DARWIN_JoystickGetDeviceInstanceID(int device_index) +{ + recDevice *device = GetDeviceForIndex(device_index); + return device ? device->instance_id : 0; +} + +static bool DARWIN_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + recDevice *device = GetDeviceForIndex(device_index); + + joystick->hwdata = device; + device->joystick = joystick; + joystick->name = device->product; + + joystick->naxes = device->axes; + joystick->nhats = device->hats; + joystick->nbuttons = device->buttons; + + if (device->ffservice) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } + + return true; +} + +/* + * Like strerror but for force feedback errors. + */ +static const char *FFStrError(unsigned int err) +{ + switch (err) { + case FFERR_DEVICEFULL: + return "device full"; + // This should be valid, but for some reason isn't defined... + /* case FFERR_DEVICENOTREG: + return "device not registered"; */ + case FFERR_DEVICEPAUSED: + return "device paused"; + case FFERR_DEVICERELEASED: + return "device released"; + case FFERR_EFFECTPLAYING: + return "effect playing"; + case FFERR_EFFECTTYPEMISMATCH: + return "effect type mismatch"; + case FFERR_EFFECTTYPENOTSUPPORTED: + return "effect type not supported"; + case FFERR_GENERIC: + return "undetermined error"; + case FFERR_HASEFFECTS: + return "device has effects"; + case FFERR_INCOMPLETEEFFECT: + return "incomplete effect"; + case FFERR_INTERNAL: + return "internal fault"; + case FFERR_INVALIDDOWNLOADID: + return "invalid download id"; + case FFERR_INVALIDPARAM: + return "invalid parameter"; + case FFERR_MOREDATA: + return "more data"; + case FFERR_NOINTERFACE: + return "interface not supported"; + case FFERR_NOTDOWNLOADED: + return "effect is not downloaded"; + case FFERR_NOTINITIALIZED: + return "object has not been initialized"; + case FFERR_OUTOFMEMORY: + return "out of memory"; + case FFERR_UNPLUGGED: + return "device is unplugged"; + case FFERR_UNSUPPORTED: + return "function call unsupported"; + case FFERR_UNSUPPORTEDAXIS: + return "axis unsupported"; + + default: + return "unknown error"; + } +} + +static bool DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude) +{ + HRESULT result; + + if (!device->ffdevice) { + result = FFCreateDevice(device->ffservice, &device->ffdevice); + if (result != FF_OK) { + return SDL_SetError("Unable to create force feedback device from service: %s", FFStrError(result)); + } + } + + // Reset and then enable actuators + result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_RESET); + if (result != FF_OK) { + return SDL_SetError("Unable to reset force feedback device: %s", FFStrError(result)); + } + + result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_SETACTUATORSON); + if (result != FF_OK) { + return SDL_SetError("Unable to enable force feedback actuators: %s", FFStrError(result)); + } + + // Create the effect + device->ffeffect = CreateRumbleEffectData(magnitude); + if (!device->ffeffect) { + return false; + } + + result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID, + device->ffeffect, &device->ffeffect_ref); + if (result != FF_OK) { + return SDL_SetError("Haptic: Unable to create effect: %s", FFStrError(result)); + } + return true; +} + +static bool DARWIN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + HRESULT result; + recDevice *device = joystick->hwdata; + + // Scale and average the two rumble strengths + Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2); + + if (!device) { + return SDL_SetError("Rumble failed, device disconnected"); + } + + if (!device->ffservice) { + return SDL_Unsupported(); + } + + if (device->ff_initialized) { + FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams); + periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude); + + result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect, + (FFEP_DURATION | FFEP_TYPESPECIFICPARAMS)); + if (result != FF_OK) { + return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result)); + } + } else { + if (!DARWIN_JoystickInitRumble(device, magnitude)) { + return false; + } + device->ff_initialized = true; + } + + result = FFEffectStart(device->ffeffect_ref, 1, 0); + if (result != FF_OK) { + return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result)); + } + return true; +} + +static bool DARWIN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static bool DARWIN_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool DARWIN_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool DARWIN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void DARWIN_JoystickUpdate(SDL_Joystick *joystick) +{ + recDevice *device = joystick->hwdata; + recElement *element; + SInt32 value, range; + int i, goodRead = false; + Uint64 timestamp = SDL_GetTicksNS(); + + if (!device) { + return; + } + + if (device->removed) { // device was unplugged; ignore it. + if (joystick->hwdata) { + joystick->hwdata = NULL; + } + return; + } + + element = device->firstAxis; + i = 0; + + while (element) { + goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value); + if (goodRead) { + SDL_SendJoystickAxis(timestamp, joystick, i, value); + } + + element = element->pNext; + ++i; + } + + element = device->firstButton; + i = 0; + while (element) { + goodRead = GetHIDElementState(device, element, &value); + if (goodRead) { + SDL_SendJoystickButton(timestamp, joystick, i, (value != 0)); + } + + element = element->pNext; + ++i; + } + + element = device->firstHat; + i = 0; + + while (element) { + Uint8 pos = 0; + + range = (element->max - element->min + 1); + goodRead = GetHIDElementState(device, element, &value); + if (goodRead) { + value -= element->min; + if (range == 4) { // 4 position hatswitch - scale up value + value *= 2; + } else if (range != 8) { // Neither a 4 nor 8 positions - fall back to default position (centered) + value = -1; + } + switch (value) { + case 0: + pos = SDL_HAT_UP; + break; + case 1: + pos = SDL_HAT_RIGHTUP; + break; + case 2: + pos = SDL_HAT_RIGHT; + break; + case 3: + pos = SDL_HAT_RIGHTDOWN; + break; + case 4: + pos = SDL_HAT_DOWN; + break; + case 5: + pos = SDL_HAT_LEFTDOWN; + break; + case 6: + pos = SDL_HAT_LEFT; + break; + case 7: + pos = SDL_HAT_LEFTUP; + break; + default: + /* Every other value is mapped to center. We do that because some + * joysticks use 8 and some 15 for this value, and apparently + * there are even more variants out there - so we try to be generous. + */ + pos = SDL_HAT_CENTERED; + break; + } + + SDL_SendJoystickHat(timestamp, joystick, i, pos); + } + + element = element->pNext; + ++i; + } +} + +static void DARWIN_JoystickClose(SDL_Joystick *joystick) +{ + recDevice *device = joystick->hwdata; + if (device) { + device->joystick = NULL; + } +} + +static void DARWIN_JoystickQuit(void) +{ + while (FreeDevice(gpDeviceList)) { + // spin + } + + if (hidman) { + IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE); + IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone); + CFRelease(hidman); + hidman = NULL; + } +} + +static bool DARWIN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +SDL_JoystickDriver SDL_DARWIN_JoystickDriver = { + DARWIN_JoystickInit, + DARWIN_JoystickGetCount, + DARWIN_JoystickDetect, + DARWIN_JoystickIsDevicePresent, + DARWIN_JoystickGetDeviceName, + DARWIN_JoystickGetDevicePath, + DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot, + DARWIN_JoystickGetDevicePlayerIndex, + DARWIN_JoystickSetDevicePlayerIndex, + DARWIN_JoystickGetDeviceGUID, + DARWIN_JoystickGetDeviceInstanceID, + DARWIN_JoystickOpen, + DARWIN_JoystickRumble, + DARWIN_JoystickRumbleTriggers, + DARWIN_JoystickSetLED, + DARWIN_JoystickSendEffect, + DARWIN_JoystickSetSensorsEnabled, + DARWIN_JoystickUpdate, + DARWIN_JoystickClose, + DARWIN_JoystickQuit, + DARWIN_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_IOKIT diff --git a/contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick_c.h new file mode 100644 index 0000000..91deb24 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick_c.h @@ -0,0 +1,80 @@ +/* + 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" + +#ifndef SDL_JOYSTICK_IOKIT_H +#define SDL_JOYSTICK_IOKIT_H + +#include +#include +#include + +struct recElement +{ + IOHIDElementRef elementRef; + IOHIDElementCookie cookie; + uint32_t usagePage, usage; // HID usage + SInt32 min; // reported min value possible + SInt32 max; // reported max value possible + + // runtime variables used for auto-calibration + SInt32 minReport; // min returned value + SInt32 maxReport; // max returned value + + struct recElement *pNext; // next element in list +}; +typedef struct recElement recElement; + +struct joystick_hwdata +{ + IOHIDDeviceRef deviceRef; // HIDManager device handle + io_service_t ffservice; // Interface for force feedback, 0 = no ff + FFDeviceObjectReference ffdevice; + FFEFFECT *ffeffect; + FFEffectObjectReference ffeffect_ref; + bool ff_initialized; + + char product[256]; // name of product + uint32_t usage; // usage page from IOUSBHID Parser.h which defines general usage + uint32_t usagePage; // usage within above page from IOUSBHID Parser.h which defines specific usage + + int axes; // number of axis (calculated, not reported by device) + int buttons; // number of buttons (calculated, not reported by device) + int hats; // number of hat switches (calculated, not reported by device) + int elements; // number of total elements (should be total of above) (calculated, not reported by device) + + recElement *firstAxis; + recElement *firstButton; + recElement *firstHat; + + bool removed; + SDL_Joystick *joystick; + bool runLoopAttached; // is 'deviceRef' attached to a CFRunLoop? + + int instance_id; + SDL_GUID guid; + int steam_virtual_gamepad_slot; + + struct joystick_hwdata *pNext; // next device +}; +typedef struct joystick_hwdata recDevice; + +#endif // SDL_JOYSTICK_IOKIT_H diff --git a/contrib/SDL-3.2.8/src/joystick/dummy/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/dummy/SDL_sysjoystick.c new file mode 100644 index 0000000..8240d45 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/dummy/SDL_sysjoystick.c @@ -0,0 +1,156 @@ +/* + 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" + +#if defined(SDL_JOYSTICK_DUMMY) || defined(SDL_JOYSTICK_DISABLED) + +// This is the dummy implementation of the SDL joystick API + +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" + +static bool DUMMY_JoystickInit(void) +{ + return true; +} + +static int DUMMY_JoystickGetCount(void) +{ + return 0; +} + +static void DUMMY_JoystickDetect(void) +{ +} + +static bool DUMMY_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + return false; +} + +static const char *DUMMY_JoystickGetDeviceName(int device_index) +{ + return NULL; +} + +static const char *DUMMY_JoystickGetDevicePath(int device_index) +{ + return NULL; +} + +static int DUMMY_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + +static int DUMMY_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void DUMMY_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static SDL_GUID DUMMY_JoystickGetDeviceGUID(int device_index) +{ + SDL_GUID guid; + SDL_zero(guid); + return guid; +} + +static SDL_JoystickID DUMMY_JoystickGetDeviceInstanceID(int device_index) +{ + return 0; +} + +static bool DUMMY_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + return SDL_SetError("Logic error: No joysticks available"); +} + +static bool DUMMY_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +static bool DUMMY_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static bool DUMMY_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool DUMMY_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool DUMMY_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void DUMMY_JoystickUpdate(SDL_Joystick *joystick) +{ +} + +static void DUMMY_JoystickClose(SDL_Joystick *joystick) +{ +} + +static void DUMMY_JoystickQuit(void) +{ +} + +static bool DUMMY_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +SDL_JoystickDriver SDL_DUMMY_JoystickDriver = { + DUMMY_JoystickInit, + DUMMY_JoystickGetCount, + DUMMY_JoystickDetect, + DUMMY_JoystickIsDevicePresent, + DUMMY_JoystickGetDeviceName, + DUMMY_JoystickGetDevicePath, + DUMMY_JoystickGetDeviceSteamVirtualGamepadSlot, + DUMMY_JoystickGetDevicePlayerIndex, + DUMMY_JoystickSetDevicePlayerIndex, + DUMMY_JoystickGetDeviceGUID, + DUMMY_JoystickGetDeviceInstanceID, + DUMMY_JoystickOpen, + DUMMY_JoystickRumble, + DUMMY_JoystickRumbleTriggers, + DUMMY_JoystickSetLED, + DUMMY_JoystickSendEffect, + DUMMY_JoystickSetSensorsEnabled, + DUMMY_JoystickUpdate, + DUMMY_JoystickClose, + DUMMY_JoystickQuit, + DUMMY_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_DUMMY || SDL_JOYSTICK_DISABLED diff --git a/contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick.c new file mode 100644 index 0000000..b481d5d --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick.c @@ -0,0 +1,445 @@ +/* + 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_JOYSTICK_EMSCRIPTEN + +#include // For the definition of NULL + +#include "SDL_sysjoystick_c.h" +#include "../SDL_joystick_c.h" + +static SDL_joylist_item *JoystickByIndex(int index); + +static SDL_joylist_item *SDL_joylist = NULL; +static SDL_joylist_item *SDL_joylist_tail = NULL; +static int numjoysticks = 0; + +static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) +{ + SDL_joylist_item *item; + int i; + + SDL_LockJoysticks(); + + if (JoystickByIndex(gamepadEvent->index) != NULL) { + goto done; + } + + item = (SDL_joylist_item *)SDL_malloc(sizeof(SDL_joylist_item)); + if (!item) { + goto done; + } + + SDL_zerop(item); + item->index = gamepadEvent->index; + + item->name = SDL_CreateJoystickName(0, 0, NULL, gamepadEvent->id); + if (!item->name) { + SDL_free(item); + goto done; + } + + item->mapping = SDL_strdup(gamepadEvent->mapping); + if (!item->mapping) { + SDL_free(item->name); + SDL_free(item); + goto done; + } + + item->naxes = gamepadEvent->numAxes; + item->nbuttons = gamepadEvent->numButtons; + item->device_instance = SDL_GetNextObjectID(); + + item->timestamp = gamepadEvent->timestamp; + + for (i = 0; i < item->naxes; i++) { + item->axis[i] = gamepadEvent->axis[i]; + } + + for (i = 0; i < item->nbuttons; i++) { + item->analogButton[i] = gamepadEvent->analogButton[i]; + item->digitalButton[i] = gamepadEvent->digitalButton[i]; + } + + if (!SDL_joylist_tail) { + SDL_joylist = SDL_joylist_tail = item; + } else { + SDL_joylist_tail->next = item; + SDL_joylist_tail = item; + } + + ++numjoysticks; + + SDL_PrivateJoystickAdded(item->device_instance); + +#ifdef DEBUG_JOYSTICK + SDL_Log("Number of joysticks is %d", numjoysticks); +#endif +#ifdef DEBUG_JOYSTICK + SDL_Log("Added joystick with index %d", item->index); +#endif + +done: + SDL_UnlockJoysticks(); + + return 1; +} + +static EM_BOOL Emscripten_JoyStickDisconnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) +{ + SDL_joylist_item *item = SDL_joylist; + SDL_joylist_item *prev = NULL; + + SDL_LockJoysticks(); + + while (item) { + if (item->index == gamepadEvent->index) { + break; + } + prev = item; + item = item->next; + } + + if (!item) { + goto done; + } + + if (item->joystick) { + item->joystick->hwdata = NULL; + } + + if (prev) { + prev->next = item->next; + } else { + SDL_assert(SDL_joylist == item); + SDL_joylist = item->next; + } + if (item == SDL_joylist_tail) { + SDL_joylist_tail = prev; + } + + // Need to decrement the joystick count before we post the event + --numjoysticks; + + SDL_PrivateJoystickRemoved(item->device_instance); + +#ifdef DEBUG_JOYSTICK + SDL_Log("Removed joystick with id %d", item->device_instance); +#endif + SDL_free(item->name); + SDL_free(item->mapping); + SDL_free(item); + +done: + SDL_UnlockJoysticks(); + + return 1; +} + +// Function to perform any system-specific joystick related cleanup +static void EMSCRIPTEN_JoystickQuit(void) +{ + SDL_joylist_item *item = NULL; + SDL_joylist_item *next = NULL; + + for (item = SDL_joylist; item; item = next) { + next = item->next; + SDL_free(item->mapping); + SDL_free(item->name); + SDL_free(item); + } + + SDL_joylist = SDL_joylist_tail = NULL; + + numjoysticks = 0; + + emscripten_set_gamepadconnected_callback(NULL, 0, NULL); + emscripten_set_gamepaddisconnected_callback(NULL, 0, NULL); +} + +// Function to scan the system for joysticks. +static bool EMSCRIPTEN_JoystickInit(void) +{ + int rc, i, numjs; + EmscriptenGamepadEvent gamepadState; + + numjoysticks = 0; + + rc = emscripten_sample_gamepad_data(); + + // Check if gamepad is supported by browser + if (rc == EMSCRIPTEN_RESULT_NOT_SUPPORTED) { + return SDL_SetError("Gamepads not supported"); + } + + numjs = emscripten_get_num_gamepads(); + + // handle already connected gamepads + if (numjs > 0) { + for (i = 0; i < numjs; i++) { + rc = emscripten_get_gamepad_status(i, &gamepadState); + if (rc == EMSCRIPTEN_RESULT_SUCCESS) { + Emscripten_JoyStickConnected(EMSCRIPTEN_EVENT_GAMEPADCONNECTED, + &gamepadState, + NULL); + } + } + } + + rc = emscripten_set_gamepadconnected_callback(NULL, + 0, + Emscripten_JoyStickConnected); + + if (rc != EMSCRIPTEN_RESULT_SUCCESS) { + EMSCRIPTEN_JoystickQuit(); + return SDL_SetError("Could not set gamepad connect callback"); + } + + rc = emscripten_set_gamepaddisconnected_callback(NULL, + 0, + Emscripten_JoyStickDisconnected); + if (rc != EMSCRIPTEN_RESULT_SUCCESS) { + EMSCRIPTEN_JoystickQuit(); + return SDL_SetError("Could not set gamepad disconnect callback"); + } + + return true; +} + +// Returns item matching given SDL device index. +static SDL_joylist_item *JoystickByDeviceIndex(int device_index) +{ + SDL_joylist_item *item = SDL_joylist; + + while (0 < device_index) { + --device_index; + item = item->next; + } + + return item; +} + +// Returns item matching given HTML gamepad index. +static SDL_joylist_item *JoystickByIndex(int index) +{ + SDL_joylist_item *item = SDL_joylist; + + if (index < 0) { + return NULL; + } + + while (item) { + if (item->index == index) { + break; + } + item = item->next; + } + + return item; +} + +static int EMSCRIPTEN_JoystickGetCount(void) +{ + return numjoysticks; +} + +static void EMSCRIPTEN_JoystickDetect(void) +{ +} + +static bool EMSCRIPTEN_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers + return false; +} + +static const char *EMSCRIPTEN_JoystickGetDeviceName(int device_index) +{ + return JoystickByDeviceIndex(device_index)->name; +} + +static const char *EMSCRIPTEN_JoystickGetDevicePath(int device_index) +{ + return NULL; +} + +static int EMSCRIPTEN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + +static int EMSCRIPTEN_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void EMSCRIPTEN_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static SDL_JoystickID EMSCRIPTEN_JoystickGetDeviceInstanceID(int device_index) +{ + return JoystickByDeviceIndex(device_index)->device_instance; +} + +static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + SDL_joylist_item *item = JoystickByDeviceIndex(device_index); + + if (!item) { + return SDL_SetError("No such device"); + } + + if (item->joystick) { + return SDL_SetError("Joystick already opened"); + } + + joystick->hwdata = (struct joystick_hwdata *)item; + item->joystick = joystick; + + // HTML5 Gamepad API doesn't say anything about these + joystick->nhats = 0; + + joystick->nbuttons = item->nbuttons; + joystick->naxes = item->naxes; + + return true; +} + +/* Function to update the state of a joystick - called as a device poll. + * This function shouldn't update the joystick structure directly, + * but instead should call SDL_PrivateJoystick*() to deliver events + * and update joystick device state. + */ +static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick) +{ + EmscriptenGamepadEvent gamepadState; + SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; + int i, result; + Uint64 timestamp = SDL_GetTicksNS(); + + emscripten_sample_gamepad_data(); + + if (item) { + result = emscripten_get_gamepad_status(item->index, &gamepadState); + if (result == EMSCRIPTEN_RESULT_SUCCESS) { + if (gamepadState.timestamp == 0 || gamepadState.timestamp != item->timestamp) { + for (i = 0; i < item->nbuttons; i++) { + if (item->digitalButton[i] != gamepadState.digitalButton[i]) { + bool down = (gamepadState.digitalButton[i] != 0); + SDL_SendJoystickButton(timestamp, item->joystick, i, down); + } + + // store values to compare them in the next update + item->analogButton[i] = gamepadState.analogButton[i]; + item->digitalButton[i] = gamepadState.digitalButton[i]; + } + + for (i = 0; i < item->naxes; i++) { + if (item->axis[i] != gamepadState.axis[i]) { + // do we need to do conversion? + SDL_SendJoystickAxis(timestamp, item->joystick, i, + (Sint16)(32767. * gamepadState.axis[i])); + } + + // store to compare in next update + item->axis[i] = gamepadState.axis[i]; + } + + item->timestamp = gamepadState.timestamp; + } + } + } +} + +// Function to close a joystick after use +static void EMSCRIPTEN_JoystickClose(SDL_Joystick *joystick) +{ + SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; + if (item) { + item->joystick = NULL; + } +} + +static SDL_GUID EMSCRIPTEN_JoystickGetDeviceGUID(int device_index) +{ + // the GUID is just the name for now + const char *name = EMSCRIPTEN_JoystickGetDeviceName(device_index); + return SDL_CreateJoystickGUIDForName(name); +} + +static bool EMSCRIPTEN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +static bool EMSCRIPTEN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static bool EMSCRIPTEN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +static bool EMSCRIPTEN_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool EMSCRIPTEN_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool EMSCRIPTEN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver = { + EMSCRIPTEN_JoystickInit, + EMSCRIPTEN_JoystickGetCount, + EMSCRIPTEN_JoystickDetect, + EMSCRIPTEN_JoystickIsDevicePresent, + EMSCRIPTEN_JoystickGetDeviceName, + EMSCRIPTEN_JoystickGetDevicePath, + EMSCRIPTEN_JoystickGetDeviceSteamVirtualGamepadSlot, + EMSCRIPTEN_JoystickGetDevicePlayerIndex, + EMSCRIPTEN_JoystickSetDevicePlayerIndex, + EMSCRIPTEN_JoystickGetDeviceGUID, + EMSCRIPTEN_JoystickGetDeviceInstanceID, + EMSCRIPTEN_JoystickOpen, + EMSCRIPTEN_JoystickRumble, + EMSCRIPTEN_JoystickRumbleTriggers, + EMSCRIPTEN_JoystickSetLED, + EMSCRIPTEN_JoystickSendEffect, + EMSCRIPTEN_JoystickSetSensorsEnabled, + EMSCRIPTEN_JoystickUpdate, + EMSCRIPTEN_JoystickClose, + EMSCRIPTEN_JoystickQuit, + EMSCRIPTEN_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_EMSCRIPTEN diff --git a/contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick_c.h new file mode 100644 index 0000000..e03a27c --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick_c.h @@ -0,0 +1,49 @@ +/* + 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_JOYSTICK_EMSCRIPTEN +#include "../SDL_sysjoystick.h" + +#include + +// A linked list of available joysticks +typedef struct SDL_joylist_item +{ + int index; + char *name; + char *mapping; + SDL_JoystickID device_instance; + SDL_Joystick *joystick; + int nbuttons; + int naxes; + double timestamp; + double axis[64]; + double analogButton[64]; + EM_BOOL digitalButton[64]; + + struct SDL_joylist_item *next; +} SDL_joylist_item; + +typedef SDL_joylist_item joystick_hwdata; + +#endif // SDL_JOYSTICK_EMSCRIPTEN diff --git a/contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c new file mode 100644 index 0000000..6cf0a90 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c @@ -0,0 +1,828 @@ +/* + 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_JOYSTICK_GAMEINPUT + +#include "../SDL_sysjoystick.h" +#include "../usb_ids.h" +#include "../../core/windows/SDL_gameinput.h" + +// Default value for SDL_HINT_JOYSTICK_GAMEINPUT +#if defined(SDL_PLATFORM_GDK) +#define SDL_GAMEINPUT_DEFAULT true +#else +#define SDL_GAMEINPUT_DEFAULT false +#endif + +enum +{ + SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE = 11 +}; + +typedef struct GAMEINPUT_InternalDevice +{ + IGameInputDevice *device; + char path[(APP_LOCAL_DEVICE_ID_SIZE * 2) + 1]; + char *name; + SDL_GUID guid; // generated by SDL + SDL_JoystickID device_instance; // generated by SDL + const GameInputDeviceInfo *info; + bool isAdded; + bool isDeleteRequested; +} GAMEINPUT_InternalDevice; + +typedef struct GAMEINPUT_InternalList +{ + GAMEINPUT_InternalDevice **devices; + int count; +} GAMEINPUT_InternalList; + +typedef struct joystick_hwdata +{ + GAMEINPUT_InternalDevice *devref; + bool report_sensors; + GameInputRumbleParams rumbleParams; + GameInputCallbackToken system_button_callback_token; +} GAMEINPUT_InternalJoystickHwdata; + +static GAMEINPUT_InternalList g_GameInputList = { NULL }; +static IGameInput *g_pGameInput = NULL; +static GameInputCallbackToken g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE; +static Uint64 g_GameInputTimestampOffset; + +static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info) +{ + if (info->supportedInput & GameInputKindGamepad) { + return true; + } + return false; +} + +static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) +{ + GAMEINPUT_InternalDevice **devicelist = NULL; + GAMEINPUT_InternalDevice *elem = NULL; + const GameInputDeviceInfo *info = NULL; + Uint16 bus = SDL_HARDWARE_BUS_USB; + Uint16 vendor = 0; + Uint16 product = 0; + Uint16 version = 0; + const char *manufacturer_string = NULL; + const char *product_string = NULL; + char tmp[4]; + int idx = 0; + + SDL_AssertJoysticksLocked(); + + info = IGameInputDevice_GetDeviceInfo(pDevice); + if (info->capabilities & GameInputDeviceCapabilityWireless) { + bus = SDL_HARDWARE_BUS_BLUETOOTH; + } else { + bus = SDL_HARDWARE_BUS_USB; + } + vendor = info->vendorId; + product = info->productId; + version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor; + + if (SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, "")) { + return true; + } + + for (idx = 0; idx < g_GameInputList.count; ++idx) { + elem = g_GameInputList.devices[idx]; + if (elem && elem->device == pDevice) { + // we're already added + elem->isDeleteRequested = false; + return true; + } + } + + elem = (GAMEINPUT_InternalDevice *)SDL_calloc(1, sizeof(*elem)); + if (!elem) { + return false; + } + + devicelist = (GAMEINPUT_InternalDevice **)SDL_realloc(g_GameInputList.devices, sizeof(elem) * (g_GameInputList.count + 1LL)); + if (!devicelist) { + SDL_free(elem); + return false; + } + + // Generate a device path + for (idx = 0; idx < APP_LOCAL_DEVICE_ID_SIZE; ++idx) { + SDL_snprintf(tmp, SDL_arraysize(tmp), "%02hhX", info->deviceId.value[idx]); + SDL_strlcat(elem->path, tmp, SDL_arraysize(tmp)); + } + + if (info->deviceStrings) { + // In theory we could get the manufacturer and product strings here, but they're NULL for all the controllers I've tested + } + + if (info->displayName) { + // This could give us a product string, but it's NULL for all the controllers I've tested + } + + IGameInputDevice_AddRef(pDevice); + elem->device = pDevice; + elem->name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string); + elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, manufacturer_string, product_string, 'g', 0); + elem->device_instance = SDL_GetNextObjectID(); + elem->info = info; + + g_GameInputList.devices = devicelist; + g_GameInputList.devices[g_GameInputList.count++] = elem; + + return true; +} + +static bool GAMEINPUT_InternalRemoveByIndex(int idx) +{ + GAMEINPUT_InternalDevice **devicelist = NULL; + GAMEINPUT_InternalDevice *elem; + int bytes = 0; + + SDL_AssertJoysticksLocked(); + + if (idx < 0 || idx >= g_GameInputList.count) { + return SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx); + } + + elem = g_GameInputList.devices[idx]; + if (elem) { + IGameInputDevice_Release(elem->device); + SDL_free(elem->name); + SDL_free(elem); + } + g_GameInputList.devices[idx] = NULL; + + if (g_GameInputList.count == 1) { + // last element in the list, free the entire list then + SDL_free(g_GameInputList.devices); + g_GameInputList.devices = NULL; + } else { + if (idx != g_GameInputList.count - 1) { + bytes = sizeof(*devicelist) * (g_GameInputList.count - idx); + SDL_memmove(&g_GameInputList.devices[idx], &g_GameInputList.devices[idx + 1], bytes); + } + } + + // decrement the count and return + --g_GameInputList.count; + return true; +} + +static GAMEINPUT_InternalDevice *GAMEINPUT_InternalFindByIndex(int idx) +{ + // We're guaranteed that the index is in range when this is called + SDL_AssertJoysticksLocked(); + return g_GameInputList.devices[idx]; +} + +static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback( + _In_ GameInputCallbackToken callbackToken, + _In_ void* context, + _In_ IGameInputDevice* device, + _In_ uint64_t timestamp, + _In_ GameInputDeviceStatus currentStatus, + _In_ GameInputDeviceStatus previousStatus) +{ + int idx = 0; + GAMEINPUT_InternalDevice *elem = NULL; + + if (!device) { + // This should never happen, but ignore it if it does + return; + } + + SDL_LockJoysticks(); + + if (currentStatus & GameInputDeviceConnected) { + GAMEINPUT_InternalAddOrFind(device); + } else { + for (idx = 0; idx < g_GameInputList.count; ++idx) { + elem = g_GameInputList.devices[idx]; + if (elem && elem->device == device) { + // will be deleted on the next Detect call + elem->isDeleteRequested = true; + break; + } + } + } + + SDL_UnlockJoysticks(); +} + +static void GAMEINPUT_JoystickDetect(void); + +static bool GAMEINPUT_JoystickInit(void) +{ + HRESULT hR; + + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT)) { + return true; + } + + if (!SDL_InitGameInput(&g_pGameInput)) { + return false; + } + + hR = IGameInput_RegisterDeviceCallback(g_pGameInput, + NULL, + GameInputKindController, + GameInputDeviceConnected, + GameInputBlockingEnumeration, + NULL, + GAMEINPUT_InternalJoystickDeviceCallback, + &g_GameInputCallbackToken); + if (FAILED(hR)) { + return SDL_SetError("IGameInput::RegisterDeviceCallback failure with HRESULT of %08lX", hR); + } + + // Calculate the relative offset between SDL timestamps and GameInput timestamps + Uint64 now = SDL_GetTicksNS(); + uint64_t timestampUS = IGameInput_GetCurrentTimestamp(g_pGameInput); + g_GameInputTimestampOffset = (SDL_NS_TO_US(now) - timestampUS); + + GAMEINPUT_JoystickDetect(); + + return true; +} + +static int GAMEINPUT_JoystickGetCount(void) +{ + SDL_AssertJoysticksLocked(); + + return g_GameInputList.count; +} + +static void GAMEINPUT_JoystickDetect(void) +{ + int idx; + GAMEINPUT_InternalDevice *elem = NULL; + + SDL_AssertJoysticksLocked(); + + for (idx = 0; idx < g_GameInputList.count; ++idx) { + elem = g_GameInputList.devices[idx]; + if (!elem) { + continue; + } + + if (!elem->isAdded) { + SDL_PrivateJoystickAdded(elem->device_instance); + elem->isAdded = true; + } + + if (elem->isDeleteRequested || !(IGameInputDevice_GetDeviceStatus(elem->device) & GameInputDeviceConnected)) { + SDL_PrivateJoystickRemoved(elem->device_instance); + GAMEINPUT_InternalRemoveByIndex(idx--); + } + } +} + +static bool GAMEINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + SDL_AssertJoysticksLocked(); + + if (g_pGameInput) { + if (vendor_id == USB_VENDOR_MICROSOFT && + product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) { + // The Xbox One controller shows up as a hardcoded raw input VID/PID, which we definitely handle + return true; + } + + for (int i = 0; i < g_GameInputList.count; ++i) { + GAMEINPUT_InternalDevice *elem = g_GameInputList.devices[i]; + if (elem && vendor_id == elem->info->vendorId && product_id == elem->info->productId) { + return true; + } + } + } + return false; +} + +static const char *GAMEINPUT_JoystickGetDeviceName(int device_index) +{ + return GAMEINPUT_InternalFindByIndex(device_index)->name; +} + +static const char *GAMEINPUT_JoystickGetDevicePath(int device_index) +{ + // APP_LOCAL_DEVICE_ID as a hex string, since it's required for some association callbacks + return GAMEINPUT_InternalFindByIndex(device_index)->path; +} + +static int GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + +static int GAMEINPUT_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void GAMEINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static SDL_GUID GAMEINPUT_JoystickGetDeviceGUID(int device_index) +{ + return GAMEINPUT_InternalFindByIndex(device_index)->guid; +} + +static SDL_JoystickID GAMEINPUT_JoystickGetDeviceInstanceID(int device_index) +{ + return GAMEINPUT_InternalFindByIndex(device_index)->device_instance; +} + +static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice *device) +{ + GameInputBatteryState battery_state; + SDL_PowerState state; + int percent = 0; + + SDL_zero(battery_state); + IGameInputDevice_GetBatteryState(device, &battery_state); + + switch (battery_state.status) { + case GameInputBatteryNotPresent: + state = SDL_POWERSTATE_NO_BATTERY; + break; + case GameInputBatteryDischarging: + state = SDL_POWERSTATE_ON_BATTERY; + break; + case GameInputBatteryIdle: + state = SDL_POWERSTATE_CHARGED; + break; + case GameInputBatteryCharging: + state = SDL_POWERSTATE_CHARGING; + break; + default: + state = SDL_POWERSTATE_UNKNOWN; + break; + } + if (battery_state.fullChargeCapacity > 0.0f) { + percent = (int)SDL_roundf((battery_state.remainingCapacity / battery_state.fullChargeCapacity) * 100.0f); + } + SDL_SendJoystickPowerInfo(joystick, state, percent); +} + +#ifdef IGameInput_RegisterSystemButtonCallback + +static void CALLBACK GAMEINPUT_InternalSystemButtonCallback( + _In_ GameInputCallbackToken callbackToken, + _In_ void * context, + _In_ IGameInputDevice * device, + _In_ uint64_t timestampUS, + _In_ GameInputSystemButtons currentButtons, + _In_ GameInputSystemButtons previousButtons) +{ + SDL_Joystick *joystick = (SDL_Joystick *)context; + + GameInputSystemButtons changedButtons = (previousButtons ^ currentButtons); + if (changedButtons) { + Uint64 timestamp = SDL_US_TO_NS(timestampUS + g_GameInputTimestampOffset); + + SDL_LockJoysticks(); + if (changedButtons & GameInputSystemButtonGuide) { + bool down = ((currentButtons & GameInputSystemButtonGuide) != 0); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, down); + } + if (changedButtons & GameInputSystemButtonShare) { + bool down = ((currentButtons & GameInputSystemButtonShare) != 0); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE, down); + } + SDL_UnlockJoysticks(); + } +} + +#endif // IGameInput_RegisterSystemButtonCallback + +static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index); + const GameInputDeviceInfo *info = elem->info; + GAMEINPUT_InternalJoystickHwdata *hwdata = NULL; + + if (!elem) { + return false; + } + + hwdata = (GAMEINPUT_InternalJoystickHwdata *)SDL_calloc(1, sizeof(*hwdata)); + if (!hwdata) { + return false; + } + + hwdata->devref = elem; + + joystick->hwdata = hwdata; + if (GAMEINPUT_InternalIsGamepad(info)) { + joystick->naxes = 6; + joystick->nbuttons = 11; + joystick->nhats = 1; + +#ifdef IGameInput_RegisterSystemButtonCallback + if (info->supportedSystemButtons != GameInputSystemButtonNone) { + if (info->supportedSystemButtons & GameInputSystemButtonShare) { + ++joystick->nbuttons; + } + +#if 1 // The C macro in GameInput.h version 10.0.26100 refers to a focus policy which I guess has been removed from the final API? +#undef IGameInput_RegisterSystemButtonCallback +#define IGameInput_RegisterSystemButtonCallback(This, device, buttonFilter, context, callbackFunc, callbackToken) ((This)->lpVtbl->RegisterSystemButtonCallback(This, device, buttonFilter, context, callbackFunc, callbackToken)) +#endif + IGameInput_RegisterSystemButtonCallback(g_pGameInput, elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token); + } +#endif // IGameInput_RegisterSystemButtonCallback + } else { + joystick->naxes = info->controllerAxisCount; + joystick->nbuttons = info->controllerButtonCount; + joystick->nhats = info->controllerSwitchCount; + } + + if (info->supportedRumbleMotors & (GameInputRumbleLowFrequency | GameInputRumbleHighFrequency)) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } + if (info->supportedRumbleMotors & (GameInputRumbleLeftTrigger | GameInputRumbleRightTrigger)) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); + } + + if (info->supportedInput & GameInputKindTouch) { + SDL_PrivateJoystickAddTouchpad(joystick, info->touchPointCount); + } + + if (info->supportedInput & GameInputKindMotion) { + // FIXME: What's the sensor update rate? + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + } + + if (info->capabilities & GameInputDeviceCapabilityWireless) { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + } else { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; + } + return true; +} + +static bool GAMEINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + // don't check for caps here, since SetRumbleState doesn't return any result - we don't need to check it + GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; + GameInputRumbleParams *params = &hwdata->rumbleParams; + params->lowFrequency = (float)low_frequency_rumble / (float)SDL_MAX_UINT16; + params->highFrequency = (float)high_frequency_rumble / (float)SDL_MAX_UINT16; + IGameInputDevice_SetRumbleState(hwdata->devref->device, params); + return true; +} + +static bool GAMEINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + // don't check for caps here, since SetRumbleState doesn't return any result - we don't need to check it + GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; + GameInputRumbleParams *params = &hwdata->rumbleParams; + params->leftTrigger = (float)left_rumble / (float)SDL_MAX_UINT16; + params->rightTrigger = (float)right_rumble / (float)SDL_MAX_UINT16; + IGameInputDevice_SetRumbleState(hwdata->devref->device, params); + return true; +} + +static bool GAMEINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool GAMEINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool GAMEINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + joystick->hwdata->report_sensors = enabled; + return true; +} + +static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) +{ + GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; + IGameInputDevice *device = hwdata->devref->device; + const GameInputDeviceInfo *info = hwdata->devref->info; + IGameInputReading *reading = NULL; + Uint64 timestamp; + GameInputGamepadState state; + HRESULT hR; + + hR = IGameInput_GetCurrentReading(g_pGameInput, info->supportedInput, device, &reading); + if (FAILED(hR)) { + // don't SetError here since there can be a legitimate case when there's no reading avail + return; + } + + timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + g_GameInputTimestampOffset); + + if (GAMEINPUT_InternalIsGamepad(info)) { + static WORD s_XInputButtons[] = { + GameInputGamepadA, // SDL_GAMEPAD_BUTTON_SOUTH + GameInputGamepadB, // SDL_GAMEPAD_BUTTON_EAST + GameInputGamepadX, // SDL_GAMEPAD_BUTTON_WEST + GameInputGamepadY, // SDL_GAMEPAD_BUTTON_NORTH + GameInputGamepadView, // SDL_GAMEPAD_BUTTON_BACK + 0, // The guide button is not available + GameInputGamepadMenu, // SDL_GAMEPAD_BUTTON_START + GameInputGamepadLeftThumbstick, // SDL_GAMEPAD_BUTTON_LEFT_STICK + GameInputGamepadRightThumbstick, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + GameInputGamepadLeftShoulder, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + GameInputGamepadRightShoulder, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + }; + Uint8 btnidx = 0, hat = 0; + + if (IGameInputReading_GetGamepadState(reading, &state)) { + for (btnidx = 0; btnidx < SDL_arraysize(s_XInputButtons); ++btnidx) { + WORD button_mask = s_XInputButtons[btnidx]; + if (!button_mask) { + continue; + } + bool down = ((state.buttons & button_mask) != 0); + SDL_SendJoystickButton(timestamp, joystick, btnidx, down); + } + + if (state.buttons & GameInputGamepadDPadUp) { + hat |= SDL_HAT_UP; + } + if (state.buttons & GameInputGamepadDPadDown) { + hat |= SDL_HAT_DOWN; + } + if (state.buttons & GameInputGamepadDPadLeft) { + hat |= SDL_HAT_LEFT; + } + if (state.buttons & GameInputGamepadDPadRight) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + +#define CONVERT_AXIS(v) (Sint16)(((v) < 0.0f) ? ((v)*32768.0f) : ((v)*32767.0f)) + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, CONVERT_AXIS(state.leftThumbstickX)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, CONVERT_AXIS(-state.leftThumbstickY)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, CONVERT_AXIS(state.rightThumbstickX)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, CONVERT_AXIS(-state.rightThumbstickY)); +#undef CONVERT_AXIS +#define CONVERT_TRIGGER(v) (Sint16)((v)*65535.0f - 32768.0f) + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, CONVERT_TRIGGER(state.leftTrigger)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, CONVERT_TRIGGER(state.rightTrigger)); +#undef CONVERT_TRIGGER + } + } else { + bool *button_state = SDL_stack_alloc(bool, info->controllerButtonCount); + float *axis_state = SDL_stack_alloc(float, info->controllerAxisCount); + GameInputSwitchPosition *switch_state = SDL_stack_alloc(GameInputSwitchPosition, info->controllerSwitchCount); + + if (button_state) { + uint32_t i; + uint32_t button_count = IGameInputReading_GetControllerButtonState(reading, info->controllerButtonCount, button_state); + for (i = 0; i < button_count; ++i) { + SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, button_state[i]); + } + SDL_stack_free(button_state); + } + +#define CONVERT_AXIS(v) (Sint16)((v)*65535.0f - 32768.0f) + if (axis_state) { + uint32_t i; + uint32_t axis_count = IGameInputReading_GetControllerAxisState(reading, info->controllerAxisCount, axis_state); + for (i = 0; i < axis_count; ++i) { + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, CONVERT_AXIS(axis_state[i])); + } + SDL_stack_free(axis_state); + } +#undef CONVERT_AXIS + + if (switch_state) { + uint32_t i; + uint32_t switch_count = IGameInputReading_GetControllerSwitchState(reading, info->controllerSwitchCount, switch_state); + for (i = 0; i < switch_count; ++i) { + Uint8 hat; + switch (switch_state[i]) { + case GameInputSwitchUp: + hat = SDL_HAT_UP; + break; + case GameInputSwitchUpRight: + hat = SDL_HAT_UP | SDL_HAT_RIGHT; + break; + case GameInputSwitchRight: + hat = SDL_HAT_RIGHT; + break; + case GameInputSwitchDownRight: + hat = SDL_HAT_DOWN | SDL_HAT_RIGHT; + break; + case GameInputSwitchDown: + hat = SDL_HAT_DOWN; + break; + case GameInputSwitchDownLeft: + hat = SDL_HAT_DOWN | SDL_HAT_LEFT; + break; + case GameInputSwitchLeft: + hat = SDL_HAT_LEFT; + break; + case GameInputSwitchUpLeft: + hat = SDL_HAT_UP | SDL_HAT_LEFT; + break; + case GameInputSwitchCenter: + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat); + } + SDL_stack_free(switch_state); + } + } + + if (info->supportedInput & GameInputKindTouch) { + GameInputTouchState *touch_state = SDL_stack_alloc(GameInputTouchState, info->touchPointCount); + if (touch_state) { + uint32_t i; + uint32_t touch_count = IGameInputReading_GetTouchState(reading, info->touchPointCount, touch_state); + for (i = 0; i < touch_count; ++i) { + GameInputTouchState *touch = &touch_state[i]; + // FIXME: We should use touch->touchId to track fingers instead of using i below + SDL_SendJoystickTouchpad(timestamp, joystick, 0, i, true, touch->positionX * info->touchSensorInfo[i].resolutionX, touch->positionY * info->touchSensorInfo[0].resolutionY, touch->pressure); + } + SDL_stack_free(touch_state); + } + } + + if (hwdata->report_sensors) { + GameInputMotionState motion_state; + + if (IGameInputReading_GetMotionState(reading, &motion_state)) { + // FIXME: How do we interpret the motion data? + } + } + + IGameInputReading_Release(reading); + + // FIXME: We can poll this at a much lower rate + GAMEINPUT_UpdatePowerInfo(joystick, device); +} + +static void GAMEINPUT_JoystickClose(SDL_Joystick* joystick) +{ + GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; + + if (hwdata->system_button_callback_token) { + IGameInput_UnregisterCallback(g_pGameInput, hwdata->system_button_callback_token, 5000); + } + SDL_free(hwdata); + + joystick->hwdata = NULL; +} + +static void GAMEINPUT_JoystickQuit(void) +{ + if (g_pGameInput) { + // free the callback + IGameInput_UnregisterCallback(g_pGameInput, g_GameInputCallbackToken, /*timeoutInUs:*/ 10000); + g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE; + + // free the list + while (g_GameInputList.count > 0) { + GAMEINPUT_InternalRemoveByIndex(0); + } + + SDL_QuitGameInput(); + g_pGameInput = NULL; + } +} + +static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index); + + if (!GAMEINPUT_InternalIsGamepad(elem->info)) { + return false; + } + + out->a.kind = EMappingKind_Button; + out->a.target = SDL_GAMEPAD_BUTTON_SOUTH; + + out->b.kind = EMappingKind_Button; + out->b.target = SDL_GAMEPAD_BUTTON_EAST; + + out->x.kind = EMappingKind_Button; + out->x.target = SDL_GAMEPAD_BUTTON_WEST; + + out->y.kind = EMappingKind_Button; + out->y.target = SDL_GAMEPAD_BUTTON_NORTH; + + out->back.kind = EMappingKind_Button; + out->back.target = SDL_GAMEPAD_BUTTON_BACK; + +#ifdef IGameInput_RegisterSystemButtonCallback + if (elem->info->supportedSystemButtons & GameInputSystemButtonGuide) { + out->guide.kind = EMappingKind_Button; + out->guide.target = SDL_GAMEPAD_BUTTON_GUIDE; + } + + if (elem->info->supportedSystemButtons & GameInputSystemButtonShare) { + out->misc1.kind = EMappingKind_Button; + out->misc1.target = SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE; + } +#endif + + out->start.kind = EMappingKind_Button; + out->start.target = SDL_GAMEPAD_BUTTON_START; + + out->leftstick.kind = EMappingKind_Button; + out->leftstick.target = SDL_GAMEPAD_BUTTON_LEFT_STICK; + + out->rightstick.kind = EMappingKind_Button; + out->rightstick.target = SDL_GAMEPAD_BUTTON_RIGHT_STICK; + + out->leftshoulder.kind = EMappingKind_Button; + out->leftshoulder.target = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER; + + out->rightshoulder.kind = EMappingKind_Button; + out->rightshoulder.target = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER; + + out->dpup.kind = EMappingKind_Hat; + out->dpup.target = SDL_HAT_UP; + + out->dpdown.kind = EMappingKind_Hat; + out->dpdown.target = SDL_HAT_DOWN; + + out->dpleft.kind = EMappingKind_Hat; + out->dpleft.target = SDL_HAT_LEFT; + + out->dpright.kind = EMappingKind_Hat; + out->dpright.target = SDL_HAT_RIGHT; + + out->leftx.kind = EMappingKind_Axis; + out->leftx.target = SDL_GAMEPAD_AXIS_LEFTX; + + out->lefty.kind = EMappingKind_Axis; + out->lefty.target = SDL_GAMEPAD_AXIS_LEFTY; + + out->rightx.kind = EMappingKind_Axis; + out->rightx.target = SDL_GAMEPAD_AXIS_RIGHTX; + + out->righty.kind = EMappingKind_Axis; + out->righty.target = SDL_GAMEPAD_AXIS_RIGHTY; + + out->lefttrigger.kind = EMappingKind_Axis; + out->lefttrigger.target = SDL_GAMEPAD_AXIS_LEFT_TRIGGER; + + out->righttrigger.kind = EMappingKind_Axis; + out->righttrigger.target = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; + + return true; +} + + +SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver = +{ + GAMEINPUT_JoystickInit, + GAMEINPUT_JoystickGetCount, + GAMEINPUT_JoystickDetect, + GAMEINPUT_JoystickIsDevicePresent, + GAMEINPUT_JoystickGetDeviceName, + GAMEINPUT_JoystickGetDevicePath, + GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot, + GAMEINPUT_JoystickGetDevicePlayerIndex, + GAMEINPUT_JoystickSetDevicePlayerIndex, + GAMEINPUT_JoystickGetDeviceGUID, + GAMEINPUT_JoystickGetDeviceInstanceID, + GAMEINPUT_JoystickOpen, + GAMEINPUT_JoystickRumble, + GAMEINPUT_JoystickRumbleTriggers, + GAMEINPUT_JoystickSetLED, + GAMEINPUT_JoystickSendEffect, + GAMEINPUT_JoystickSetSensorsEnabled, + GAMEINPUT_JoystickUpdate, + GAMEINPUT_JoystickClose, + GAMEINPUT_JoystickQuit, + GAMEINPUT_JoystickGetGamepadMapping +}; + + +#endif // SDL_JOYSTICK_GAMEINPUT diff --git a/contrib/SDL-3.2.8/src/joystick/haiku/SDL_haikujoystick.cc b/contrib/SDL-3.2.8/src/joystick/haiku/SDL_haikujoystick.cc new file mode 100644 index 0000000..977e4fc --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/haiku/SDL_haikujoystick.cc @@ -0,0 +1,315 @@ +/* + 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_JOYSTICK_HAIKU + +// This is the Haiku implementation of the SDL joystick API + +#include +#include + +extern "C" +{ + +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" + + +// The maximum number of joysticks we'll detect +#define MAX_JOYSTICKS 16 + +// A list of available joysticks + static char *SDL_joyport[MAX_JOYSTICKS]; + static char *SDL_joyname[MAX_JOYSTICKS]; + +// The private structure used to keep track of a joystick + struct joystick_hwdata + { + BJoystick *stick; + uint8 *new_hats; + int16 *new_axes; + }; + + static int numjoysticks = 0; + + static bool HAIKU_JoystickInit(void) + { + BJoystick joystick; + int i; + int32 nports; + char name[B_OS_NAME_LENGTH]; + + // Search for attached joysticks + nports = joystick.CountDevices(); + numjoysticks = 0; + SDL_memset(SDL_joyport, 0, sizeof(SDL_joyport)); + SDL_memset(SDL_joyname, 0, sizeof(SDL_joyname)); + for (i = 0; (numjoysticks < MAX_JOYSTICKS) && (i < nports); ++i) { + if (joystick.GetDeviceName(i, name) == B_OK) { + if (joystick.Open(name) != B_ERROR) { + BString stick_name; + joystick.GetControllerName(&stick_name); + SDL_joyport[numjoysticks] = SDL_strdup(name); + SDL_joyname[numjoysticks] = SDL_CreateJoystickName(0, 0, NULL, stick_name.String()); + numjoysticks++; + joystick.Close(); + + SDL_PrivateJoystickAdded(numjoysticks); + } + } + } + return true; + } + + static int HAIKU_JoystickGetCount(void) + { + return numjoysticks; + } + + static void HAIKU_JoystickDetect(void) + { + } + + static bool HAIKU_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) + { + // We don't override any other drivers + return false; + } + + static const char *HAIKU_JoystickGetDeviceName(int device_index) + { + return SDL_joyname[device_index]; + } + + static const char *HAIKU_JoystickGetDevicePath(int device_index) + { + return SDL_joyport[device_index]; + } + + static int HAIKU_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) + { + return -1; + } + + static int HAIKU_JoystickGetDevicePlayerIndex(int device_index) + { + return -1; + } + + static void HAIKU_JoystickSetDevicePlayerIndex(int device_index, int player_index) + { + } + + static SDL_JoystickID HAIKU_JoystickGetDeviceInstanceID(int device_index) + { + return device_index + 1; + } + + static void HAIKU_JoystickClose(SDL_Joystick *joystick); + + static bool HAIKU_JoystickOpen(SDL_Joystick *joystick, int device_index) + { + BJoystick *stick; + + // Create the joystick data structure + joystick->hwdata = (struct joystick_hwdata *) SDL_calloc(1, sizeof(*joystick->hwdata)); + if (joystick->hwdata == NULL) { + return false; + } + stick = new BJoystick; + joystick->hwdata->stick = stick; + + // Open the requested joystick for use + if (stick->Open(SDL_joyport[device_index]) == B_ERROR) { + HAIKU_JoystickClose(joystick); + return SDL_SetError("Unable to open joystick"); + } + + // Set the joystick to calibrated mode + stick->EnableCalibration(); + + // Get the number of buttons, hats, and axes on the joystick + joystick->nbuttons = stick->CountButtons(); + joystick->naxes = stick->CountAxes(); + joystick->nhats = stick->CountHats(); + + joystick->hwdata->new_axes = (int16 *) SDL_calloc(joystick->naxes, sizeof(int16)); + joystick->hwdata->new_hats = (uint8 *) SDL_calloc(joystick->nhats, sizeof(uint8)); + if (!joystick->hwdata->new_hats || !joystick->hwdata->new_axes) { + HAIKU_JoystickClose(joystick); + return false; + } + + // We're done! + return true; + } + +/* Function to update the state of a joystick - called as a device poll. + * This function shouldn't update the joystick structure directly, + * but instead should call SDL_PrivateJoystick*() to deliver events + * and update joystick device state. + */ + static void HAIKU_JoystickUpdate(SDL_Joystick *joystick) + { + static const Uint8 hat_map[9] = { + SDL_HAT_CENTERED, + SDL_HAT_UP, + SDL_HAT_RIGHTUP, + SDL_HAT_RIGHT, + SDL_HAT_RIGHTDOWN, + SDL_HAT_DOWN, + SDL_HAT_LEFTDOWN, + SDL_HAT_LEFT, + SDL_HAT_LEFTUP + }; + + BJoystick *stick; + int i; + int16 *axes; + uint8 *hats; + uint32 buttons; + Uint64 timestamp = SDL_GetTicksNS(); + + // Set up data pointers + stick = joystick->hwdata->stick; + axes = joystick->hwdata->new_axes; + hats = joystick->hwdata->new_hats; + + // Get the new joystick state + stick->Update(); + stick->GetAxisValues(axes); + stick->GetHatValues(hats); + buttons = stick->ButtonValues(); + + // Generate axis motion events + for (i = 0; i < joystick->naxes; ++i) { + SDL_SendJoystickAxis(timestamp, joystick, i, axes[i]); + } + + // Generate hat change events + for (i = 0; i < joystick->nhats; ++i) { + SDL_SendJoystickHat(timestamp, joystick, i, hat_map[hats[i]]); + } + + // Generate button events + for (i = 0; i < joystick->nbuttons; ++i) { + bool down = ((buttons & 0x01) != 0); + SDL_SendJoystickButton(timestamp, joystick, i, down); + buttons >>= 1; + } + } + +// Function to close a joystick after use + static void HAIKU_JoystickClose(SDL_Joystick *joystick) + { + if (joystick->hwdata) { + joystick->hwdata->stick->Close(); + delete joystick->hwdata->stick; + SDL_free(joystick->hwdata->new_hats); + SDL_free(joystick->hwdata->new_axes); + SDL_free(joystick->hwdata); + } + } + +// Function to perform any system-specific joystick related cleanup + static void HAIKU_JoystickQuit(void) + { + int i; + + for (i = 0; i < numjoysticks; ++i) { + SDL_free(SDL_joyport[i]); + } + SDL_joyport[0] = NULL; + + for (i = 0; i < numjoysticks; ++i) { + SDL_free(SDL_joyname[i]); + } + SDL_joyname[0] = NULL; + } + + static SDL_GUID HAIKU_JoystickGetDeviceGUID(int device_index) + { + // the GUID is just the name for now + const char *name = HAIKU_JoystickGetDeviceName(device_index); + return SDL_CreateJoystickGUIDForName(name); + } + + static bool HAIKU_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) + { + return SDL_Unsupported(); + } + + + static bool HAIKU_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) + { + return SDL_Unsupported(); + } + + static bool HAIKU_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) + { + return false; + } + + static bool HAIKU_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) + { + return SDL_Unsupported(); + } + + + static bool HAIKU_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) + { + return SDL_Unsupported(); + } + + static bool HAIKU_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) + { + return SDL_Unsupported(); + } + + SDL_JoystickDriver SDL_HAIKU_JoystickDriver = + { + HAIKU_JoystickInit, + HAIKU_JoystickGetCount, + HAIKU_JoystickDetect, + HAIKU_JoystickIsDevicePresent, + HAIKU_JoystickGetDeviceName, + HAIKU_JoystickGetDevicePath, + HAIKU_JoystickGetDeviceSteamVirtualGamepadSlot, + HAIKU_JoystickGetDevicePlayerIndex, + HAIKU_JoystickSetDevicePlayerIndex, + HAIKU_JoystickGetDeviceGUID, + HAIKU_JoystickGetDeviceInstanceID, + HAIKU_JoystickOpen, + HAIKU_JoystickRumble, + HAIKU_JoystickRumbleTriggers, + HAIKU_JoystickSetLED, + HAIKU_JoystickSendEffect, + HAIKU_JoystickSetSensorsEnabled, + HAIKU_JoystickUpdate, + HAIKU_JoystickClose, + HAIKU_JoystickQuit, + HAIKU_JoystickGetGamepadMapping + }; + +} // extern "C" + +#endif // SDL_JOYSTICK_HAIKU diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_combined.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_combined.c new file mode 100644 index 0000000..5426edb --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_combined.c @@ -0,0 +1,236 @@ +/* + 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. +*/ +// This driver supports the Nintendo Switch Joy-Cons pair controllers +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "SDL_hidapijoystick_c.h" +#include "../SDL_sysjoystick.h" + +static void HIDAPI_DriverCombined_RegisterHints(SDL_HintCallback callback, void *userdata) +{ +} + +static void HIDAPI_DriverCombined_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ +} + +static bool HIDAPI_DriverCombined_IsEnabled(void) +{ + return true; +} + +static bool HIDAPI_DriverCombined_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + // This is always explicitly created for combined devices + return false; +} + +static bool HIDAPI_DriverCombined_InitDevice(SDL_HIDAPI_Device *device) +{ + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverCombined_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverCombined_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static bool HIDAPI_DriverCombined_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + int i; + char *serial = NULL, *new_serial; + size_t serial_length = 0, new_length; + + SDL_AssertJoysticksLocked(); + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + if (!child->driver->OpenJoystick(child, joystick)) { + child->broken = true; + + while (i-- > 0) { + child = device->children[i]; + child->driver->CloseJoystick(child, joystick); + } + if (serial) { + SDL_free(serial); + } + return false; + } + + // Extend the serial number with the child serial number + if (joystick->serial) { + new_length = serial_length + 1 + SDL_strlen(joystick->serial); + new_serial = (char *)SDL_realloc(serial, new_length); + if (new_serial) { + if (serial) { + SDL_strlcat(new_serial, ",", new_length); + SDL_strlcat(new_serial, joystick->serial, new_length); + } else { + SDL_strlcpy(new_serial, joystick->serial, new_length); + } + serial = new_serial; + serial_length = new_length; + } + SDL_free(joystick->serial); + joystick->serial = NULL; + } + } + + // Update the joystick with the combined serial numbers + if (joystick->serial) { + SDL_free(joystick->serial); + } + joystick->serial = serial; + + return true; +} + +static bool HIDAPI_DriverCombined_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + int i; + bool result = false; + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + if (child->driver->RumbleJoystick(child, joystick, low_frequency_rumble, high_frequency_rumble)) { + result = true; + } + } + return result; +} + +static bool HIDAPI_DriverCombined_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + int i; + bool result = false; + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + if (child->driver->RumbleJoystickTriggers(child, joystick, left_rumble, right_rumble)) { + result = true; + } + } + return result; +} + +static Uint32 HIDAPI_DriverCombined_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + int i; + Uint32 caps = 0; + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + caps |= child->driver->GetJoystickCapabilities(child, joystick); + } + return caps; +} + +static bool HIDAPI_DriverCombined_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + int i; + bool result = false; + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + if (child->driver->SetJoystickLED(child, joystick, red, green, blue)) { + result = true; + } + } + return result; +} + +static bool HIDAPI_DriverCombined_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverCombined_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + int i; + bool result = false; + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + if (child->driver->SetJoystickSensorsEnabled(child, joystick, enabled)) { + result = true; + } + } + return result; +} + +static bool HIDAPI_DriverCombined_UpdateDevice(SDL_HIDAPI_Device *device) +{ + int i; + int result = true; + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + if (!child->driver->UpdateDevice(child)) { + result = false; + } + } + return result; +} + +static void HIDAPI_DriverCombined_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + int i; + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + child->driver->CloseJoystick(child, joystick); + } +} + +static void HIDAPI_DriverCombined_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined = { + "SDL_JOYSTICK_HIDAPI_COMBINED", + true, + HIDAPI_DriverCombined_RegisterHints, + HIDAPI_DriverCombined_UnregisterHints, + HIDAPI_DriverCombined_IsEnabled, + HIDAPI_DriverCombined_IsSupportedDevice, + HIDAPI_DriverCombined_InitDevice, + HIDAPI_DriverCombined_GetDevicePlayerIndex, + HIDAPI_DriverCombined_SetDevicePlayerIndex, + HIDAPI_DriverCombined_UpdateDevice, + HIDAPI_DriverCombined_OpenJoystick, + HIDAPI_DriverCombined_RumbleJoystick, + HIDAPI_DriverCombined_RumbleJoystickTriggers, + HIDAPI_DriverCombined_GetJoystickCapabilities, + HIDAPI_DriverCombined_SetJoystickLED, + HIDAPI_DriverCombined_SendJoystickEffect, + HIDAPI_DriverCombined_SetJoystickSensorsEnabled, + HIDAPI_DriverCombined_CloseJoystick, + HIDAPI_DriverCombined_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_gamecube.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_gamecube.c new file mode 100644 index 0000000..4d45c7a --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_gamecube.c @@ -0,0 +1,534 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" +#include "../../hidapi/SDL_hidapi_c.h" + +#ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE + +// Define this if you want to log all packets from the controller +// #define DEBUG_GAMECUBE_PROTOCOL + +#define MAX_CONTROLLERS 4 + +typedef struct +{ + bool pc_mode; + SDL_JoystickID joysticks[MAX_CONTROLLERS]; + Uint8 wireless[MAX_CONTROLLERS]; + Uint8 min_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_COUNT]; + Uint8 max_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_COUNT]; + Uint8 rumbleAllowed[MAX_CONTROLLERS]; + Uint8 rumble[1 + MAX_CONTROLLERS]; + // Without this variable, hid_write starts to lag a TON + bool rumbleUpdate; + bool useRumbleBrake; +} SDL_DriverGameCube_Context; + +static void HIDAPI_DriverGameCube_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata); +} + +static void HIDAPI_DriverGameCube_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata); +} + +static bool HIDAPI_DriverGameCube_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, + SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverGameCube_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) { + // Nintendo Co., Ltd. Wii U GameCube Controller Adapter + return true; + } + if (vendor_id == USB_VENDOR_DRAGONRISE && + (product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 || + product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2)) { + // EVORETRO GameCube Controller Adapter + return true; + } + return false; +} + +static void ResetAxisRange(SDL_DriverGameCube_Context *ctx, int joystick_index) +{ + SDL_memset(&ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT], 128 - 88, SDL_GAMEPAD_AXIS_COUNT); + SDL_memset(&ctx->max_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT], 128 + 88, SDL_GAMEPAD_AXIS_COUNT); + + // Trigger axes may have a higher resting value + ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT + SDL_GAMEPAD_AXIS_LEFT_TRIGGER] = 40; + ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER] = 40; +} + +static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + if (hint) { + SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata; + ctx->useRumbleBrake = SDL_GetStringBoolean(hint, false); + } +} + +static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverGameCube_Context *ctx; + Uint8 packet[37]; + Uint8 *curSlot; + Uint8 i; + int size; + Uint8 initMagic = 0x13; + Uint8 rumbleMagic = 0x11; + +#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS + SDL_EnableGameCubeAdaptors(); +#endif + + ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + device->context = ctx; + + ctx->joysticks[0] = 0; + ctx->joysticks[1] = 0; + ctx->joysticks[2] = 0; + ctx->joysticks[3] = 0; + ctx->rumble[0] = rumbleMagic; + ctx->useRumbleBrake = false; + + if (device->vendor_id != USB_VENDOR_NINTENDO) { + ctx->pc_mode = true; + } + + if (ctx->pc_mode) { + for (i = 0; i < MAX_CONTROLLERS; ++i) { + ResetAxisRange(ctx, i); + HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); + } + } else { + // This is all that's needed to initialize the device. Really! + if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028"); + return false; + } + + // Wait for the adapter to initialize + SDL_Delay(10); + + // Add all the applicable joysticks + while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) { +#ifdef DEBUG_GAMECUBE_PROTOCOL + HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size); +#endif + if (size < 37 || packet[0] != 0x21) { + continue; // Nothing to do yet...? + } + + // Go through all 4 slots + curSlot = packet + 1; + for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) { + ctx->wireless[i] = (curSlot[0] & 0x20) != 0; + + // Only allow rumble if the adapter's second USB cable is connected + ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i]; + + if (curSlot[0] & 0x30) { // 0x10 - Wired, 0x20 - Wireless + if (ctx->joysticks[i] == 0) { + ResetAxisRange(ctx, i); + HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); + } + } else { + if (ctx->joysticks[i] != 0) { + HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]); + ctx->joysticks[i] = 0; + } + continue; + } + } + } + } + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE, + SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx); + + HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller"); + + return true; +} + +static int HIDAPI_DriverGameCube_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; + Uint8 i; + + for (i = 0; i < 4; ++i) { + if (instance_id == ctx->joysticks[i]) { + return i; + } + } + return -1; +} + +static void HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, int size) +{ + SDL_Joystick *joystick; + Uint8 i, v; + Sint16 axis_value; + Uint64 timestamp = SDL_GetTicksNS(); + + if (size != 10) { + return; // How do we handle this packet? + } + + i = packet[0] - 1; + if (i >= MAX_CONTROLLERS) { + return; // How do we handle this packet? + } + + joystick = SDL_GetJoystickFromID(ctx->joysticks[i]); + if (!joystick) { + // Hasn't been opened yet, skip + return; + } + +#define READ_BUTTON(off, flag, button) \ + SDL_SendJoystickButton( \ + timestamp, \ + joystick, \ + button, \ + ((packet[off] & flag) != 0)); + READ_BUTTON(1, 0x02, 0) // A + READ_BUTTON(1, 0x04, 1) // B + READ_BUTTON(1, 0x08, 3) // Y + READ_BUTTON(1, 0x01, 2) // X + READ_BUTTON(2, 0x80, 4) // DPAD_LEFT + READ_BUTTON(2, 0x20, 5) // DPAD_RIGHT + READ_BUTTON(2, 0x40, 6) // DPAD_DOWN + READ_BUTTON(2, 0x10, 7) // DPAD_UP + READ_BUTTON(2, 0x02, 8) // START + READ_BUTTON(1, 0x80, 9) // RIGHTSHOULDER + /* These two buttons are for the bottoms of the analog triggers. + * More than likely, you're going to want to read the axes instead! + * -flibit + */ + READ_BUTTON(1, 0x20, 10) // TRIGGERRIGHT + READ_BUTTON(1, 0x10, 11) // TRIGGERLEFT +#undef READ_BUTTON + +#define READ_AXIS(off, axis, invert) \ + v = invert ? (0xff - packet[off]) : packet[off]; \ + if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ + ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ + if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ + ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ + axis_value = (Sint16)HIDAPI_RemapVal(v, ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \ + SDL_SendJoystickAxis( \ + timestamp, \ + joystick, \ + axis, axis_value); + READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX, 0) + READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY, 1) + READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTX, 0) + READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTY, 1) + READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0) + READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0) +#undef READ_AXIS +} + +static void HIDAPI_DriverGameCube_HandleNintendoPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, Uint8 *packet, int size) +{ + SDL_Joystick *joystick; + Uint8 *curSlot; + Uint8 i; + Sint16 axis_value; + Uint64 timestamp = SDL_GetTicksNS(); + + if (size < 37 || packet[0] != 0x21) { + return; // Nothing to do right now...? + } + + // Go through all 4 slots + curSlot = packet + 1; + for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) { + ctx->wireless[i] = (curSlot[0] & 0x20) != 0; + + // Only allow rumble if the adapter's second USB cable is connected + ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i]; + + if (curSlot[0] & 0x30) { // 0x10 - Wired, 0x20 - Wireless + if (ctx->joysticks[i] == 0) { + ResetAxisRange(ctx, i); + HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); + } + joystick = SDL_GetJoystickFromID(ctx->joysticks[i]); + + // Hasn't been opened yet, skip + if (!joystick) { + continue; + } + } else { + if (ctx->joysticks[i] != 0) { + HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]); + ctx->joysticks[i] = 0; + } + continue; + } + +#define READ_BUTTON(off, flag, button) \ + SDL_SendJoystickButton( \ + timestamp, \ + joystick, \ + button, \ + ((curSlot[off] & flag) != 0)); + READ_BUTTON(1, 0x01, 0) // A + READ_BUTTON(1, 0x02, 1) // B + READ_BUTTON(1, 0x04, 2) // X + READ_BUTTON(1, 0x08, 3) // Y + READ_BUTTON(1, 0x10, 4) // DPAD_LEFT + READ_BUTTON(1, 0x20, 5) // DPAD_RIGHT + READ_BUTTON(1, 0x40, 6) // DPAD_DOWN + READ_BUTTON(1, 0x80, 7) // DPAD_UP + READ_BUTTON(2, 0x01, 8) // START + READ_BUTTON(2, 0x02, 9) // RIGHTSHOULDER + /* These two buttons are for the bottoms of the analog triggers. + * More than likely, you're going to want to read the axes instead! + * -flibit + */ + READ_BUTTON(2, 0x04, 10) // TRIGGERRIGHT + READ_BUTTON(2, 0x08, 11) // TRIGGERLEFT +#undef READ_BUTTON + +#define READ_AXIS(off, axis) \ + if (curSlot[off] < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ + ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = curSlot[off]; \ + if (curSlot[off] > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ + ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = curSlot[off]; \ + axis_value = (Sint16)HIDAPI_RemapVal(curSlot[off], ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \ + SDL_SendJoystickAxis( \ + timestamp, \ + joystick, \ + axis, axis_value); + READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX) + READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY) + READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTX) + READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTY) + READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) + READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) +#undef READ_AXIS + } +} + +static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; + Uint8 packet[USB_PACKET_LENGTH]; + int size; + + // Read input packet + while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) { +#ifdef DEBUG_GAMECUBE_PROTOCOL + HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size); +#endif + if (ctx->pc_mode) { + HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, size); + } else { + HIDAPI_DriverGameCube_HandleNintendoPacket(device, ctx, packet, size); + } + } + + // Write rumble packet + if (ctx->rumbleUpdate) { + SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble)); + ctx->rumbleUpdate = false; + } + + // If we got here, nothing bad happened! + return true; +} + +static bool HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; + Uint8 i; + + SDL_AssertJoysticksLocked(); + + for (i = 0; i < MAX_CONTROLLERS; i += 1) { + if (joystick->instance_id == ctx->joysticks[i]) { + joystick->nbuttons = 12; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + if (ctx->wireless[i]) { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + } else { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; + } + return true; + } + } + return false; // Should never get here! +} + +static bool HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; + Uint8 i, val; + + SDL_AssertJoysticksLocked(); + + if (ctx->pc_mode) { + return SDL_Unsupported(); + } + + for (i = 0; i < MAX_CONTROLLERS; i += 1) { + if (joystick->instance_id == ctx->joysticks[i]) { + if (ctx->wireless[i]) { + return SDL_SetError("Nintendo GameCube WaveBird controllers do not support rumble"); + } + if (!ctx->rumbleAllowed[i]) { + return SDL_SetError("Second USB cable for WUP-028 not connected"); + } + if (ctx->useRumbleBrake) { + if (low_frequency_rumble == 0 && high_frequency_rumble > 0) { + val = 0; // if only low is 0 we want to do a regular stop + } else if (low_frequency_rumble == 0 && high_frequency_rumble == 0) { + val = 2; // if both frequencies are 0 we want to do a hard stop + } else { + val = 1; // normal rumble + } + } else { + val = (low_frequency_rumble > 0 || high_frequency_rumble > 0); + } + if (val != ctx->rumble[i + 1]) { + ctx->rumble[i + 1] = val; + ctx->rumbleUpdate = true; + } + return true; + } + } + + // Should never get here! + return SDL_SetError("Couldn't find joystick"); +} + +static bool HIDAPI_DriverGameCube_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverGameCube_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; + Uint32 result = 0; + + SDL_AssertJoysticksLocked(); + + if (!ctx->pc_mode) { + Uint8 i; + + for (i = 0; i < MAX_CONTROLLERS; i += 1) { + if (joystick->instance_id == ctx->joysticks[i]) { + if (!ctx->wireless[i] && ctx->rumbleAllowed[i]) { + result |= SDL_JOYSTICK_CAP_RUMBLE; + break; + } + } + } + } + + return result; +} + +static bool HIDAPI_DriverGameCube_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverGameCube_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverGameCube_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; + + // Stop rumble activity + if (ctx->rumbleUpdate) { + SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble)); + ctx->rumbleUpdate = false; + } +} + +static void HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE, + SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx); +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube = { + SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, + true, + HIDAPI_DriverGameCube_RegisterHints, + HIDAPI_DriverGameCube_UnregisterHints, + HIDAPI_DriverGameCube_IsEnabled, + HIDAPI_DriverGameCube_IsSupportedDevice, + HIDAPI_DriverGameCube_InitDevice, + HIDAPI_DriverGameCube_GetDevicePlayerIndex, + HIDAPI_DriverGameCube_SetDevicePlayerIndex, + HIDAPI_DriverGameCube_UpdateDevice, + HIDAPI_DriverGameCube_OpenJoystick, + HIDAPI_DriverGameCube_RumbleJoystick, + HIDAPI_DriverGameCube_RumbleJoystickTriggers, + HIDAPI_DriverGameCube_GetJoystickCapabilities, + HIDAPI_DriverGameCube_SetJoystickLED, + HIDAPI_DriverGameCube_SendJoystickEffect, + HIDAPI_DriverGameCube_SetJoystickSensorsEnabled, + HIDAPI_DriverGameCube_CloseJoystick, + HIDAPI_DriverGameCube_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_GAMECUBE + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_luna.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_luna.c new file mode 100644 index 0000000..7c889a6 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_luna.c @@ -0,0 +1,421 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_LUNA + +// Define this if you want to log all packets from the controller +// #define DEBUG_LUNA_PROTOCOL + +// Sending rumble on macOS blocks for a long time and eventually fails +#ifndef SDL_PLATFORM_MACOS +#define ENABLE_LUNA_BLUETOOTH_RUMBLE +#endif + +enum +{ + SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE = 11, + SDL_GAMEPAD_NUM_LUNA_BUTTONS, +}; + +typedef struct +{ + Uint8 last_state[USB_PACKET_LENGTH]; +} SDL_DriverLuna_Context; + +static void HIDAPI_DriverLuna_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata); +} + +static void HIDAPI_DriverLuna_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata); +} + +static bool HIDAPI_DriverLuna_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LUNA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverLuna_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + return SDL_IsJoystickAmazonLunaController(vendor_id, product_id); +} + +static bool HIDAPI_DriverLuna_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverLuna_Context *ctx; + + ctx = (SDL_DriverLuna_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + device->context = ctx; + + HIDAPI_SetDeviceName(device, "Amazon Luna Controller"); + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverLuna_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverLuna_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static bool HIDAPI_DriverLuna_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + SDL_zeroa(ctx->last_state); + + // Initialize the joystick capabilities + joystick->nbuttons = SDL_GAMEPAD_NUM_LUNA_BUTTONS; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + return true; +} + +static bool HIDAPI_DriverLuna_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ +#ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE + if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) { + // Same packet as on Xbox One controllers connected via Bluetooth + Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB }; + + // Magnitude is 1..100 so scale the 16-bit input here + rumble_packet[4] = (Uint8)(low_frequency_rumble / 655); + rumble_packet[5] = (Uint8)(high_frequency_rumble / 655); + + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + + return true; + } +#endif // ENABLE_LUNA_BLUETOOTH_RUMBLE + + // There is currently no rumble packet over USB + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverLuna_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverLuna_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + Uint32 result = 0; + +#ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE + if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) { + result |= SDL_JOYSTICK_CAP_RUMBLE; + } +#endif + + return result; +} + +static bool HIDAPI_DriverLuna_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverLuna_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverLuna_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void HIDAPI_DriverLuna_HandleUSBStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size) +{ + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[1] != data[1]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[1] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[1] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[1] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[1] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x80) != 0)); + } + if (ctx->last_state[2] != data[2]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, ((data[2] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x08) != 0)); + } + + if (ctx->last_state[3] != data[3]) { + Uint8 hat; + + switch (data[3] & 0x0f) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + Sint16 axis = READ_STICK_AXIS(4); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(5); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(6); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(7); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + +#define READ_TRIGGER_AXIS(offset) \ + (Sint16) HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16) + { + Sint16 axis = READ_TRIGGER_AXIS(8); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = READ_TRIGGER_AXIS(9); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + } +#undef READ_TRIGGER_AXIS + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void HIDAPI_DriverLuna_HandleBluetoothStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size) +{ + Uint64 timestamp = SDL_GetTicksNS(); + + if (size >= 2 && data[0] == 0x02) { + // Home button has dedicated report + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x1) != 0)); + return; + } + + if (size >= 2 && data[0] == 0x04) { + // Battery level report + int percent = (int)SDL_roundf((data[1] / 255.0f) * 100.0f); + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent); + return; + } + + if (size < 17 || data[0] != 0x01) { + // We don't know how to handle this report + return; + } + + if (ctx->last_state[13] != data[13]) { + Uint8 hat; + + switch (data[13] & 0x0f) { + case 1: + hat = SDL_HAT_UP; + break; + case 2: + hat = SDL_HAT_RIGHTUP; + break; + case 3: + hat = SDL_HAT_RIGHT; + break; + case 4: + hat = SDL_HAT_RIGHTDOWN; + break; + case 5: + hat = SDL_HAT_DOWN; + break; + case 6: + hat = SDL_HAT_LEFTDOWN; + break; + case 7: + hat = SDL_HAT_LEFT; + break; + case 8: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + if (ctx->last_state[14] != data[14]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x80) != 0)); + } + if (ctx->last_state[15] != data[15]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[15] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x40) != 0)); + } + if (ctx->last_state[16] != data[16]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[16] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, ((data[16] & 0x02) != 0)); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + Sint16 axis = READ_STICK_AXIS(2); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(4); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(6); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(8); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + +#define READ_TRIGGER_AXIS(offset) \ + (Sint16) HIDAPI_RemapVal((float)((int)(((data[offset] | (data[offset + 1] << 8)) & 0x3ff) - 0x200)), 0x00 - 0x200, 0x3ff - 0x200, SDL_MIN_SINT16, SDL_MAX_SINT16) + { + Sint16 axis = READ_TRIGGER_AXIS(9); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = READ_TRIGGER_AXIS(11); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + } +#undef READ_TRIGGER_AXIS + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverLuna_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_LUNA_PROTOCOL + HIDAPI_DumpPacket("Amazon Luna packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + switch (size) { + case 10: + HIDAPI_DriverLuna_HandleUSBStatePacket(joystick, ctx, data, size); + break; + default: + HIDAPI_DriverLuna_HandleBluetoothStatePacket(joystick, ctx, data, size); + break; + } + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverLuna_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_DriverLuna_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna = { + SDL_HINT_JOYSTICK_HIDAPI_LUNA, + true, + HIDAPI_DriverLuna_RegisterHints, + HIDAPI_DriverLuna_UnregisterHints, + HIDAPI_DriverLuna_IsEnabled, + HIDAPI_DriverLuna_IsSupportedDevice, + HIDAPI_DriverLuna_InitDevice, + HIDAPI_DriverLuna_GetDevicePlayerIndex, + HIDAPI_DriverLuna_SetDevicePlayerIndex, + HIDAPI_DriverLuna_UpdateDevice, + HIDAPI_DriverLuna_OpenJoystick, + HIDAPI_DriverLuna_RumbleJoystick, + HIDAPI_DriverLuna_RumbleJoystickTriggers, + HIDAPI_DriverLuna_GetJoystickCapabilities, + HIDAPI_DriverLuna_SetJoystickLED, + HIDAPI_DriverLuna_SendJoystickEffect, + HIDAPI_DriverLuna_SetJoystickSensorsEnabled, + HIDAPI_DriverLuna_CloseJoystick, + HIDAPI_DriverLuna_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_LUNA + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_nintendo.h b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_nintendo.h new file mode 100644 index 0000000..0a5836f --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_nintendo.h @@ -0,0 +1,49 @@ +/* + 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. +*/ + +// These are values used in the controller type byte of the controller GUID + +// These values come directly out of the hardware, so don't change them +typedef enum +{ + k_eSwitchDeviceInfoControllerType_Unknown = 0, + k_eSwitchDeviceInfoControllerType_JoyConLeft = 1, + k_eSwitchDeviceInfoControllerType_JoyConRight = 2, + k_eSwitchDeviceInfoControllerType_ProController = 3, + k_eSwitchDeviceInfoControllerType_LicProController = 6, + k_eSwitchDeviceInfoControllerType_HVCLeft = 7, + k_eSwitchDeviceInfoControllerType_HVCRight = 8, + k_eSwitchDeviceInfoControllerType_NESLeft = 9, + k_eSwitchDeviceInfoControllerType_NESRight = 10, + k_eSwitchDeviceInfoControllerType_SNES = 11, + k_eSwitchDeviceInfoControllerType_N64 = 12, + k_eSwitchDeviceInfoControllerType_SEGA_Genesis = 13, +} ESwitchDeviceInfoControllerType; + +// These values are used internally but can be updated as needed +typedef enum +{ + k_eWiiExtensionControllerType_Unknown = 0, + k_eWiiExtensionControllerType_None = 128, + k_eWiiExtensionControllerType_Nunchuk = 129, + k_eWiiExtensionControllerType_Gamepad = 130, + k_eWiiExtensionControllerType_WiiUPro = 131, +} EWiiExtensionControllerType; diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps3.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps3.c new file mode 100644 index 0000000..6c82647 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps3.c @@ -0,0 +1,1446 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_PS3 + +// Define this if you want to log all packets from the controller +// #define DEBUG_PS3_PROTOCOL + +#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) + +typedef enum +{ + k_EPS3ReportIdState = 1, + k_EPS3ReportIdEffects = 1, +} EPS3ReportId; + +typedef enum +{ + k_EPS3SonySixaxisReportIdState = 0, + k_EPS3SonySixaxisReportIdEffects = 0, +} EPS3SonySixaxisReportId; + +// Commands for Sony's sixaxis.sys Windows driver +// All commands must be sent using 49-byte buffer containing output report +// Byte 0 indicates reportId and must always be 0 +// Byte 1 indicates a command, supported values are specified below: +typedef enum +{ + // This command allows to set user LEDs. + // Bytes 5,6.7.8 contain mode for corresponding LED: 0 - LED is off, 1 - LED in on, 2 - LED is flashing. + // Bytes 9-16 specify 64-bit LED flash period in 100 ns units if some LED is flashing, otherwise not used. + k_EPS3SixaxisCommandSetLEDs = 1, + + // This command allows to set left and right motors. + // Byte 5 is right motor duration (0-255) and byte 6, if not zero, activates right motor. Zero value disables right motor. + // Byte 7 is left motor duration (0-255) and byte 8 is left motor amplitude (0-255). + k_EPS3SixaxisCommandSetMotors = 2, + + // This command allows to block/unblock setting device LEDs by applications. + // Byte 5 is used as parameter - any non-zero value blocks LEDs, zero value will unblock LEDs. + k_EPS3SixaxisCommandBlockLEDs = 3, + + // This command refreshes driver settings. No parameters used. + // When sixaxis driver loads it reads 'CurrentDriverSetting' binary value from 'HKLM\System\CurrentControlSet\Services\sixaxis\Parameters' registry key. + // If the key is not present then default values are used. Sending this command forces sixaxis driver to re-read the registry and update driver settings. + k_EPS3SixaxisCommandRefreshDriverSetting = 9, + + // This command clears current bluetooth pairing. No parameters used. + k_EPS3SixaxisCommandClearPairing = 10 +} EPS3SixaxisDriverCommands; + +typedef struct +{ + SDL_HIDAPI_Device *device; + SDL_Joystick *joystick; + bool is_shanwan; + bool has_analog_buttons; + bool report_sensors; + bool effects_updated; + int player_index; + Uint8 rumble_left; + Uint8 rumble_right; + Uint8 last_state[USB_PACKET_LENGTH]; +} SDL_DriverPS3_Context; + +static bool HIDAPI_DriverPS3_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size); + +static void HIDAPI_DriverPS3_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3, callback, userdata); +} + +static void HIDAPI_DriverPS3_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3, callback, userdata); +} + +static bool HIDAPI_DriverPS3_IsEnabled(void) +{ + bool default_value; + +#ifdef SDL_PLATFORM_MACOS + // This works well on macOS + default_value = true; +#elif defined(SDL_PLATFORM_WIN32) + /* For official Sony driver (sixaxis.sys) use SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER. + * + * See https://github.com/ViGEm/DsHidMini as an alternative driver + */ + default_value = false; +#elif defined(SDL_PLATFORM_LINUX) + /* Linux drivers do a better job of managing the transition between + * USB and Bluetooth. There are also some quirks in communicating + * with PS3 controllers that have been implemented in SDL's hidapi + * for libusb, but are not possible to support using hidraw if the + * kernel doesn't already know about them. + */ + default_value = false; +#else + // Untested, default off + default_value = false; +#endif + + if (default_value) { + default_value = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT); + } + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3, default_value); +} + +static bool HIDAPI_DriverPS3_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_SONY && product_id == USB_PRODUCT_SONY_DS3) { + return true; + } + if (vendor_id == USB_VENDOR_SHANWAN && product_id == USB_PRODUCT_SHANWAN_DS3) { + return true; + } + return false; +} + +static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length) +{ + SDL_memset(report, 0, length); + report[0] = report_id; + return SDL_hid_get_feature_report(dev, report, length); +} + +static int SendFeatureReport(SDL_hid_device *dev, Uint8 *report, size_t length) +{ + return SDL_hid_send_feature_report(dev, report, length); +} + +static bool HIDAPI_DriverPS3_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS3_Context *ctx; + bool is_shanwan = false; + + if (device->vendor_id == USB_VENDOR_SONY && + SDL_strncasecmp(device->name, "ShanWan", 7) == 0) { + is_shanwan = true; + } + if (device->vendor_id == USB_VENDOR_SHANWAN || + device->vendor_id == USB_VENDOR_SHANWAN_ALT) { + is_shanwan = true; + } + + ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + ctx->is_shanwan = is_shanwan; + ctx->has_analog_buttons = true; + + device->context = ctx; + + // Set the controller into report mode over Bluetooth + if (device->is_bluetooth) { + Uint8 data[] = { 0xf4, 0x42, 0x03, 0x00, 0x00 }; + + SendFeatureReport(device->dev, data, sizeof(data)); + } + + // Set the controller into report mode over USB + if (!device->is_bluetooth) { + Uint8 data[USB_PACKET_LENGTH]; + + int size = ReadFeatureReport(device->dev, 0xf2, data, 17); + if (size < 0) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "HIDAPI_DriverPS3_InitDevice(): Couldn't read feature report 0xf2"); + return false; + } +#ifdef DEBUG_PS3_PROTOCOL + HIDAPI_DumpPacket("PS3 0xF2 packet: size = %d", data, size); +#endif + size = ReadFeatureReport(device->dev, 0xf5, data, 8); + if (size < 0) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "HIDAPI_DriverPS3_InitDevice(): Couldn't read feature report 0xf5"); + return false; + } +#ifdef DEBUG_PS3_PROTOCOL + HIDAPI_DumpPacket("PS3 0xF5 packet: size = %d", data, size); +#endif + if (!ctx->is_shanwan) { + // An output report could cause ShanWan controllers to rumble non-stop + SDL_hid_write(device->dev, data, 1); + } + } + + device->type = SDL_GAMEPAD_TYPE_PS3; + HIDAPI_SetDeviceName(device, "PS3 Controller"); + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverPS3_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static bool HIDAPI_DriverPS3_UpdateEffects(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + Uint8 effects[] = { + 0x01, 0xff, 0x00, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x27, 0x10, 0x00, 0x32, + 0xff, 0x27, 0x10, 0x00, 0x32, + 0xff, 0x27, 0x10, 0x00, 0x32, + 0xff, 0x27, 0x10, 0x00, 0x32, + 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + effects[2] = ctx->rumble_right ? 1 : 0; + effects[4] = ctx->rumble_left; + + effects[9] = (0x01 << (1 + (ctx->player_index % 4))); + + return HIDAPI_DriverPS3_SendJoystickEffect(device, ctx->joystick, effects, sizeof(effects)); +} + +static void HIDAPI_DriverPS3_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + if (!ctx) { + return; + } + + ctx->player_index = player_index; + + // This will set the new LED state based on the new player index + HIDAPI_DriverPS3_UpdateEffects(device); +} + +static bool HIDAPI_DriverPS3_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + ctx->joystick = joystick; + ctx->effects_updated = false; + ctx->rumble_left = 0; + ctx->rumble_right = 0; + SDL_zeroa(ctx->last_state); + + // Initialize player index (needed for setting LEDs) + ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); + + // Initialize the joystick capabilities + joystick->nbuttons = 11; + joystick->naxes = 6; + if (ctx->has_analog_buttons) { + joystick->naxes += 10; + } + joystick->nhats = 1; + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f); + + return true; +} + +static bool HIDAPI_DriverPS3_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + ctx->rumble_left = (low_frequency_rumble >> 8); + ctx->rumble_right = (high_frequency_rumble >> 8); + + return HIDAPI_DriverPS3_UpdateEffects(device); +} + +static bool HIDAPI_DriverPS3_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverPS3_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return SDL_JOYSTICK_CAP_RUMBLE; +} + +static bool HIDAPI_DriverPS3_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverPS3_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) +{ + Uint8 data[49]; + int report_size, offset; + + SDL_zeroa(data); + + data[0] = k_EPS3ReportIdEffects; + report_size = sizeof(data); + offset = 1; + SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size)); + + if (SDL_HIDAPI_SendRumble(device, data, report_size) != report_size) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static bool HIDAPI_DriverPS3_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + ctx->report_sensors = enabled; + + return true; +} + +static float HIDAPI_DriverPS3_ScaleAccel(Sint16 value) +{ + // Accelerometer values are in big endian order + value = SDL_Swap16BE(value); + return ((float)(value - 511) / 113.0f) * SDL_STANDARD_GRAVITY; +} + +static void HIDAPI_DriverPS3_HandleMiniStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[4] != data[4]) { + Uint8 hat; + + switch (data[4] & 0x0f) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[4] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[4] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[4] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[4] & 0x80) != 0)); + } + + if (ctx->last_state[5] != data[5]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[5] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[5] & 0x02) != 0)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[5] & 0x04) ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[5] & 0x08) ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[5] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[5] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[5] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[5] & 0x80) != 0)); + } + + axis = ((int)data[2] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = ((int)data[3] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = ((int)data[0] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = ((int)data[1] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void HIDAPI_DriverPS3_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[2] != data[2]) { + Uint8 hat = 0; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x08) != 0)); + + if (data[2] & 0x10) { + hat |= SDL_HAT_UP; + } + if (data[2] & 0x20) { + hat |= SDL_HAT_RIGHT; + } + if (data[2] & 0x40) { + hat |= SDL_HAT_DOWN; + } + if (data[2] & 0x80) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + if (ctx->last_state[3] != data[3]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x80) != 0)); + } + + if (ctx->last_state[4] != data[4]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[4] & 0x01) != 0)); + } + + axis = ((int)data[18] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = ((int)data[19] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + axis = ((int)data[6] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = ((int)data[7] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = ((int)data[8] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = ((int)data[9] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + + // Buttons are mapped as axes in the order they appear in the button enumeration + if (ctx->has_analog_buttons) { + static int button_axis_offsets[] = { + 24, // SDL_GAMEPAD_BUTTON_SOUTH + 23, // SDL_GAMEPAD_BUTTON_EAST + 25, // SDL_GAMEPAD_BUTTON_WEST + 22, // SDL_GAMEPAD_BUTTON_NORTH + 0, // SDL_GAMEPAD_BUTTON_BACK + 0, // SDL_GAMEPAD_BUTTON_GUIDE + 0, // SDL_GAMEPAD_BUTTON_START + 0, // SDL_GAMEPAD_BUTTON_LEFT_STICK + 0, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + 20, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + 21, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + 14, // SDL_GAMEPAD_BUTTON_DPAD_UP + 16, // SDL_GAMEPAD_BUTTON_DPAD_DOWN + 17, // SDL_GAMEPAD_BUTTON_DPAD_LEFT + 15, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT + }; + Uint8 i, axis_index = 6; + + for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) { + int offset = button_axis_offsets[i]; + if (!offset) { + // This button doesn't report as an axis + continue; + } + + axis = ((int)data[offset] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis); + ++axis_index; + } + } + + if (ctx->report_sensors) { + float sensor_data[3]; + + sensor_data[0] = HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[41], data[42])); + sensor_data[1] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[45], data[46])); + sensor_data[2] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[43], data[44])); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, sensor_data, SDL_arraysize(sensor_data)); + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverPS3_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_PS3_PROTOCOL + HIDAPI_DumpPacket("PS3 packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + if (size == 7) { + // Seen on a ShanWan PS2 -> PS3 USB converter + HIDAPI_DriverPS3_HandleMiniStatePacket(joystick, ctx, data, size); + + // Wait for the first report to set the LED state after the controller stops blinking + if (!ctx->effects_updated) { + HIDAPI_DriverPS3_UpdateEffects(device); + ctx->effects_updated = true; + } + continue; + } + + switch (data[0]) { + case k_EPS3ReportIdState: + if (data[1] == 0xFF) { + // Invalid data packet, ignore + break; + } + HIDAPI_DriverPS3_HandleStatePacket(joystick, ctx, data, size); + + // Wait for the first report to set the LED state after the controller stops blinking + if (!ctx->effects_updated) { + HIDAPI_DriverPS3_UpdateEffects(device); + ctx->effects_updated = true; + } + break; + default: +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown PS3 packet: 0x%.2x", data[0]); +#endif + break; + } + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverPS3_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + ctx->joystick = NULL; +} + +static void HIDAPI_DriverPS3_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3 = { + SDL_HINT_JOYSTICK_HIDAPI_PS3, + true, + HIDAPI_DriverPS3_RegisterHints, + HIDAPI_DriverPS3_UnregisterHints, + HIDAPI_DriverPS3_IsEnabled, + HIDAPI_DriverPS3_IsSupportedDevice, + HIDAPI_DriverPS3_InitDevice, + HIDAPI_DriverPS3_GetDevicePlayerIndex, + HIDAPI_DriverPS3_SetDevicePlayerIndex, + HIDAPI_DriverPS3_UpdateDevice, + HIDAPI_DriverPS3_OpenJoystick, + HIDAPI_DriverPS3_RumbleJoystick, + HIDAPI_DriverPS3_RumbleJoystickTriggers, + HIDAPI_DriverPS3_GetJoystickCapabilities, + HIDAPI_DriverPS3_SetJoystickLED, + HIDAPI_DriverPS3_SendJoystickEffect, + HIDAPI_DriverPS3_SetJoystickSensorsEnabled, + HIDAPI_DriverPS3_CloseJoystick, + HIDAPI_DriverPS3_FreeDevice, +}; + +static bool HIDAPI_DriverPS3ThirdParty_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, + SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverPS3ThirdParty_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + Uint8 data[USB_PACKET_LENGTH]; + int size; + + if (vendor_id == USB_VENDOR_LOGITECH && + product_id == USB_PRODUCT_LOGITECH_CHILLSTREAM) { + return true; + } + + if ((type == SDL_GAMEPAD_TYPE_PS3 && vendor_id != USB_VENDOR_SONY) || + HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) { + if (device && device->dev) { + size = ReadFeatureReport(device->dev, 0x03, data, sizeof(data)); + if (size == 8 && data[2] == 0x26) { + // Supported third party controller + return true; + } else { + return false; + } + } else { + // Might be supported by this driver, enumerate and find out + return true; + } + } + return false; +} + +static bool HIDAPI_DriverPS3ThirdParty_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS3_Context *ctx; + + ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + if (device->vendor_id == USB_VENDOR_SWITCH && device->product_id == USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER) { + ctx->has_analog_buttons = false; + } else { + ctx->has_analog_buttons = true; + } + + device->context = ctx; + + device->type = SDL_GAMEPAD_TYPE_PS3; + + if (device->vendor_id == USB_VENDOR_LOGITECH && + device->product_id == USB_PRODUCT_LOGITECH_CHILLSTREAM) { + HIDAPI_SetDeviceName(device, "Logitech ChillStream"); + } + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverPS3ThirdParty_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverPS3ThirdParty_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static bool HIDAPI_DriverPS3ThirdParty_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + ctx->joystick = joystick; + SDL_zeroa(ctx->last_state); + + // Initialize the joystick capabilities + joystick->nbuttons = 11; + joystick->naxes = 6; + if (ctx->has_analog_buttons) { + joystick->naxes += 10; + } + joystick->nhats = 1; + + if (device->vendor_id == USB_VENDOR_SWITCH && device->product_id == USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER) { + // This is a wireless controller using a USB dongle + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + } + + return true; +} + +static bool HIDAPI_DriverPS3ThirdParty_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverPS3ThirdParty_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverPS3ThirdParty_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return 0; +} + +static bool HIDAPI_DriverPS3ThirdParty_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverPS3ThirdParty_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverPS3ThirdParty_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket18(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[0] != data[0]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x20) != 0)); + } + + if (ctx->last_state[1] != data[1]) { + Uint8 hat; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x08) != 0)); + + switch (data[1] >> 4) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + axis = ((int)data[16] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = ((int)data[17] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + axis = ((int)data[2] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = ((int)data[3] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = ((int)data[4] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = ((int)data[5] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + + // Buttons are mapped as axes in the order they appear in the button enumeration + if (ctx->has_analog_buttons) { + static int button_axis_offsets[] = { + 12, // SDL_GAMEPAD_BUTTON_SOUTH + 11, // SDL_GAMEPAD_BUTTON_EAST + 13, // SDL_GAMEPAD_BUTTON_WEST + 10, // SDL_GAMEPAD_BUTTON_NORTH + 0, // SDL_GAMEPAD_BUTTON_BACK + 0, // SDL_GAMEPAD_BUTTON_GUIDE + 0, // SDL_GAMEPAD_BUTTON_START + 0, // SDL_GAMEPAD_BUTTON_LEFT_STICK + 0, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + 14, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + 15, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + 8, // SDL_GAMEPAD_BUTTON_DPAD_UP + 9, // SDL_GAMEPAD_BUTTON_DPAD_DOWN + 7, // SDL_GAMEPAD_BUTTON_DPAD_LEFT + 6, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT + }; + Uint8 i, axis_index = 6; + + for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) { + int offset = button_axis_offsets[i]; + if (!offset) { + // This button doesn't report as an axis + continue; + } + + axis = ((int)data[offset] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis); + ++axis_index; + } + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket19(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[0] != data[0]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x20) != 0)); + } + + if (ctx->last_state[1] != data[1]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0)); + } + + if (ctx->device->vendor_id == USB_VENDOR_SAITEK && ctx->device->product_id == USB_PRODUCT_SAITEK_CYBORG_V3) { + // Cyborg V.3 Rumble Pad doesn't set the dpad bits as expected, so use the axes instead + Uint8 hat = 0; + + if (data[7]) { + hat |= SDL_HAT_RIGHT; + } + if (data[8]) { + hat |= SDL_HAT_LEFT; + } + if (data[9]) { + hat |= SDL_HAT_UP; + } + if (data[10]) { + hat |= SDL_HAT_DOWN; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } else { + if (ctx->last_state[2] != data[2]) { + Uint8 hat; + + switch (data[2] & 0x0f) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + } + + if (data[0] & 0x40) { + axis = 32767; + } else { + axis = ((int)data[17] * 257) - 32768; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + if (data[0] & 0x80) { + axis = 32767; + } else { + axis = ((int)data[18] * 257) - 32768; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + axis = ((int)data[3] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = ((int)data[4] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = ((int)data[5] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = ((int)data[6] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + + // Buttons are mapped as axes in the order they appear in the button enumeration + if (ctx->has_analog_buttons) { + static int button_axis_offsets[] = { + 13, // SDL_GAMEPAD_BUTTON_SOUTH + 12, // SDL_GAMEPAD_BUTTON_EAST + 14, // SDL_GAMEPAD_BUTTON_WEST + 11, // SDL_GAMEPAD_BUTTON_NORTH + 0, // SDL_GAMEPAD_BUTTON_BACK + 0, // SDL_GAMEPAD_BUTTON_GUIDE + 0, // SDL_GAMEPAD_BUTTON_START + 0, // SDL_GAMEPAD_BUTTON_LEFT_STICK + 0, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + 15, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + 16, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + 9, // SDL_GAMEPAD_BUTTON_DPAD_UP + 10, // SDL_GAMEPAD_BUTTON_DPAD_DOWN + 8, // SDL_GAMEPAD_BUTTON_DPAD_LEFT + 7, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT + }; + Uint8 i, axis_index = 6; + + for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) { + int offset = button_axis_offsets[i]; + if (!offset) { + // This button doesn't report as an axis + continue; + } + + axis = ((int)data[offset] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis); + ++axis_index; + } + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverPS3ThirdParty_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_PS3_PROTOCOL + HIDAPI_DumpPacket("PS3 packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + if (size >= 19) { + HIDAPI_DriverPS3ThirdParty_HandleStatePacket19(joystick, ctx, data, size); + } else if (size == 18) { + // This packet format was seen with the Logitech ChillStream + HIDAPI_DriverPS3ThirdParty_HandleStatePacket18(joystick, ctx, data, size); + } else { +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown PS3 packet, size %d", size); +#endif + } + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverPS3ThirdParty_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + ctx->joystick = NULL; +} + +static void HIDAPI_DriverPS3ThirdParty_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3ThirdParty = { + SDL_HINT_JOYSTICK_HIDAPI_PS3, + true, + HIDAPI_DriverPS3_RegisterHints, + HIDAPI_DriverPS3_UnregisterHints, + HIDAPI_DriverPS3ThirdParty_IsEnabled, + HIDAPI_DriverPS3ThirdParty_IsSupportedDevice, + HIDAPI_DriverPS3ThirdParty_InitDevice, + HIDAPI_DriverPS3ThirdParty_GetDevicePlayerIndex, + HIDAPI_DriverPS3ThirdParty_SetDevicePlayerIndex, + HIDAPI_DriverPS3ThirdParty_UpdateDevice, + HIDAPI_DriverPS3ThirdParty_OpenJoystick, + HIDAPI_DriverPS3ThirdParty_RumbleJoystick, + HIDAPI_DriverPS3ThirdParty_RumbleJoystickTriggers, + HIDAPI_DriverPS3ThirdParty_GetJoystickCapabilities, + HIDAPI_DriverPS3ThirdParty_SetJoystickLED, + HIDAPI_DriverPS3ThirdParty_SendJoystickEffect, + HIDAPI_DriverPS3ThirdParty_SetJoystickSensorsEnabled, + HIDAPI_DriverPS3ThirdParty_CloseJoystick, + HIDAPI_DriverPS3ThirdParty_FreeDevice, +}; + +static bool HIDAPI_DriverPS3_UpdateRumbleSonySixaxis(SDL_HIDAPI_Device *device); +static bool HIDAPI_DriverPS3_UpdateLEDsSonySixaxis(SDL_HIDAPI_Device *device); + +static void HIDAPI_DriverPS3SonySixaxis_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER, callback, userdata); +} + +static void HIDAPI_DriverPS3SonySixaxis_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER, callback, userdata); +} + +static bool HIDAPI_DriverPS3SonySixaxis_IsEnabled(void) +{ +#ifdef SDL_PLATFORM_WIN32 + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER, false); +#else + return false; +#endif +} + +static bool HIDAPI_DriverPS3SonySixaxis_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_SONY && product_id == USB_PRODUCT_SONY_DS3) { + return true; + } + return false; +} + +static bool HIDAPI_DriverPS3SonySixaxis_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS3_Context *ctx; + + ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + ctx->has_analog_buttons = true; + + device->context = ctx; + + Uint8 data[USB_PACKET_LENGTH]; + + int size = ReadFeatureReport(device->dev, 0xf2, data, sizeof(data)); + if (size < 0) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "HIDAPI_DriverPS3SonySixaxis_InitDevice(): Couldn't read feature report 0xf2. Trying again with 0x0."); + SDL_zeroa(data); + size = ReadFeatureReport(device->dev, 0x00, data, sizeof(data)); + if (size < 0) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "HIDAPI_DriverPS3SonySixaxis_InitDevice(): Couldn't read feature report 0x00."); + return false; + } +#ifdef DEBUG_PS3_PROTOCOL + HIDAPI_DumpPacket("PS3 0x0 packet: size = %d", data, size); +#endif + } +#ifdef DEBUG_PS3_PROTOCOL + HIDAPI_DumpPacket("PS3 0xF2 packet: size = %d", data, size); +#endif + + device->type = SDL_GAMEPAD_TYPE_PS3; + HIDAPI_SetDeviceName(device, "PS3 Controller"); + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverPS3SonySixaxis_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverPS3SonySixaxis_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + if (!ctx) { + return; + } + + ctx->player_index = player_index; + + // This will set the new LED state based on the new player index + HIDAPI_DriverPS3_UpdateLEDsSonySixaxis(device); +} + +static bool HIDAPI_DriverPS3SonySixaxis_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + ctx->joystick = joystick; + ctx->effects_updated = false; + ctx->rumble_left = 0; + ctx->rumble_right = 0; + SDL_zeroa(ctx->last_state); + + // Initialize player index (needed for setting LEDs) + ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); + + // Initialize the joystick capabilities + joystick->nbuttons = 11; + joystick->naxes = 6; + if (ctx->has_analog_buttons) { + joystick->naxes += 10; + } + joystick->nhats = 1; + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f); + + return true; +} + +static bool HIDAPI_DriverPS3SonySixaxis_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + ctx->rumble_left = (low_frequency_rumble >> 8); + ctx->rumble_right = (high_frequency_rumble >> 8); + + return HIDAPI_DriverPS3_UpdateRumbleSonySixaxis(device); +} + +static bool HIDAPI_DriverPS3SonySixaxis_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverPS3SonySixaxis_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return 0; +} + +static bool HIDAPI_DriverPS3SonySixaxis_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverPS3SonySixaxis_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) +{ + Uint8 data[49]; + int report_size; + + SDL_zeroa(data); + + data[0] = k_EPS3SonySixaxisReportIdEffects; + report_size = sizeof(data); + + // No offset with Sony sixaxis.sys driver + SDL_memcpy(&data, effect, SDL_min(sizeof(data), (size_t)size)); + + if (SDL_HIDAPI_SendRumble(device, data, report_size) != report_size) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static bool HIDAPI_DriverPS3SonySixaxis_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + ctx->report_sensors = enabled; + + return true; +} + +static void HIDAPI_DriverPS3SonySixaxis_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[2] != data[2]) { + Uint8 hat = 0; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x08) != 0)); + + if (data[2] & 0x10) { + hat |= SDL_HAT_UP; + } + if (data[2] & 0x20) { + hat |= SDL_HAT_RIGHT; + } + if (data[2] & 0x40) { + hat |= SDL_HAT_DOWN; + } + if (data[2] & 0x80) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + if (ctx->last_state[3] != data[3]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x80) != 0)); + } + + if (ctx->last_state[4] != data[4]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[4] & 0x01) != 0)); + } + + axis = ((int)data[18] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = ((int)data[19] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + axis = ((int)data[6] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = ((int)data[7] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = ((int)data[8] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = ((int)data[9] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + + // Buttons are mapped as axes in the order they appear in the button enumeration + if (ctx->has_analog_buttons) { + static int button_axis_offsets[] = { + 24, // SDL_GAMEPAD_BUTTON_SOUTH + 23, // SDL_GAMEPAD_BUTTON_EAST + 25, // SDL_GAMEPAD_BUTTON_WEST + 22, // SDL_GAMEPAD_BUTTON_NORTH + 0, // SDL_GAMEPAD_BUTTON_BACK + 0, // SDL_GAMEPAD_BUTTON_GUIDE + 0, // SDL_GAMEPAD_BUTTON_START + 0, // SDL_GAMEPAD_BUTTON_LEFT_STICK + 0, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + 20, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + 21, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + 14, // SDL_GAMEPAD_BUTTON_DPAD_UP + 16, // SDL_GAMEPAD_BUTTON_DPAD_DOWN + 17, // SDL_GAMEPAD_BUTTON_DPAD_LEFT + 15, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT + }; + Uint8 i, axis_index = 6; + + for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) { + int offset = button_axis_offsets[i]; + if (!offset) { + // This button doesn't report as an axis + continue; + } + + axis = ((int)data[offset] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis); + ++axis_index; + } + } + + if (ctx->report_sensors) { + float sensor_data[3]; + + sensor_data[0] = HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[41], data[42])); + sensor_data[1] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[45], data[46])); + sensor_data[2] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[43], data[44])); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, sensor_data, SDL_arraysize(sensor_data)); + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverPS3SonySixaxis_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + if (!joystick) { + return false; + } + + // With sixaxis.sys driver we need to use hid_get_feature_report instead of hid_read + size = ReadFeatureReport(device->dev, 0x0, data, sizeof(data)); + if (size < 0) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "HIDAPI_DriverPS3SonySixaxis_UpdateDevice(): Couldn't read feature report 0x00"); + return false; + } + + switch (data[0]) { + case k_EPS3SonySixaxisReportIdState: + HIDAPI_DriverPS3SonySixaxis_HandleStatePacket(joystick, ctx, &data[1], size - 1); // report data starts in data[1] + + // Wait for the first report to set the LED state after the controller stops blinking + if (!ctx->effects_updated) { + HIDAPI_DriverPS3_UpdateLEDsSonySixaxis(device); + ctx->effects_updated = true; + } + + break; + default: +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown PS3 packet: 0x%.2x", data[0]); +#endif + break; + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverPS3SonySixaxis_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + ctx->joystick = NULL; +} + +static void HIDAPI_DriverPS3SonySixaxis_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +static bool HIDAPI_DriverPS3_UpdateRumbleSonySixaxis(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + Uint8 effects[] = { + 0x0, // Report Id + k_EPS3SixaxisCommandSetMotors, // 2 = Set Motors + 0x00, 0x00, 0x00, // padding + 0xff, // Small Motor duration - 0xff is forever + 0x00, // Small Motor off/on (0 or 1) + 0xff, // Large Motor duration - 0xff is forever + 0x00 // Large Motor force (0 to 255) + }; + + effects[6] = ctx->rumble_right ? 1 : 0; // Small motor + effects[8] = ctx->rumble_left; // Large motor + + return HIDAPI_DriverPS3SonySixaxis_SendJoystickEffect(device, ctx->joystick, effects, sizeof(effects)); +} + +static bool HIDAPI_DriverPS3_UpdateLEDsSonySixaxis(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; + + Uint8 effects[] = { + 0x0, // Report Id + k_EPS3SixaxisCommandSetLEDs, // 1 = Set LEDs + 0x00, 0x00, 0x00, // padding + 0x00, 0x00, 0x00, 0x00 // LED #4, LED #3, LED #2, LED #1 (0 = Off, 1 = On, 2 = Flashing) + }; + + // Turn on LED light on DS3 Controller for relevant player (player_index 0 lights up LED #1, player_index 1 lights up LED #2, etc) + if (ctx->player_index < 4) { + effects[8 - ctx->player_index] = 1; + } + + return HIDAPI_DriverPS3SonySixaxis_SendJoystickEffect(device, ctx->joystick, effects, sizeof(effects)); +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3SonySixaxis = { + SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER, + true, + HIDAPI_DriverPS3SonySixaxis_RegisterHints, + HIDAPI_DriverPS3SonySixaxis_UnregisterHints, + HIDAPI_DriverPS3SonySixaxis_IsEnabled, + HIDAPI_DriverPS3SonySixaxis_IsSupportedDevice, + HIDAPI_DriverPS3SonySixaxis_InitDevice, + HIDAPI_DriverPS3SonySixaxis_GetDevicePlayerIndex, + HIDAPI_DriverPS3SonySixaxis_SetDevicePlayerIndex, + HIDAPI_DriverPS3SonySixaxis_UpdateDevice, + HIDAPI_DriverPS3SonySixaxis_OpenJoystick, + HIDAPI_DriverPS3SonySixaxis_RumbleJoystick, + HIDAPI_DriverPS3SonySixaxis_RumbleJoystickTriggers, + HIDAPI_DriverPS3SonySixaxis_GetJoystickCapabilities, + HIDAPI_DriverPS3SonySixaxis_SetJoystickLED, + HIDAPI_DriverPS3SonySixaxis_SendJoystickEffect, + HIDAPI_DriverPS3SonySixaxis_SetJoystickSensorsEnabled, + HIDAPI_DriverPS3SonySixaxis_CloseJoystick, + HIDAPI_DriverPS3SonySixaxis_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_PS3 + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps4.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps4.c new file mode 100644 index 0000000..7404bf2 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -0,0 +1,1390 @@ +/* + 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. +*/ +/* This driver supports both simplified reports and the extended input reports enabled by Steam. + Code and logic contributed by Valve Corporation under the SDL zlib license. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_PS4 + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_PS4_PROTOCOL +#endif + +// Define this if you want to log calibration data +#if 0 +#define DEBUG_PS4_CALIBRATION +#endif + +#define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500 + +#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) +#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ + (((Uint32)(B)) << 8) | \ + (((Uint32)(C)) << 16) | \ + (((Uint32)(D)) << 24)) + +enum +{ + SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD = 11 +}; + +typedef enum +{ + k_EPS4ReportIdUsbState = 1, + k_EPS4ReportIdUsbEffects = 5, + k_EPS4ReportIdBluetoothState1 = 17, + k_EPS4ReportIdBluetoothState2 = 18, + k_EPS4ReportIdBluetoothState3 = 19, + k_EPS4ReportIdBluetoothState4 = 20, + k_EPS4ReportIdBluetoothState5 = 21, + k_EPS4ReportIdBluetoothState6 = 22, + k_EPS4ReportIdBluetoothState7 = 23, + k_EPS4ReportIdBluetoothState8 = 24, + k_EPS4ReportIdBluetoothState9 = 25, + k_EPS4ReportIdBluetoothEffects = 17, + k_EPS4ReportIdDisconnectMessage = 226, +} EPS4ReportId; + +typedef enum +{ + k_ePS4FeatureReportIdGyroCalibration_USB = 0x02, + k_ePS4FeatureReportIdCapabilities = 0x03, + k_ePS4FeatureReportIdGyroCalibration_BT = 0x05, + k_ePS4FeatureReportIdSerialNumber = 0x12, +} EPS4FeatureReportID; + +typedef struct +{ + Uint8 ucLeftJoystickX; + Uint8 ucLeftJoystickY; + Uint8 ucRightJoystickX; + Uint8 ucRightJoystickY; + Uint8 rgucButtonsHatAndCounter[3]; + Uint8 ucTriggerLeft; + Uint8 ucTriggerRight; + Uint8 rgucTimestamp[2]; + Uint8 _rgucPad0[1]; + Uint8 rgucGyroX[2]; + Uint8 rgucGyroY[2]; + Uint8 rgucGyroZ[2]; + Uint8 rgucAccelX[2]; + Uint8 rgucAccelY[2]; + Uint8 rgucAccelZ[2]; + Uint8 _rgucPad1[5]; + Uint8 ucBatteryLevel; + Uint8 _rgucPad2[4]; + Uint8 ucTouchpadCounter1; + Uint8 rgucTouchpadData1[3]; + Uint8 ucTouchpadCounter2; + Uint8 rgucTouchpadData2[3]; +} PS4StatePacket_t; + +typedef struct +{ + Uint8 ucRumbleRight; + Uint8 ucRumbleLeft; + Uint8 ucLedRed; + Uint8 ucLedGreen; + Uint8 ucLedBlue; + Uint8 ucLedDelayOn; + Uint8 ucLedDelayOff; + Uint8 _rgucPad0[8]; + Uint8 ucVolumeLeft; + Uint8 ucVolumeRight; + Uint8 ucVolumeMic; + Uint8 ucVolumeSpeaker; +} DS4EffectsState_t; + +typedef struct +{ + Sint16 bias; + float scale; +} IMUCalibrationData; + +/* Rumble hint mode: + * "0": enhanced features are never used + * "1": enhanced features are always used + * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it. + */ +typedef enum +{ + PS4_ENHANCED_REPORT_HINT_OFF, + PS4_ENHANCED_REPORT_HINT_ON, + PS4_ENHANCED_REPORT_HINT_AUTO +} HIDAPI_PS4_EnhancedReportHint; + +typedef struct +{ + SDL_HIDAPI_Device *device; + SDL_Joystick *joystick; + bool is_dongle; + bool is_nacon_dongle; + bool official_controller; + bool sensors_supported; + bool lightbar_supported; + bool vibration_supported; + bool touchpad_supported; + bool effects_supported; + HIDAPI_PS4_EnhancedReportHint enhanced_report_hint; + bool enhanced_reports; + bool enhanced_mode; + bool enhanced_mode_available; + Uint8 report_interval; + bool report_sensors; + bool report_touchpad; + bool report_battery; + bool hardware_calibration; + IMUCalibrationData calibration[6]; + Uint64 last_packet; + int player_index; + Uint8 rumble_left; + Uint8 rumble_right; + bool color_set; + Uint8 led_red; + Uint8 led_green; + Uint8 led_blue; + Uint16 gyro_numerator; + Uint16 gyro_denominator; + Uint16 accel_numerator; + Uint16 accel_denominator; + Uint64 sensor_ticks; + Uint16 last_tick; + Uint16 valid_crc_packets; // wrapping counter + PS4StatePacket_t last_state; +} SDL_DriverPS4_Context; + +static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage); + +static void HIDAPI_DriverPS4_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4, callback, userdata); +} + +static void HIDAPI_DriverPS4_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4, callback, userdata); +} + +static bool HIDAPI_DriverPS4_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS4, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length) +{ + SDL_memset(report, 0, length); + report[0] = report_id; + return SDL_hid_get_feature_report(dev, report, length); +} + +static bool HIDAPI_DriverPS4_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + Uint8 data[USB_PACKET_LENGTH]; + int size; + + if (type == SDL_GAMEPAD_TYPE_PS4) { + return true; + } + + if (HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) { + if (device && device->dev) { + size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data)); + if (size == 48 && data[2] == 0x27) { + // Supported third party controller + return true; + } else { + return false; + } + } else { + // Might be supported by this driver, enumerate and find out + return true; + } + } + + return false; +} + +static void SetLedsForPlayerIndex(DS4EffectsState_t *effects, int player_index) +{ + /* This list is the same as what hid-sony.c uses in the Linux kernel. + The first 4 values correspond to what the PS4 assigns. + */ + static const Uint8 colors[7][3] = { + { 0x00, 0x00, 0x40 }, // Blue + { 0x40, 0x00, 0x00 }, // Red + { 0x00, 0x40, 0x00 }, // Green + { 0x20, 0x00, 0x20 }, // Pink + { 0x02, 0x01, 0x00 }, // Orange + { 0x00, 0x01, 0x01 }, // Teal + { 0x01, 0x01, 0x01 } // White + }; + + if (player_index >= 0) { + player_index %= SDL_arraysize(colors); + } else { + player_index = 0; + } + + effects->ucLedRed = colors[player_index][0]; + effects->ucLedGreen = colors[player_index][1]; + effects->ucLedBlue = colors[player_index][2]; +} + +static bool ReadWiredSerial(SDL_HIDAPI_Device *device, char *serial, size_t serial_size) +{ + Uint8 data[USB_PACKET_LENGTH]; + int size; + + size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data)); + if (size >= 7 && (data[1] || data[2] || data[3] || data[4] || data[5] || data[6])) { + (void)SDL_snprintf(serial, serial_size, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + data[6], data[5], data[4], data[3], data[2], data[1]); + return true; + } + return false; +} + +static bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS4_Context *ctx; + Uint8 data[USB_PACKET_LENGTH]; + int size; + char serial[18]; + SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; + + ctx = (SDL_DriverPS4_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + + ctx->gyro_numerator = 1; + ctx->gyro_denominator = 16; + ctx->accel_numerator = 1; + ctx->accel_denominator = 8192; + + device->context = ctx; + + if (device->serial && SDL_strlen(device->serial) == 12) { + int i, j; + + j = -1; + for (i = 0; i < 12; i += 2) { + j += 1; + SDL_memmove(&serial[j], &device->serial[i], 2); + j += 2; + serial[j] = '-'; + } + serial[j] = '\0'; + } else { + serial[0] = '\0'; + } + + // Check for type of connection + ctx->is_dongle = (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_DONGLE); + if (ctx->is_dongle) { + ReadWiredSerial(device, serial, sizeof(serial)); + ctx->enhanced_reports = true; + } else if (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) { + ctx->enhanced_reports = true; + + } else if (device->vendor_id == USB_VENDOR_SONY) { + if (device->is_bluetooth) { + // Read a report to see if we're in enhanced mode + size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16); +#ifdef DEBUG_PS4_PROTOCOL + if (size > 0) { + HIDAPI_DumpPacket("PS4 first packet: size = %d", data, size); + } else { + SDL_Log("PS4 first packet: size = %d", size); + } +#endif + if (size > 0 && + data[0] >= k_EPS4ReportIdBluetoothState1 && + data[0] <= k_EPS4ReportIdBluetoothState9) { + ctx->enhanced_reports = true; + } + } else { + ReadWiredSerial(device, serial, sizeof(serial)); + ctx->enhanced_reports = true; + } + } else { + // Third party controllers appear to all be wired + ctx->enhanced_reports = true; + } + + if (device->vendor_id == USB_VENDOR_SONY) { + ctx->official_controller = true; + ctx->sensors_supported = true; + ctx->lightbar_supported = true; + ctx->vibration_supported = true; + ctx->touchpad_supported = true; + } else { + // Third party controller capability request + size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data)); + // Get the device capabilities + if (size == 48 && data[2] == 0x27) { + Uint8 capabilities = data[4]; + Uint8 device_type = data[5]; + Uint16 gyro_numerator = LOAD16(data[10], data[11]); + Uint16 gyro_denominator = LOAD16(data[12], data[13]); + Uint16 accel_numerator = LOAD16(data[14], data[15]); + Uint16 accel_denominator = LOAD16(data[16], data[17]); + +#ifdef DEBUG_PS4_PROTOCOL + HIDAPI_DumpPacket("PS4 capabilities: size = %d", data, size); +#endif + if (capabilities & 0x02) { + ctx->sensors_supported = true; + } + if (capabilities & 0x04) { + ctx->lightbar_supported = true; + } + if (capabilities & 0x08) { + ctx->vibration_supported = true; + } + if (capabilities & 0x40) { + ctx->touchpad_supported = true; + } + + switch (device_type) { + case 0x00: + joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; + break; + case 0x01: + joystick_type = SDL_JOYSTICK_TYPE_GUITAR; + break; + case 0x02: + joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT; + break; + case 0x04: + joystick_type = SDL_JOYSTICK_TYPE_DANCE_PAD; + break; + case 0x06: + joystick_type = SDL_JOYSTICK_TYPE_WHEEL; + break; + case 0x07: + joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK; + break; + case 0x08: + joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK; + break; + default: + joystick_type = SDL_JOYSTICK_TYPE_UNKNOWN; + break; + } + + if (gyro_numerator && gyro_denominator) { + ctx->gyro_numerator = gyro_numerator; + ctx->gyro_denominator = gyro_denominator; + } + if (accel_numerator && accel_denominator) { + ctx->accel_numerator = accel_numerator; + ctx->accel_denominator = accel_denominator; + } + } else if (device->vendor_id == USB_VENDOR_RAZER) { + // The Razer Raiju doesn't respond to the detection protocol, but has a touchpad and vibration + ctx->vibration_supported = true; + ctx->touchpad_supported = true; + } + } + ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported); + + if (device->vendor_id == USB_VENDOR_NACON_ALT && + device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRELESS) { + ctx->is_nacon_dongle = true; + } + + if (device->vendor_id == USB_VENDOR_PDP && + (device->product_id == USB_PRODUCT_VICTRIX_FS_PRO || + device->product_id == USB_PRODUCT_VICTRIX_FS_PRO_V2)) { + /* The Victrix FS Pro V2 reports that it has lightbar support, + * but it doesn't respond to the effects packet, and will hang + * on reboot if we send it. + */ + ctx->effects_supported = false; + } + + device->joystick_type = joystick_type; + device->type = SDL_GAMEPAD_TYPE_PS4; + if (ctx->official_controller) { + HIDAPI_SetDeviceName(device, "PS4 Controller"); + } + HIDAPI_SetDeviceSerial(device, serial); + + // Prefer the USB device over the Bluetooth device + if (device->is_bluetooth) { + if (HIDAPI_HasConnectedUSBDevice(device->serial)) { + return true; + } + } else { + HIDAPI_DisconnectBluetoothDevice(device->serial); + } + if ((ctx->is_dongle || ctx->is_nacon_dongle) && serial[0] == '\0') { + // Not yet connected + return true; + } + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverPS4_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static bool HIDAPI_DriverPS4_LoadOfficialCalibrationData(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + int i, tries, size; + bool have_data = false; + Uint8 data[USB_PACKET_LENGTH]; + + if (!ctx->official_controller) { +#ifdef DEBUG_PS4_CALIBRATION + SDL_Log("Not an official controller, ignoring calibration"); +#endif + return false; + } + + for (tries = 0; tries < 5; ++tries) { + // For Bluetooth controllers, this report switches them into advanced report mode + size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdGyroCalibration_USB, data, sizeof(data)); + if (size < 35) { +#ifdef DEBUG_PS4_CALIBRATION + SDL_Log("Short read of calibration data: %d, ignoring calibration", size); +#endif + return false; + } + + if (device->is_bluetooth) { + size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdGyroCalibration_BT, data, sizeof(data)); + if (size < 35) { +#ifdef DEBUG_PS4_CALIBRATION + SDL_Log("Short read of calibration data: %d, ignoring calibration", size); +#endif + return false; + } + } + + // In some cases this report returns all zeros. Usually immediately after connection with the PS4 Dongle + for (i = 0; i < size; ++i) { + if (data[i]) { + have_data = true; + break; + } + } + if (have_data) { + break; + } + + SDL_Delay(2); + } + + if (have_data) { + Sint16 sGyroPitchBias, sGyroYawBias, sGyroRollBias; + Sint16 sGyroPitchPlus, sGyroPitchMinus; + Sint16 sGyroYawPlus, sGyroYawMinus; + Sint16 sGyroRollPlus, sGyroRollMinus; + Sint16 sGyroSpeedPlus, sGyroSpeedMinus; + + Sint16 sAccXPlus, sAccXMinus; + Sint16 sAccYPlus, sAccYMinus; + Sint16 sAccZPlus, sAccZMinus; + + float flNumerator; + float flDenominator; + Sint16 sRange2g; + +#ifdef DEBUG_PS4_CALIBRATION + HIDAPI_DumpPacket("PS4 calibration packet: size = %d", data, size); +#endif + + sGyroPitchBias = LOAD16(data[1], data[2]); + sGyroYawBias = LOAD16(data[3], data[4]); + sGyroRollBias = LOAD16(data[5], data[6]); + + if (device->is_bluetooth || ctx->is_dongle) { + sGyroPitchPlus = LOAD16(data[7], data[8]); + sGyroYawPlus = LOAD16(data[9], data[10]); + sGyroRollPlus = LOAD16(data[11], data[12]); + sGyroPitchMinus = LOAD16(data[13], data[14]); + sGyroYawMinus = LOAD16(data[15], data[16]); + sGyroRollMinus = LOAD16(data[17], data[18]); + } else { + sGyroPitchPlus = LOAD16(data[7], data[8]); + sGyroPitchMinus = LOAD16(data[9], data[10]); + sGyroYawPlus = LOAD16(data[11], data[12]); + sGyroYawMinus = LOAD16(data[13], data[14]); + sGyroRollPlus = LOAD16(data[15], data[16]); + sGyroRollMinus = LOAD16(data[17], data[18]); + } + + sGyroSpeedPlus = LOAD16(data[19], data[20]); + sGyroSpeedMinus = LOAD16(data[21], data[22]); + + sAccXPlus = LOAD16(data[23], data[24]); + sAccXMinus = LOAD16(data[25], data[26]); + sAccYPlus = LOAD16(data[27], data[28]); + sAccYMinus = LOAD16(data[29], data[30]); + sAccZPlus = LOAD16(data[31], data[32]); + sAccZMinus = LOAD16(data[33], data[34]); + + flNumerator = (float)(sGyroSpeedPlus + sGyroSpeedMinus) * ctx->gyro_denominator / ctx->gyro_numerator; + flDenominator = (float)(SDL_abs(sGyroPitchPlus - sGyroPitchBias) + SDL_abs(sGyroPitchMinus - sGyroPitchBias)); + if (flDenominator != 0.0f) { + ctx->calibration[0].bias = sGyroPitchBias; + ctx->calibration[0].scale = flNumerator / flDenominator; + } + + flDenominator = (float)(SDL_abs(sGyroYawPlus - sGyroYawBias) + SDL_abs(sGyroYawMinus - sGyroYawBias)); + if (flDenominator != 0.0f) { + ctx->calibration[1].bias = sGyroYawBias; + ctx->calibration[1].scale = flNumerator / flDenominator; + } + + flDenominator = (float)(SDL_abs(sGyroRollPlus - sGyroRollBias) + SDL_abs(sGyroRollMinus - sGyroRollBias)); + if (flDenominator != 0.0f) { + ctx->calibration[2].bias = sGyroRollBias; + ctx->calibration[2].scale = flNumerator / flDenominator; + } + + sRange2g = sAccXPlus - sAccXMinus; + ctx->calibration[3].bias = sAccXPlus - sRange2g / 2; + ctx->calibration[3].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g; + + sRange2g = sAccYPlus - sAccYMinus; + ctx->calibration[4].bias = sAccYPlus - sRange2g / 2; + ctx->calibration[4].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g; + + sRange2g = sAccZPlus - sAccZMinus; + ctx->calibration[5].bias = sAccZPlus - sRange2g / 2; + ctx->calibration[5].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g; + + ctx->hardware_calibration = true; + for (i = 0; i < 6; ++i) { +#ifdef DEBUG_PS4_CALIBRATION + SDL_Log("calibration[%d] bias = %d, sensitivity = %f", i, ctx->calibration[i].bias, ctx->calibration[i].scale); +#endif + // Some controllers have a bad calibration + if (SDL_abs(ctx->calibration[i].bias) > 1024 || SDL_fabsf(1.0f - ctx->calibration[i].scale) > 0.5f) { +#ifdef DEBUG_PS4_CALIBRATION + SDL_Log("invalid calibration, ignoring"); +#endif + ctx->hardware_calibration = false; + } + } + } else { +#ifdef DEBUG_PS4_CALIBRATION + SDL_Log("Calibration data not available"); +#endif + } + return ctx->hardware_calibration; +} + +static void HIDAPI_DriverPS4_LoadCalibrationData(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + int i; + + if (!HIDAPI_DriverPS4_LoadOfficialCalibrationData(device)) { + for (i = 0; i < SDL_arraysize(ctx->calibration); ++i) { + ctx->calibration[i].bias = 0; + ctx->calibration[i].scale = 1.0f; + } + } + + // Scale the raw data to the units expected by SDL + for (i = 0; i < SDL_arraysize(ctx->calibration); ++i) { + double scale = ctx->calibration[i].scale; + + if (i < 3) { + scale *= ((double)ctx->gyro_numerator / ctx->gyro_denominator) * SDL_PI_D / 180.0; + + if (device->vendor_id == USB_VENDOR_SONY && + device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) { + // The Armor-X Pro seems to only deliver half the rotation it should + scale *= 2.0; + } + } else { + scale *= ((double)ctx->accel_numerator / ctx->accel_denominator) * SDL_STANDARD_GRAVITY; + + if (device->vendor_id == USB_VENDOR_SONY && + device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) { + /* The Armor-X Pro seems to only deliver half the acceleration it should, + * and in the opposite direction on all axes */ + scale *= -2.0; + } + } + ctx->calibration[i].scale = (float)scale; + } +} + +static float HIDAPI_DriverPS4_ApplyCalibrationData(SDL_DriverPS4_Context *ctx, int index, Sint16 value) +{ + IMUCalibrationData *calibration = &ctx->calibration[index]; + + return ((float)value - calibration->bias) * calibration->scale; +} + +static bool HIDAPI_DriverPS4_UpdateEffects(SDL_DriverPS4_Context *ctx, bool application_usage) +{ + DS4EffectsState_t effects; + + SDL_zero(effects); + + if (ctx->vibration_supported) { + effects.ucRumbleLeft = ctx->rumble_left; + effects.ucRumbleRight = ctx->rumble_right; + } + + if (ctx->lightbar_supported) { + // Populate the LED state with the appropriate color from our lookup table + if (ctx->color_set) { + effects.ucLedRed = ctx->led_red; + effects.ucLedGreen = ctx->led_green; + effects.ucLedBlue = ctx->led_blue; + } else { + SetLedsForPlayerIndex(&effects, ctx->player_index); + } + } + return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage); +} + +static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + + if (ctx->enhanced_reports) { + // This is just a dummy packet that should have no effect, since we don't set the CRC + Uint8 data[78]; + + SDL_zeroa(data); + + data[0] = k_EPS4ReportIdBluetoothEffects; + data[1] = 0xC0; // Magic value HID + CRC + + if (SDL_HIDAPI_LockRumble()) { + SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data)); + } + } else { +#if 0 /* The 8BitDo Zero 2 has perfect emulation of a PS4 controller, except it + * only sends reports when the state changes, so we can't disconnect here. + */ + // We can't even send an invalid effects packet, or it will put the controller in enhanced mode + if (device->num_joysticks > 0) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } +#endif + } +} + +static void HIDAPI_DriverPS4_SetEnhancedModeAvailable(SDL_DriverPS4_Context *ctx) +{ + if (ctx->enhanced_mode_available) { + return; + } + ctx->enhanced_mode_available = true; + + if (ctx->touchpad_supported) { + SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2); + ctx->report_touchpad = true; + } + + if (ctx->sensors_supported) { + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, (float)(1000 / ctx->report_interval)); + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval)); + } + + if (ctx->official_controller) { + ctx->report_battery = true; + } + + HIDAPI_UpdateDeviceProperties(ctx->device); +} + +static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx) +{ + HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx); + + if (!ctx->enhanced_mode) { + ctx->enhanced_mode = true; + + // Switch into enhanced report mode + HIDAPI_DriverPS4_UpdateEffects(ctx, false); + } +} + +static void HIDAPI_DriverPS4_SetEnhancedReportHint(SDL_DriverPS4_Context *ctx, HIDAPI_PS4_EnhancedReportHint enhanced_report_hint) +{ + switch (enhanced_report_hint) { + case PS4_ENHANCED_REPORT_HINT_OFF: + // Nothing to do, enhanced mode is a one-way ticket + break; + case PS4_ENHANCED_REPORT_HINT_ON: + HIDAPI_DriverPS4_SetEnhancedMode(ctx); + break; + case PS4_ENHANCED_REPORT_HINT_AUTO: + HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx); + break; + } + ctx->enhanced_report_hint = enhanced_report_hint; +} + +static void HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS4_Context *ctx) +{ + ctx->enhanced_reports = true; + + if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) { + HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON); + } +} + +static void HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS4_Context *ctx) +{ + if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) { + HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON); + } +} + +static void SDLCALL SDL_PS4EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata; + + if (ctx->device->is_bluetooth) { + if (hint && SDL_strcasecmp(hint, "auto") == 0) { + HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_AUTO); + } else if (SDL_GetStringBoolean(hint, true)) { + HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON); + } else { + HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_OFF); + } + } else { + HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON); + } +} + +static void SDLCALL SDL_PS4ReportIntervalHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + const int DEFAULT_REPORT_INTERVAL = 4; + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata; + int new_report_interval = DEFAULT_REPORT_INTERVAL; + + if (hint) { + int report_interval = SDL_atoi(hint); + switch (report_interval) { + case 1: + case 2: + case 4: + // Valid values + new_report_interval = report_interval; + break; + default: + break; + } + } + + if (new_report_interval != ctx->report_interval) { + ctx->report_interval = (Uint8)new_report_interval; + + HIDAPI_DriverPS4_UpdateEffects(ctx, false); + SDL_LockJoysticks(); + SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_GYRO, (float)(1000 / ctx->report_interval)); + SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval)); + SDL_UnlockJoysticks(); + } +} + +static void HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + + if (!ctx->joystick) { + return; + } + + ctx->player_index = player_index; + + // This will set the new LED state based on the new player index + // SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode + HIDAPI_DriverPS4_UpdateEffects(ctx, false); +} + +static bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + ctx->joystick = joystick; + ctx->last_packet = SDL_GetTicks(); + ctx->report_sensors = false; + ctx->report_touchpad = false; + ctx->rumble_left = 0; + ctx->rumble_right = 0; + ctx->color_set = false; + SDL_zero(ctx->last_state); + + // Initialize player index (needed for setting LEDs) + ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); + + // Initialize the joystick capabilities + joystick->nbuttons = 11; + if (ctx->touchpad_supported) { + joystick->nbuttons += 1; + } + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL, + SDL_PS4ReportIntervalHintChanged, ctx); + SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_PS4EnhancedReportsChanged, ctx); + return true; +} + +static bool HIDAPI_DriverPS4_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + + if (!ctx->vibration_supported) { + return SDL_Unsupported(); + } + + ctx->rumble_left = (low_frequency_rumble >> 8); + ctx->rumble_right = (high_frequency_rumble >> 8); + + return HIDAPI_DriverPS4_UpdateEffects(ctx, true); +} + +static bool HIDAPI_DriverPS4_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverPS4_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + Uint32 result = 0; + + if (ctx->enhanced_mode_available) { + if (ctx->lightbar_supported) { + result |= SDL_JOYSTICK_CAP_RGB_LED; + } + if (ctx->vibration_supported) { + result |= SDL_JOYSTICK_CAP_RUMBLE; + } + } + + return result; +} + +static bool HIDAPI_DriverPS4_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + + if (!ctx->lightbar_supported) { + return SDL_Unsupported(); + } + + ctx->color_set = true; + ctx->led_red = red; + ctx->led_green = green; + ctx->led_blue = blue; + + return HIDAPI_DriverPS4_UpdateEffects(ctx, true); +} + +static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage) +{ + Uint8 data[78]; + int report_size, offset; + + if (!ctx->effects_supported) { + // We shouldn't be sending packets to this controller + return SDL_Unsupported(); + } + + if (!ctx->enhanced_mode) { + if (application_usage) { + HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx); + } + + if (!ctx->enhanced_mode) { + // We're not in enhanced mode, effects aren't allowed + return SDL_Unsupported(); + } + } + + SDL_zeroa(data); + + if (ctx->device->is_bluetooth && ctx->official_controller) { + data[0] = k_EPS4ReportIdBluetoothEffects; + data[1] = 0xC0 | ctx->report_interval; // Magic value HID + CRC, also sets update interval + data[3] = 0x03; // 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval + + report_size = 78; + offset = 6; + } else { + data[0] = k_EPS4ReportIdUsbEffects; + data[1] = 0x07; // Magic value + + report_size = 32; + offset = 4; + } + + SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size)); + + if (ctx->device->is_bluetooth) { + // Bluetooth reports need a CRC at the end of the packet (at least on Linux) + Uint8 ubHdr = 0xA2; // hidp header is part of the CRC calculation + Uint32 unCRC; + unCRC = SDL_crc32(0, &ubHdr, 1); + unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC))); + SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC)); + } + + if (SDL_HIDAPI_SendRumble(ctx->device, data, report_size) != report_size) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static bool HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + + return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, effect, size, true); +} + +static bool HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + + HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx); + + if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) { + return SDL_Unsupported(); + } + + if (enabled) { + HIDAPI_DriverPS4_LoadCalibrationData(device); + } + ctx->report_sensors = enabled; + + return true; +} + +static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS4_Context *ctx, PS4StatePacket_t *packet, int size) +{ + static const float TOUCHPAD_SCALEX = 1.0f / 1920; + static const float TOUCHPAD_SCALEY = 1.0f / 920; // This is noted as being 944 resolution, but 920 feels better + Sint16 axis; + bool touchpad_down; + int touchpad_x, touchpad_y; + Uint64 timestamp = SDL_GetTicksNS(); + + if (size > 9 && ctx->report_touchpad && ctx->enhanced_reports) { + touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0); + touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8); + touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4); + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); + + touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0); + touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8); + touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4); + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); + } + + if (ctx->last_state.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) { + { + Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0)); + } + { + Uint8 hat; + Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F); + + switch (data) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + } + + if (ctx->last_state.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) { + Uint8 data = packet->rgucButtonsHatAndCounter[1]; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0)); + } + + /* Some fightsticks, ex: Victrix FS Pro will only this these digital trigger bits and not the analog values so this needs to run whenever the + trigger is evaluated + */ + if (packet->rgucButtonsHatAndCounter[1] & 0x0C) { + Uint8 data = packet->rgucButtonsHatAndCounter[1]; + packet->ucTriggerLeft = (data & 0x04) && packet->ucTriggerLeft == 0 ? 255 : packet->ucTriggerLeft; + packet->ucTriggerRight = (data & 0x08) && packet->ucTriggerRight == 0 ? 255 : packet->ucTriggerRight; + } + + if (ctx->last_state.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) { + Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD, ((data & 0x02) != 0)); + } + + axis = ((int)packet->ucTriggerLeft * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = ((int)packet->ucTriggerRight * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + axis = ((int)packet->ucLeftJoystickX * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = ((int)packet->ucLeftJoystickY * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = ((int)packet->ucRightJoystickX * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = ((int)packet->ucRightJoystickY * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + + if (size > 9 && ctx->report_battery && ctx->enhanced_reports) { + SDL_PowerState state; + int percent; + Uint8 level = (packet->ucBatteryLevel & 0x0F); + + if (packet->ucBatteryLevel & 0x10) { + if (level <= 10) { + state = SDL_POWERSTATE_CHARGING; + percent = SDL_min(level * 10 + 5, 100); + } else if (level == 11) { + state = SDL_POWERSTATE_CHARGED; + percent = 100; + } else { + state = SDL_POWERSTATE_UNKNOWN; + percent = 0; + } + } else { + state = SDL_POWERSTATE_ON_BATTERY; + percent = SDL_min(level * 10 + 5, 100); + } + SDL_SendJoystickPowerInfo(joystick, state, percent); + } + + if (size > 9 && ctx->report_sensors) { + Uint16 tick; + Uint16 delta; + Uint64 sensor_timestamp; + float data[3]; + + tick = LOAD16(packet->rgucTimestamp[0], packet->rgucTimestamp[1]); + if (ctx->last_tick < tick) { + delta = (tick - ctx->last_tick); + } else { + delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1); + } + ctx->sensor_ticks += delta; + ctx->last_tick = tick; + + // Sensor timestamp is in 5.33us units + sensor_timestamp = (ctx->sensor_ticks * SDL_NS_PER_US * 16) / 3; + + data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1])); + data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1])); + data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1])); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, data, 3); + + data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1])); + data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1])); + data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1])); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3); + } + + SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state)); +} + +static bool VerifyCRC(Uint8 *data, int size) +{ + Uint8 ubHdr = 0xA1; // hidp header is part of the CRC calculation + Uint32 unCRC, unPacketCRC; + Uint8 *packetCRC = data + size - sizeof(unPacketCRC); + unCRC = SDL_crc32(0, &ubHdr, 1); + unCRC = SDL_crc32(unCRC, data, (size_t)(size - sizeof(unCRC))); + + unPacketCRC = LOAD32(packetCRC[0], + packetCRC[1], + packetCRC[2], + packetCRC[3]); + return (unCRC == unPacketCRC); +} + +static bool HIDAPI_DriverPS4_IsPacketValid(SDL_DriverPS4_Context *ctx, Uint8 *data, int size) +{ + switch (data[0]) { + case k_EPS4ReportIdUsbState: + if (size == 10) { + // This is non-enhanced mode, this packet is fine + return true; + } + + if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS4StatePacket_t))) { + // The report timestamp doesn't change when the controller isn't connected + PS4StatePacket_t *packet = (PS4StatePacket_t *)&data[1]; + if (SDL_memcmp(packet->rgucTimestamp, ctx->last_state.rgucTimestamp, sizeof(packet->rgucTimestamp)) == 0) { + return false; + } + if (ctx->last_state.rgucAccelX[0] == 0 && ctx->last_state.rgucAccelX[1] == 0 && + ctx->last_state.rgucAccelY[0] == 0 && ctx->last_state.rgucAccelY[1] == 0 && + ctx->last_state.rgucAccelZ[0] == 0 && ctx->last_state.rgucAccelZ[1] == 0) { + // We don't have any state to compare yet, go ahead and copy it + SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS4StatePacket_t)); + return false; + } + } + + /* In the case of a DS4 USB dongle, bit[2] of byte 31 indicates if a DS4 is actually connected (indicated by '0'). + * For non-dongle, this bit is always 0 (connected). + * This is usually the ID over USB, but the DS4v2 that started shipping with the PS4 Slim will also send this + * packet over BT with a size of 128 + */ + if (size >= 64 && !(data[31] & 0x04)) { + return true; + } + break; + case k_EPS4ReportIdBluetoothState1: + case k_EPS4ReportIdBluetoothState2: + case k_EPS4ReportIdBluetoothState3: + case k_EPS4ReportIdBluetoothState4: + case k_EPS4ReportIdBluetoothState5: + case k_EPS4ReportIdBluetoothState6: + case k_EPS4ReportIdBluetoothState7: + case k_EPS4ReportIdBluetoothState8: + case k_EPS4ReportIdBluetoothState9: + // Bluetooth state packets have two additional bytes at the beginning, the first notes if HID data is present + if (size >= 78 && (data[1] & 0x80)) { + if (VerifyCRC(data, 78)) { + ++ctx->valid_crc_packets; + } else { + if (ctx->valid_crc_packets > 0) { + --ctx->valid_crc_packets; + } + if (ctx->valid_crc_packets >= 3) { + // We're generally getting valid CRC, but failed one + return false; + } + } + return true; + } + break; + default: + break; + } + return false; +} + +static bool HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH * 2]; + int size; + int packet_count = 0; + Uint64 now = SDL_GetTicks(); + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_PS4_PROTOCOL + HIDAPI_DumpPacket("PS4 packet: size = %d", data, size); +#endif + if (!HIDAPI_DriverPS4_IsPacketValid(ctx, data, size)) { + continue; + } + + ++packet_count; + ctx->last_packet = now; + + if (!joystick) { + continue; + } + + switch (data[0]) { + case k_EPS4ReportIdUsbState: + HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[1], size - 1); + break; + case k_EPS4ReportIdBluetoothState1: + case k_EPS4ReportIdBluetoothState2: + case k_EPS4ReportIdBluetoothState3: + case k_EPS4ReportIdBluetoothState4: + case k_EPS4ReportIdBluetoothState5: + case k_EPS4ReportIdBluetoothState6: + case k_EPS4ReportIdBluetoothState7: + case k_EPS4ReportIdBluetoothState8: + case k_EPS4ReportIdBluetoothState9: + // This is the extended report, we can enable effects now in auto mode + HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(ctx); + + // Bluetooth state packets have two additional bytes at the beginning, the first notes if HID is present + HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[3], size - 3); + break; + default: +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown PS4 packet: 0x%.2x", data[0]); +#endif + break; + } + } + + if (device->is_bluetooth) { + if (packet_count == 0) { + // Check to see if it looks like the device disconnected + if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) { + // Send an empty output report to tickle the Bluetooth stack + HIDAPI_DriverPS4_TickleBluetooth(device); + ctx->last_packet = now; + } + } else { + // Reconnect the Bluetooth device once the USB device is gone + if (device->num_joysticks == 0 && + !HIDAPI_HasConnectedUSBDevice(device->serial)) { + HIDAPI_JoystickConnected(device, NULL); + } + } + } + + if (ctx->is_dongle || ctx->is_nacon_dongle) { + if (packet_count == 0) { + if (device->num_joysticks > 0) { + // Check to see if it looks like the device disconnected + if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + } + } else { + if (device->num_joysticks == 0) { + char serial[18]; + size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data)); + if (size >= 7 && (data[1] || data[2] || data[3] || data[4] || data[5] || data[6])) { + (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + data[6], data[5], data[4], data[3], data[2], data[1]); + HIDAPI_SetDeviceSerial(device, serial); + } + HIDAPI_JoystickConnected(device, NULL); + } + } + } + + if (packet_count == 0 && size < 0 && device->num_joysticks > 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverPS4_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL, + SDL_PS4ReportIntervalHintChanged, ctx); + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_PS4EnhancedReportsChanged, ctx); + + ctx->joystick = NULL; + + ctx->report_sensors = false; + ctx->enhanced_mode = false; + ctx->enhanced_mode_available = false; +} + +static void HIDAPI_DriverPS4_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 = { + SDL_HINT_JOYSTICK_HIDAPI_PS4, + true, + HIDAPI_DriverPS4_RegisterHints, + HIDAPI_DriverPS4_UnregisterHints, + HIDAPI_DriverPS4_IsEnabled, + HIDAPI_DriverPS4_IsSupportedDevice, + HIDAPI_DriverPS4_InitDevice, + HIDAPI_DriverPS4_GetDevicePlayerIndex, + HIDAPI_DriverPS4_SetDevicePlayerIndex, + HIDAPI_DriverPS4_UpdateDevice, + HIDAPI_DriverPS4_OpenJoystick, + HIDAPI_DriverPS4_RumbleJoystick, + HIDAPI_DriverPS4_RumbleJoystickTriggers, + HIDAPI_DriverPS4_GetJoystickCapabilities, + HIDAPI_DriverPS4_SetJoystickLED, + HIDAPI_DriverPS4_SendJoystickEffect, + HIDAPI_DriverPS4_SetJoystickSensorsEnabled, + HIDAPI_DriverPS4_CloseJoystick, + HIDAPI_DriverPS4_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_PS4 + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps5.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps5.c new file mode 100644 index 0000000..abf59a8 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps5.c @@ -0,0 +1,1624 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_PS5 + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_PS5_PROTOCOL +#endif + +// Define this if you want to log calibration data +#if 0 +#define DEBUG_PS5_CALIBRATION +#endif + +#define GYRO_RES_PER_DEGREE 1024.0f +#define ACCEL_RES_PER_G 8192.0f +#define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500 + +#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) +#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ + (((Uint32)(B)) << 8) | \ + (((Uint32)(C)) << 16) | \ + (((Uint32)(D)) << 24)) + +enum +{ + SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD = 11, + SDL_GAMEPAD_BUTTON_PS5_MICROPHONE, + SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION, + SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION, + SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE, + SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE +}; + +typedef enum +{ + k_EPS5ReportIdState = 0x01, + k_EPS5ReportIdUsbEffects = 0x02, + k_EPS5ReportIdBluetoothEffects = 0x31, + k_EPS5ReportIdBluetoothState = 0x31, +} EPS5ReportId; + +typedef enum +{ + k_EPS5FeatureReportIdCapabilities = 0x03, + k_EPS5FeatureReportIdCalibration = 0x05, + k_EPS5FeatureReportIdSerialNumber = 0x09, + k_EPS5FeatureReportIdFirmwareInfo = 0x20, +} EPS5FeatureReportId; + +typedef struct +{ + Uint8 ucLeftJoystickX; + Uint8 ucLeftJoystickY; + Uint8 ucRightJoystickX; + Uint8 ucRightJoystickY; + Uint8 rgucButtonsHatAndCounter[3]; + Uint8 ucTriggerLeft; + Uint8 ucTriggerRight; +} PS5SimpleStatePacket_t; + +typedef struct +{ + Uint8 ucLeftJoystickX; // 0 + Uint8 ucLeftJoystickY; // 1 + Uint8 ucRightJoystickX; // 2 + Uint8 ucRightJoystickY; // 3 + Uint8 ucTriggerLeft; // 4 + Uint8 ucTriggerRight; // 5 + Uint8 ucCounter; // 6 + Uint8 rgucButtonsAndHat[4]; // 7 + Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian + Uint8 rgucGyroX[2]; // 15 + Uint8 rgucGyroY[2]; // 17 + Uint8 rgucGyroZ[2]; // 19 + Uint8 rgucAccelX[2]; // 21 + Uint8 rgucAccelY[2]; // 23 + Uint8 rgucAccelZ[2]; // 25 + Uint8 rgucSensorTimestamp[4]; // 27 - 16/32 bit little endian + +} PS5StatePacketCommon_t; + +typedef struct +{ + Uint8 ucLeftJoystickX; // 0 + Uint8 ucLeftJoystickY; // 1 + Uint8 ucRightJoystickX; // 2 + Uint8 ucRightJoystickY; // 3 + Uint8 ucTriggerLeft; // 4 + Uint8 ucTriggerRight; // 5 + Uint8 ucCounter; // 6 + Uint8 rgucButtonsAndHat[4]; // 7 + Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian + Uint8 rgucGyroX[2]; // 15 + Uint8 rgucGyroY[2]; // 17 + Uint8 rgucGyroZ[2]; // 19 + Uint8 rgucAccelX[2]; // 21 + Uint8 rgucAccelY[2]; // 23 + Uint8 rgucAccelZ[2]; // 25 + Uint8 rgucSensorTimestamp[4]; // 27 - 32 bit little endian + Uint8 ucSensorTemp; // 31 + Uint8 ucTouchpadCounter1; // 32 - high bit clear + counter + Uint8 rgucTouchpadData1[3]; // 33 - X/Y, 12 bits per axis + Uint8 ucTouchpadCounter2; // 36 - high bit clear + counter + Uint8 rgucTouchpadData2[3]; // 37 - X/Y, 12 bits per axis + Uint8 rgucUnknown1[8]; // 40 + Uint8 rgucTimer2[4]; // 48 - 32 bit little endian + Uint8 ucBatteryLevel; // 52 + Uint8 ucConnectState; // 53 - 0x08 = USB, 0x01 = headphone + + // There's more unknown data at the end, and a 32-bit CRC on Bluetooth +} PS5StatePacket_t; + +typedef struct +{ + Uint8 ucLeftJoystickX; // 0 + Uint8 ucLeftJoystickY; // 1 + Uint8 ucRightJoystickX; // 2 + Uint8 ucRightJoystickY; // 3 + Uint8 ucTriggerLeft; // 4 + Uint8 ucTriggerRight; // 5 + Uint8 ucCounter; // 6 + Uint8 rgucButtonsAndHat[4]; // 7 + Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian + Uint8 rgucGyroX[2]; // 15 + Uint8 rgucGyroY[2]; // 17 + Uint8 rgucGyroZ[2]; // 19 + Uint8 rgucAccelX[2]; // 21 + Uint8 rgucAccelY[2]; // 23 + Uint8 rgucAccelZ[2]; // 25 + Uint8 rgucSensorTimestamp[2]; // 27 - 16 bit little endian + Uint8 ucBatteryLevel; // 29 + Uint8 ucUnknown; // 30 + Uint8 ucTouchpadCounter1; // 31 - high bit clear + counter + Uint8 rgucTouchpadData1[3]; // 32 - X/Y, 12 bits per axis + Uint8 ucTouchpadCounter2; // 35 - high bit clear + counter + Uint8 rgucTouchpadData2[3]; // 36 - X/Y, 12 bits per axis + + // There's more unknown data at the end, and a 32-bit CRC on Bluetooth +} PS5StatePacketAlt_t; + +typedef struct +{ + Uint8 ucEnableBits1; // 0 + Uint8 ucEnableBits2; // 1 + Uint8 ucRumbleRight; // 2 + Uint8 ucRumbleLeft; // 3 + Uint8 ucHeadphoneVolume; // 4 + Uint8 ucSpeakerVolume; // 5 + Uint8 ucMicrophoneVolume; // 6 + Uint8 ucAudioEnableBits; // 7 + Uint8 ucMicLightMode; // 8 + Uint8 ucAudioMuteBits; // 9 + Uint8 rgucRightTriggerEffect[11]; // 10 + Uint8 rgucLeftTriggerEffect[11]; // 21 + Uint8 rgucUnknown1[6]; // 32 + Uint8 ucEnableBits3; // 38 + Uint8 rgucUnknown2[2]; // 39 + Uint8 ucLedAnim; // 41 + Uint8 ucLedBrightness; // 42 + Uint8 ucPadLights; // 43 + Uint8 ucLedRed; // 44 + Uint8 ucLedGreen; // 45 + Uint8 ucLedBlue; // 46 +} DS5EffectsState_t; + +typedef enum +{ + k_EDS5EffectRumbleStart = (1 << 0), + k_EDS5EffectRumble = (1 << 1), + k_EDS5EffectLEDReset = (1 << 2), + k_EDS5EffectLED = (1 << 3), + k_EDS5EffectPadLights = (1 << 4), + k_EDS5EffectMicLight = (1 << 5) +} EDS5Effect; + +typedef enum +{ + k_EDS5LEDResetStateNone, + k_EDS5LEDResetStatePending, + k_EDS5LEDResetStateComplete, +} EDS5LEDResetState; + +typedef struct +{ + Sint16 bias; + float sensitivity; +} IMUCalibrationData; + +/* Rumble hint mode: + * "0": enhanced features are never used + * "1": enhanced features are always used + * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it. + */ +typedef enum +{ + PS5_ENHANCED_REPORT_HINT_OFF, + PS5_ENHANCED_REPORT_HINT_ON, + PS5_ENHANCED_REPORT_HINT_AUTO +} HIDAPI_PS5_EnhancedReportHint; + +typedef struct +{ + SDL_HIDAPI_Device *device; + SDL_Joystick *joystick; + bool is_nacon_dongle; + bool use_alternate_report; + bool sensors_supported; + bool lightbar_supported; + bool vibration_supported; + bool playerled_supported; + bool touchpad_supported; + bool effects_supported; + HIDAPI_PS5_EnhancedReportHint enhanced_report_hint; + bool enhanced_reports; + bool enhanced_mode; + bool enhanced_mode_available; + bool report_sensors; + bool report_touchpad; + bool report_battery; + bool hardware_calibration; + IMUCalibrationData calibration[6]; + Uint16 firmware_version; + Uint64 last_packet; + int player_index; + bool player_lights; + Uint8 rumble_left; + Uint8 rumble_right; + bool color_set; + Uint8 led_red; + Uint8 led_green; + Uint8 led_blue; + EDS5LEDResetState led_reset_state; + Uint64 sensor_ticks; + Uint32 last_tick; + union + { + PS5SimpleStatePacket_t simple; + PS5StatePacketCommon_t state; + PS5StatePacketAlt_t alt_state; + PS5StatePacket_t full_state; + Uint8 data[64]; + } last_state; +} SDL_DriverPS5_Context; + +static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, bool application_usage); + +static void HIDAPI_DriverPS5_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5, callback, userdata); +} + +static void HIDAPI_DriverPS5_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5, callback, userdata); +} + +static bool HIDAPI_DriverPS5_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length) +{ + SDL_memset(report, 0, length); + report[0] = report_id; + return SDL_hid_get_feature_report(dev, report, length); +} + +static bool HIDAPI_DriverPS5_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + Uint8 data[USB_PACKET_LENGTH]; + int size; + + if (type == SDL_GAMEPAD_TYPE_PS5) { + return true; + } + + if (HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) { + if (device && device->dev) { + size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data)); + if (size == 48 && data[2] == 0x28) { + // Supported third party controller + return true; + } else { + return false; + } + } else { + // Might be supported by this driver, enumerate and find out + return true; + } + } + return false; +} + +static void SetLedsForPlayerIndex(DS5EffectsState_t *effects, int player_index) +{ + /* This list is the same as what hid-sony.c uses in the Linux kernel. + The first 4 values correspond to what the PS4 assigns. + */ + static const Uint8 colors[7][3] = { + { 0x00, 0x00, 0x40 }, // Blue + { 0x40, 0x00, 0x00 }, // Red + { 0x00, 0x40, 0x00 }, // Green + { 0x20, 0x00, 0x20 }, // Pink + { 0x20, 0x10, 0x00 }, // Orange + { 0x00, 0x10, 0x10 }, // Teal + { 0x10, 0x10, 0x10 } // White + }; + + if (player_index >= 0) { + player_index %= SDL_arraysize(colors); + } else { + player_index = 0; + } + + effects->ucLedRed = colors[player_index][0]; + effects->ucLedGreen = colors[player_index][1]; + effects->ucLedBlue = colors[player_index][2]; +} + +static void SetLightsForPlayerIndex(DS5EffectsState_t *effects, int player_index) +{ + static const Uint8 lights[] = { + 0x04, + 0x0A, + 0x15, + 0x1B, + 0x1F + }; + + if (player_index >= 0) { + // Bitmask, 0x1F enables all lights, 0x20 changes instantly instead of fade + player_index %= SDL_arraysize(lights); + effects->ucPadLights = lights[player_index] | 0x20; + } else { + effects->ucPadLights = 0x00; + } +} + +static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS5_Context *ctx; + Uint8 data[USB_PACKET_LENGTH * 2]; + int size; + char serial[18]; + SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; + + ctx = (SDL_DriverPS5_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + + device->context = ctx; + + if (device->serial && SDL_strlen(device->serial) == 12) { + int i, j; + + j = -1; + for (i = 0; i < 12; i += 2) { + j += 1; + SDL_memmove(&serial[j], &device->serial[i], 2); + j += 2; + serial[j] = '-'; + } + serial[j] = '\0'; + } else { + serial[0] = '\0'; + } + + // Read a report to see what mode we're in + size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16); +#ifdef DEBUG_PS5_PROTOCOL + if (size > 0) { + HIDAPI_DumpPacket("PS5 first packet: size = %d", data, size); + } else { + SDL_Log("PS5 first packet: size = %d", size); + } +#endif + if (size == 64) { + // Connected over USB + ctx->enhanced_reports = true; + } else if (size > 0 && data[0] == k_EPS5ReportIdBluetoothEffects) { + // Connected over Bluetooth, using enhanced reports + ctx->enhanced_reports = true; + } else { + // Connected over Bluetooth, using simple reports (DirectInput enabled) + } + + if (device->vendor_id == USB_VENDOR_SONY && ctx->enhanced_reports) { + /* Read the serial number (Bluetooth address in reverse byte order) + This will also enable enhanced reports over Bluetooth + */ + if (ReadFeatureReport(device->dev, k_EPS5FeatureReportIdSerialNumber, data, sizeof(data)) >= 7) { + (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + data[6], data[5], data[4], data[3], data[2], data[1]); + } + + /* Read the firmware version + This will also enable enhanced reports over Bluetooth + */ + if (ReadFeatureReport(device->dev, k_EPS5FeatureReportIdFirmwareInfo, data, USB_PACKET_LENGTH) >= 46) { + ctx->firmware_version = (Uint16)data[44] | ((Uint16)data[45] << 8); + } + } + + // Get the device capabilities + if (device->vendor_id == USB_VENDOR_SONY) { + ctx->sensors_supported = true; + ctx->lightbar_supported = true; + ctx->vibration_supported = true; + ctx->playerled_supported = true; + ctx->touchpad_supported = true; + } else { + // Third party controller capability request + size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data)); + if (size == 48 && data[2] == 0x28) { + Uint8 capabilities = data[4]; + Uint8 capabilities2 = data[20]; + Uint8 device_type = data[5]; + +#ifdef DEBUG_PS5_PROTOCOL + HIDAPI_DumpPacket("PS5 capabilities: size = %d", data, size); +#endif + if (capabilities & 0x02) { + ctx->sensors_supported = true; + } + if (capabilities & 0x04) { + ctx->lightbar_supported = true; + } + if (capabilities & 0x08) { + ctx->vibration_supported = true; + } + if (capabilities & 0x40) { + ctx->touchpad_supported = true; + } + if (capabilities2 & 0x80) { + ctx->playerled_supported = true; + } + + switch (device_type) { + case 0x00: + joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; + break; + case 0x01: + joystick_type = SDL_JOYSTICK_TYPE_GUITAR; + break; + case 0x02: + joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT; + break; + case 0x06: + joystick_type = SDL_JOYSTICK_TYPE_WHEEL; + break; + case 0x07: + joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK; + break; + case 0x08: + joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK; + break; + default: + joystick_type = SDL_JOYSTICK_TYPE_UNKNOWN; + break; + } + + ctx->use_alternate_report = true; + + if (device->vendor_id == USB_VENDOR_NACON_ALT && + (device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED || + device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS)) { + // This doesn't report vibration capability, but it can do rumble + ctx->vibration_supported = true; + } + } else if (device->vendor_id == USB_VENDOR_RAZER && + (device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRED || + device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRELESS)) { + // The Razer Wolverine V2 Pro doesn't respond to the detection protocol, but has a touchpad and sensors and no vibration + ctx->sensors_supported = true; + ctx->touchpad_supported = true; + ctx->use_alternate_report = true; + } else if (device->vendor_id == USB_VENDOR_RAZER && + device->product_id == USB_PRODUCT_RAZER_KITSUNE) { + // The Razer Kitsune doesn't respond to the detection protocol, but has a touchpad + joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK; + ctx->touchpad_supported = true; + ctx->use_alternate_report = true; + } + } + ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported || ctx->playerled_supported); + + if (device->vendor_id == USB_VENDOR_NACON_ALT && + device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) { + ctx->is_nacon_dongle = true; + } + + device->joystick_type = joystick_type; + device->type = SDL_GAMEPAD_TYPE_PS5; + if (device->vendor_id == USB_VENDOR_SONY) { + if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) { + HIDAPI_SetDeviceName(device, "DualSense Edge Wireless Controller"); + } else { + HIDAPI_SetDeviceName(device, "DualSense Wireless Controller"); + } + } + HIDAPI_SetDeviceSerial(device, serial); + + if (ctx->is_nacon_dongle) { + // We don't know if this is connected yet, wait for reports + return true; + } + + // Prefer the USB device over the Bluetooth device + if (device->is_bluetooth) { + if (HIDAPI_HasConnectedUSBDevice(device->serial)) { + return true; + } + } else { + HIDAPI_DisconnectBluetoothDevice(device->serial); + } + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverPS5_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverPS5_LoadCalibrationData(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + int i, size; + Uint8 data[USB_PACKET_LENGTH]; + + size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCalibration, data, sizeof(data)); + if (size < 35) { +#ifdef DEBUG_PS5_CALIBRATION + SDL_Log("Short read of calibration data: %d, ignoring calibration", size); +#endif + return; + } + + { + Sint16 sGyroPitchBias, sGyroYawBias, sGyroRollBias; + Sint16 sGyroPitchPlus, sGyroPitchMinus; + Sint16 sGyroYawPlus, sGyroYawMinus; + Sint16 sGyroRollPlus, sGyroRollMinus; + Sint16 sGyroSpeedPlus, sGyroSpeedMinus; + + Sint16 sAccXPlus, sAccXMinus; + Sint16 sAccYPlus, sAccYMinus; + Sint16 sAccZPlus, sAccZMinus; + + float flNumerator; + Sint16 sRange2g; + +#ifdef DEBUG_PS5_CALIBRATION + HIDAPI_DumpPacket("PS5 calibration packet: size = %d", data, size); +#endif + + sGyroPitchBias = LOAD16(data[1], data[2]); + sGyroYawBias = LOAD16(data[3], data[4]); + sGyroRollBias = LOAD16(data[5], data[6]); + + sGyroPitchPlus = LOAD16(data[7], data[8]); + sGyroPitchMinus = LOAD16(data[9], data[10]); + sGyroYawPlus = LOAD16(data[11], data[12]); + sGyroYawMinus = LOAD16(data[13], data[14]); + sGyroRollPlus = LOAD16(data[15], data[16]); + sGyroRollMinus = LOAD16(data[17], data[18]); + + sGyroSpeedPlus = LOAD16(data[19], data[20]); + sGyroSpeedMinus = LOAD16(data[21], data[22]); + + sAccXPlus = LOAD16(data[23], data[24]); + sAccXMinus = LOAD16(data[25], data[26]); + sAccYPlus = LOAD16(data[27], data[28]); + sAccYMinus = LOAD16(data[29], data[30]); + sAccZPlus = LOAD16(data[31], data[32]); + sAccZMinus = LOAD16(data[33], data[34]); + + flNumerator = (sGyroSpeedPlus + sGyroSpeedMinus) * GYRO_RES_PER_DEGREE; + ctx->calibration[0].bias = sGyroPitchBias; + ctx->calibration[0].sensitivity = flNumerator / (sGyroPitchPlus - sGyroPitchMinus); + + ctx->calibration[1].bias = sGyroYawBias; + ctx->calibration[1].sensitivity = flNumerator / (sGyroYawPlus - sGyroYawMinus); + + ctx->calibration[2].bias = sGyroRollBias; + ctx->calibration[2].sensitivity = flNumerator / (sGyroRollPlus - sGyroRollMinus); + + sRange2g = sAccXPlus - sAccXMinus; + ctx->calibration[3].bias = sAccXPlus - sRange2g / 2; + ctx->calibration[3].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g; + + sRange2g = sAccYPlus - sAccYMinus; + ctx->calibration[4].bias = sAccYPlus - sRange2g / 2; + ctx->calibration[4].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g; + + sRange2g = sAccZPlus - sAccZMinus; + ctx->calibration[5].bias = sAccZPlus - sRange2g / 2; + ctx->calibration[5].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g; + + ctx->hardware_calibration = true; + for (i = 0; i < 6; ++i) { + float divisor = (i < 3 ? 64.0f : 1.0f); +#ifdef DEBUG_PS5_CALIBRATION + SDL_Log("calibration[%d] bias = %d, sensitivity = %f", i, ctx->calibration[i].bias, ctx->calibration[i].sensitivity); +#endif + // Some controllers have a bad calibration + if ((SDL_abs(ctx->calibration[i].bias) > 1024) || (SDL_fabsf(1.0f - ctx->calibration[i].sensitivity / divisor) > 0.5f)) { +#ifdef DEBUG_PS5_CALIBRATION + SDL_Log("invalid calibration, ignoring"); +#endif + ctx->hardware_calibration = false; + } + } + } +} + +static float HIDAPI_DriverPS5_ApplyCalibrationData(SDL_DriverPS5_Context *ctx, int index, Sint16 value) +{ + float result; + + if (ctx->hardware_calibration) { + IMUCalibrationData *calibration = &ctx->calibration[index]; + + result = (value - calibration->bias) * calibration->sensitivity; + } else if (index < 3) { + result = value * 64.f; + } else { + result = value; + } + + // Convert the raw data to the units expected by SDL + if (index < 3) { + result = (result / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f; + } else { + result = (result / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; + } + return result; +} + +static bool HIDAPI_DriverPS5_UpdateEffects(SDL_DriverPS5_Context *ctx, int effect_mask, bool application_usage) +{ + DS5EffectsState_t effects; + + // Make sure the Bluetooth connection sequence has completed before sending LED color change + if (ctx->device->is_bluetooth && ctx->enhanced_reports && + (effect_mask & (k_EDS5EffectLED | k_EDS5EffectPadLights)) != 0) { + if (ctx->led_reset_state != k_EDS5LEDResetStateComplete) { + ctx->led_reset_state = k_EDS5LEDResetStatePending; + return true; + } + } + + SDL_zero(effects); + + if (ctx->vibration_supported) { + if (ctx->rumble_left || ctx->rumble_right) { + if (ctx->firmware_version < 0x0224) { + effects.ucEnableBits1 |= 0x01; // Enable rumble emulation + + // Shift to reduce effective rumble strength to match Xbox controllers + effects.ucRumbleLeft = ctx->rumble_left >> 1; + effects.ucRumbleRight = ctx->rumble_right >> 1; + } else { + effects.ucEnableBits3 |= 0x04; // Enable improved rumble emulation on 2.24 firmware and newer + + effects.ucRumbleLeft = ctx->rumble_left; + effects.ucRumbleRight = ctx->rumble_right; + } + effects.ucEnableBits1 |= 0x02; // Disable audio haptics + } else { + // Leaving emulated rumble bits off will restore audio haptics + } + + if ((effect_mask & k_EDS5EffectRumbleStart) != 0) { + effects.ucEnableBits1 |= 0x02; // Disable audio haptics + } + if ((effect_mask & k_EDS5EffectRumble) != 0) { + // Already handled above + } + } + if (ctx->lightbar_supported) { + if ((effect_mask & k_EDS5EffectLEDReset) != 0) { + effects.ucEnableBits2 |= 0x08; // Reset LED state + } + if ((effect_mask & k_EDS5EffectLED) != 0) { + effects.ucEnableBits2 |= 0x04; // Enable LED color + + // Populate the LED state with the appropriate color from our lookup table + if (ctx->color_set) { + effects.ucLedRed = ctx->led_red; + effects.ucLedGreen = ctx->led_green; + effects.ucLedBlue = ctx->led_blue; + } else { + SetLedsForPlayerIndex(&effects, ctx->player_index); + } + } + } + if (ctx->playerled_supported) { + if ((effect_mask & k_EDS5EffectPadLights) != 0) { + effects.ucEnableBits2 |= 0x10; // Enable touchpad lights + + if (ctx->player_lights) { + SetLightsForPlayerIndex(&effects, ctx->player_index); + } else { + effects.ucPadLights = 0x00; + } + } + } + if ((effect_mask & k_EDS5EffectMicLight) != 0) { + effects.ucEnableBits2 |= 0x01; // Enable microphone light + + effects.ucMicLightMode = 0; // Bitmask, 0x00 = off, 0x01 = solid, 0x02 = pulse + } + + return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage); +} + +static void HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_DriverPS5_Context *ctx) +{ + bool led_reset_complete = false; + + if (ctx->enhanced_reports && ctx->sensors_supported && !ctx->use_alternate_report) { + const PS5StatePacketCommon_t *packet = &ctx->last_state.state; + + // Check the timer to make sure the Bluetooth connection LED animation is complete + const Uint32 connection_complete = 10200000; + Uint32 timestamp = LOAD32(packet->rgucSensorTimestamp[0], + packet->rgucSensorTimestamp[1], + packet->rgucSensorTimestamp[2], + packet->rgucSensorTimestamp[3]); + if (timestamp >= connection_complete) { + led_reset_complete = true; + } + } else { + // We don't know how to check the timer, just assume it's complete for now + led_reset_complete = true; + } + + if (led_reset_complete) { + HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLEDReset, false); + + ctx->led_reset_state = k_EDS5LEDResetStateComplete; + + HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false); + } +} + +static void HIDAPI_DriverPS5_TickleBluetooth(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + + if (ctx->enhanced_reports) { + // This is just a dummy packet that should have no effect, since we don't set the CRC + Uint8 data[78]; + + SDL_zeroa(data); + + data[0] = k_EPS5ReportIdBluetoothEffects; + data[1] = 0x02; // Magic value + + if (SDL_HIDAPI_LockRumble()) { + SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data)); + } + } else { + // We can't even send an invalid effects packet, or it will put the controller in enhanced mode + if (device->num_joysticks > 0) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + } +} + +static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx) +{ + if (ctx->enhanced_mode_available) { + return; + } + ctx->enhanced_mode_available = true; + + if (ctx->touchpad_supported) { + SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2); + ctx->report_touchpad = true; + } + + if (ctx->sensors_supported) { + if (ctx->device->is_bluetooth) { + // Bluetooth sensor update rate appears to be 1000 Hz + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 1000.0f); + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 1000.0f); + } else { + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 250.0f); + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 250.0f); + } + } + + ctx->report_battery = true; + + HIDAPI_UpdateDeviceProperties(ctx->device); +} + +static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_DriverPS5_Context *ctx) +{ + HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx); + + if (!ctx->enhanced_mode) { + ctx->enhanced_mode = true; + + // Switch into enhanced report mode + HIDAPI_DriverPS5_UpdateEffects(ctx, 0, false); + + // Update the light effects + HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false); + } +} + +static void HIDAPI_DriverPS5_SetEnhancedReportHint(SDL_DriverPS5_Context *ctx, HIDAPI_PS5_EnhancedReportHint enhanced_report_hint) +{ + switch (enhanced_report_hint) { + case PS5_ENHANCED_REPORT_HINT_OFF: + // Nothing to do, enhanced mode is a one-way ticket + break; + case PS5_ENHANCED_REPORT_HINT_ON: + HIDAPI_DriverPS5_SetEnhancedMode(ctx); + break; + case PS5_ENHANCED_REPORT_HINT_AUTO: + HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx); + break; + } + ctx->enhanced_report_hint = enhanced_report_hint; +} + +static void HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS5_Context *ctx) +{ + ctx->enhanced_reports = true; + + if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) { + HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON); + } +} + +static void HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS5_Context *ctx) +{ + if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) { + HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON); + } +} + +static void SDLCALL SDL_PS5EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata; + + if (ctx->device->is_bluetooth) { + if (hint && SDL_strcasecmp(hint, "auto") == 0) { + HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_AUTO); + } else if (SDL_GetStringBoolean(hint, true)) { + HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON); + } else { + HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_OFF); + } + } else { + HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON); + } +} + +static void SDLCALL SDL_PS5PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata; + bool player_lights = SDL_GetStringBoolean(hint, true); + + if (player_lights != ctx->player_lights) { + ctx->player_lights = player_lights; + + HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectPadLights, false); + } +} + +static void HIDAPI_DriverPS5_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + + if (!ctx->joystick) { + return; + } + + ctx->player_index = player_index; + + // This will set the new LED state based on the new player index + // SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode + HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false); +} + +static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + ctx->joystick = joystick; + ctx->last_packet = SDL_GetTicks(); + ctx->report_sensors = false; + ctx->report_touchpad = false; + ctx->rumble_left = 0; + ctx->rumble_right = 0; + ctx->color_set = false; + ctx->led_reset_state = k_EDS5LEDResetStateNone; + SDL_zero(ctx->last_state); + + // Initialize player index (needed for setting LEDs) + ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); + ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, true); + + // Initialize the joystick capabilities + if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) { + joystick->nbuttons = 17; // paddles and touchpad and microphone + } else if (ctx->touchpad_supported) { + joystick->nbuttons = 13; // touchpad and microphone + } else { + joystick->nbuttons = 11; + } + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + joystick->firmware_version = ctx->firmware_version; + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_PS5EnhancedReportsChanged, ctx); + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, + SDL_PS5PlayerLEDHintChanged, ctx); + + return true; +} + +static bool HIDAPI_DriverPS5_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + + if (!ctx->vibration_supported) { + return SDL_Unsupported(); + } + + if (!ctx->rumble_left && !ctx->rumble_right) { + HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumbleStart, true); + } + + ctx->rumble_left = (low_frequency_rumble >> 8); + ctx->rumble_right = (high_frequency_rumble >> 8); + + return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumble, true); +} + +static bool HIDAPI_DriverPS5_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverPS5_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + Uint32 result = 0; + + if (ctx->enhanced_mode_available) { + if (ctx->lightbar_supported) { + result |= SDL_JOYSTICK_CAP_RGB_LED; + } + if (ctx->playerled_supported) { + result |= SDL_JOYSTICK_CAP_PLAYER_LED; + } + if (ctx->vibration_supported) { + result |= SDL_JOYSTICK_CAP_RUMBLE; + } + } + + return result; +} + +static bool HIDAPI_DriverPS5_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + + if (!ctx->lightbar_supported) { + return SDL_Unsupported(); + } + + ctx->color_set = true; + ctx->led_red = red; + ctx->led_green = green; + ctx->led_blue = blue; + + return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLED, true); +} + +static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, bool application_usage) +{ + Uint8 data[78]; + int report_size, offset; + Uint8 *pending_data; + int *pending_size; + int maximum_size; + + if (!ctx->effects_supported) { + // We shouldn't be sending packets to this controller + return SDL_Unsupported(); + } + + if (!ctx->enhanced_mode) { + if (application_usage) { + HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx); + } + + if (!ctx->enhanced_mode) { + // We're not in enhanced mode, effects aren't allowed + return SDL_Unsupported(); + } + } + + SDL_zeroa(data); + + if (ctx->device->is_bluetooth) { + data[0] = k_EPS5ReportIdBluetoothEffects; + data[1] = 0x02; // Magic value + + report_size = 78; + offset = 2; + } else { + data[0] = k_EPS5ReportIdUsbEffects; + + report_size = 48; + offset = 1; + } + + SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size)); + + if (ctx->device->is_bluetooth) { + // Bluetooth reports need a CRC at the end of the packet (at least on Linux) + Uint8 ubHdr = 0xA2; // hidp header is part of the CRC calculation + Uint32 unCRC; + unCRC = SDL_crc32(0, &ubHdr, 1); + unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC))); + SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC)); + } + + if (!SDL_HIDAPI_LockRumble()) { + return false; + } + + // See if we can update an existing pending request + if (SDL_HIDAPI_GetPendingRumbleLocked(ctx->device, &pending_data, &pending_size, &maximum_size)) { + DS5EffectsState_t *effects = (DS5EffectsState_t *)&data[offset]; + DS5EffectsState_t *pending_effects = (DS5EffectsState_t *)&pending_data[offset]; + if (report_size == *pending_size && + effects->ucEnableBits1 == pending_effects->ucEnableBits1 && + effects->ucEnableBits2 == pending_effects->ucEnableBits2) { + // We're simply updating the data for this request + SDL_memcpy(pending_data, data, report_size); + SDL_HIDAPI_UnlockRumble(); + return true; + } + } + + if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, report_size) != report_size) { + return false; + } + + return true; +} + +static bool HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + + return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, effect, size, true); +} + +static bool HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + + HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx); + + if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) { + return SDL_Unsupported(); + } + + if (enabled) { + HIDAPI_DriverPS5_LoadCalibrationData(device); + } + ctx->report_sensors = enabled; + + return true; +} + +static void HIDAPI_DriverPS5_HandleSimpleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5SimpleStatePacket_t *packet, Uint64 timestamp) +{ + Sint16 axis; + + if (ctx->last_state.simple.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) { + { + Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0)); + } + { + Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F); + Uint8 hat; + + switch (data) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + } + + if (ctx->last_state.simple.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) { + Uint8 data = packet->rgucButtonsHatAndCounter[1]; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0)); + } + + if (ctx->last_state.simple.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) { + Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, ((data & 0x02) != 0)); + } + + if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x04)) { + axis = SDL_JOYSTICK_AXIS_MAX; + } else { + axis = ((int)packet->ucTriggerLeft * 257) - 32768; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + if (packet->ucTriggerRight == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x08)) { + axis = SDL_JOYSTICK_AXIS_MAX; + } else { + axis = ((int)packet->ucTriggerRight * 257) - 32768; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + axis = ((int)packet->ucLeftJoystickX * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = ((int)packet->ucLeftJoystickY * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = ((int)packet->ucRightJoystickX * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = ((int)packet->ucRightJoystickY * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + + SDL_memcpy(&ctx->last_state.simple, packet, sizeof(ctx->last_state.simple)); +} + +static void HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketCommon_t *packet, Uint64 timestamp) +{ + Sint16 axis; + + if (ctx->last_state.state.rgucButtonsAndHat[0] != packet->rgucButtonsAndHat[0]) { + { + Uint8 data = (packet->rgucButtonsAndHat[0] >> 4); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0)); + } + { + Uint8 data = (packet->rgucButtonsAndHat[0] & 0x0F); + Uint8 hat; + + switch (data) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + } + + if (ctx->last_state.state.rgucButtonsAndHat[1] != packet->rgucButtonsAndHat[1]) { + Uint8 data = packet->rgucButtonsAndHat[1]; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0)); + } + + if (ctx->last_state.state.rgucButtonsAndHat[2] != packet->rgucButtonsAndHat[2]) { + Uint8 data = packet->rgucButtonsAndHat[2]; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_MICROPHONE, ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION, ((data & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE, ((data & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE, ((data & 0x80) != 0)); + } + + if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsAndHat[1] & 0x04)) { + axis = SDL_JOYSTICK_AXIS_MAX; + } else { + axis = ((int)packet->ucTriggerLeft * 257) - 32768; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + if (packet->ucTriggerRight == 0 && (packet->rgucButtonsAndHat[1] & 0x08)) { + axis = SDL_JOYSTICK_AXIS_MAX; + } else { + axis = ((int)packet->ucTriggerRight * 257) - 32768; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + axis = ((int)packet->ucLeftJoystickX * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = ((int)packet->ucLeftJoystickY * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = ((int)packet->ucRightJoystickX * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = ((int)packet->ucRightJoystickY * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + + if (ctx->report_sensors) { + Uint64 sensor_timestamp; + float data[3]; + + if (ctx->use_alternate_report) { + // 16-bit timestamp + Uint32 delta; + Uint16 tick = LOAD16(packet->rgucSensorTimestamp[0], + packet->rgucSensorTimestamp[1]); + if (ctx->last_tick < tick) { + delta = (tick - ctx->last_tick); + } else { + delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1); + } + ctx->last_tick = tick; + ctx->sensor_ticks += delta; + + // Sensor timestamp is in 1us units + sensor_timestamp = SDL_US_TO_NS(ctx->sensor_ticks); + } else { + // 32-bit timestamp + Uint32 delta; + Uint32 tick = LOAD32(packet->rgucSensorTimestamp[0], + packet->rgucSensorTimestamp[1], + packet->rgucSensorTimestamp[2], + packet->rgucSensorTimestamp[3]); + if (ctx->last_tick < tick) { + delta = (tick - ctx->last_tick); + } else { + delta = (SDL_MAX_UINT32 - ctx->last_tick + tick + 1); + } + ctx->last_tick = tick; + ctx->sensor_ticks += delta; + + // Sensor timestamp is in 0.33us units + sensor_timestamp = (ctx->sensor_ticks * SDL_NS_PER_US) / 3; + } + + data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1])); + data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1])); + data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1])); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, data, 3); + + data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1])); + data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1])); + data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1])); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3); + } +} + +static void HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacket_t *packet, Uint64 timestamp) +{ + static const float TOUCHPAD_SCALEX = 1.0f / 1920; + static const float TOUCHPAD_SCALEY = 1.0f / 1070; + bool touchpad_down; + int touchpad_x, touchpad_y; + + if (ctx->report_touchpad) { + touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0); + touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8); + touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4); + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); + + touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0); + touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8); + touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4); + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); + } + + if (ctx->report_battery) { + SDL_PowerState state; + int percent; + Uint8 status = (packet->ucBatteryLevel >> 4) & 0x0F; + Uint8 level = (packet->ucBatteryLevel & 0x0F); + + switch (status) { + case 0: + state = SDL_POWERSTATE_ON_BATTERY; + percent = SDL_min(level * 10 + 5, 100); + break; + case 1: + state = SDL_POWERSTATE_CHARGING; + percent = SDL_min(level * 10 + 5, 100); + break; + case 2: + state = SDL_POWERSTATE_CHARGED; + percent = 100; + break; + default: + state = SDL_POWERSTATE_UNKNOWN; + percent = 0; + break; + } + SDL_SendJoystickPowerInfo(joystick, state, percent); + } + + HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp); + + SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state)); +} + +static void HIDAPI_DriverPS5_HandleStatePacketAlt(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketAlt_t *packet, Uint64 timestamp) +{ + static const float TOUCHPAD_SCALEX = 1.0f / 1920; + static const float TOUCHPAD_SCALEY = 1.0f / 1070; + bool touchpad_down; + int touchpad_x, touchpad_y; + + if (ctx->report_touchpad) { + touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0); + touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8); + touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4); + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); + + touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0); + touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8); + touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4); + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); + } + + HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp); + + SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state)); +} + +static bool VerifyCRC(Uint8 *data, int size) +{ + Uint8 ubHdr = 0xA1; // hidp header is part of the CRC calculation + Uint32 unCRC, unPacketCRC; + Uint8 *packetCRC = data + size - sizeof(unPacketCRC); + unCRC = SDL_crc32(0, &ubHdr, 1); + unCRC = SDL_crc32(unCRC, data, (size_t)(size - sizeof(unCRC))); + + unPacketCRC = LOAD32(packetCRC[0], + packetCRC[1], + packetCRC[2], + packetCRC[3]); + return (unCRC == unPacketCRC); +} + +static bool HIDAPI_DriverPS5_IsPacketValid(SDL_DriverPS5_Context *ctx, Uint8 *data, int size) +{ + switch (data[0]) { + case k_EPS5ReportIdState: + if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) { + // The report timestamp doesn't change when the controller isn't connected + PS5StatePacketAlt_t *packet = (PS5StatePacketAlt_t *)&data[1]; + if (SDL_memcmp(packet->rgucPacketSequence, ctx->last_state.state.rgucPacketSequence, sizeof(packet->rgucPacketSequence)) == 0) { + return false; + } + if (ctx->last_state.alt_state.rgucAccelX[0] == 0 && ctx->last_state.alt_state.rgucAccelX[1] == 0 && + ctx->last_state.alt_state.rgucAccelY[0] == 0 && ctx->last_state.alt_state.rgucAccelY[1] == 0 && + ctx->last_state.alt_state.rgucAccelZ[0] == 0 && ctx->last_state.alt_state.rgucAccelZ[1] == 0) { + // We don't have any state to compare yet, go ahead and copy it + SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS5StatePacketAlt_t)); + return false; + } + } + return true; + + case k_EPS5ReportIdBluetoothState: + if (VerifyCRC(data, size)) { + return true; + } + break; + default: + break; + } + return false; +} + +static bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH * 2]; + int size; + int packet_count = 0; + Uint64 now = SDL_GetTicks(); + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { + Uint64 timestamp = SDL_GetTicksNS(); + +#ifdef DEBUG_PS5_PROTOCOL + HIDAPI_DumpPacket("PS5 packet: size = %d", data, size); +#endif + if (!HIDAPI_DriverPS5_IsPacketValid(ctx, data, size)) { + continue; + } + + ++packet_count; + ctx->last_packet = now; + + if (!joystick) { + continue; + } + + switch (data[0]) { + case k_EPS5ReportIdState: + if (size == 10 || size == 78) { + HIDAPI_DriverPS5_HandleSimpleStatePacket(joystick, device->dev, ctx, (PS5SimpleStatePacket_t *)&data[1], timestamp); + } else { + if (ctx->use_alternate_report) { + HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[1], timestamp); + } else { + HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[1], timestamp); + } + } + break; + case k_EPS5ReportIdBluetoothState: + // This is the extended report, we can enable effects now in auto mode + HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(ctx); + + if (ctx->use_alternate_report) { + HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[2], timestamp); + } else { + HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[2], timestamp); + } + if (ctx->led_reset_state == k_EDS5LEDResetStatePending) { + HIDAPI_DriverPS5_CheckPendingLEDReset(ctx); + } + break; + default: +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown PS5 packet: 0x%.2x", data[0]); +#endif + break; + } + } + + if (device->is_bluetooth) { + if (packet_count == 0) { + // Check to see if it looks like the device disconnected + if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) { + // Send an empty output report to tickle the Bluetooth stack + HIDAPI_DriverPS5_TickleBluetooth(device); + ctx->last_packet = now; + } + } else { + // Reconnect the Bluetooth device once the USB device is gone + if (device->num_joysticks == 0 && + !HIDAPI_HasConnectedUSBDevice(device->serial)) { + HIDAPI_JoystickConnected(device, NULL); + } + } + } + + if (ctx->is_nacon_dongle) { + if (packet_count == 0) { + if (device->num_joysticks > 0) { + // Check to see if it looks like the device disconnected + if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + } + } else { + if (device->num_joysticks == 0) { + HIDAPI_JoystickConnected(device, NULL); + } + } + } + + if (packet_count == 0 && size < 0 && device->num_joysticks > 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverPS5_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_PS5EnhancedReportsChanged, ctx); + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, + SDL_PS5PlayerLEDHintChanged, ctx); + + ctx->joystick = NULL; + + ctx->report_sensors = false; + ctx->enhanced_mode = false; + ctx->enhanced_mode_available = false; +} + +static void HIDAPI_DriverPS5_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5 = { + SDL_HINT_JOYSTICK_HIDAPI_PS5, + true, + HIDAPI_DriverPS5_RegisterHints, + HIDAPI_DriverPS5_UnregisterHints, + HIDAPI_DriverPS5_IsEnabled, + HIDAPI_DriverPS5_IsSupportedDevice, + HIDAPI_DriverPS5_InitDevice, + HIDAPI_DriverPS5_GetDevicePlayerIndex, + HIDAPI_DriverPS5_SetDevicePlayerIndex, + HIDAPI_DriverPS5_UpdateDevice, + HIDAPI_DriverPS5_OpenJoystick, + HIDAPI_DriverPS5_RumbleJoystick, + HIDAPI_DriverPS5_RumbleJoystickTriggers, + HIDAPI_DriverPS5_GetJoystickCapabilities, + HIDAPI_DriverPS5_SetJoystickLED, + HIDAPI_DriverPS5_SendJoystickEffect, + HIDAPI_DriverPS5_SetJoystickSensorsEnabled, + HIDAPI_DriverPS5_CloseJoystick, + HIDAPI_DriverPS5_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_PS5 + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.c new file mode 100644 index 0000000..5fd93dc --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.c @@ -0,0 +1,285 @@ +/* + 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_JOYSTICK_HIDAPI + +// Handle rumble on a separate thread so it doesn't block the application + +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" +#include "../../thread/SDL_systhread.h" + +typedef struct SDL_HIDAPI_RumbleRequest +{ + SDL_HIDAPI_Device *device; + Uint8 data[2 * USB_PACKET_LENGTH]; // need enough space for the biggest report: dualshock4 is 78 bytes + int size; + SDL_HIDAPI_RumbleSentCallback callback; + void *userdata; + struct SDL_HIDAPI_RumbleRequest *prev; + +} SDL_HIDAPI_RumbleRequest; + +typedef struct SDL_HIDAPI_RumbleContext +{ + SDL_AtomicInt initialized; + SDL_AtomicInt running; + SDL_Thread *thread; + SDL_Semaphore *request_sem; + SDL_HIDAPI_RumbleRequest *requests_head; + SDL_HIDAPI_RumbleRequest *requests_tail; +} SDL_HIDAPI_RumbleContext; + +#ifndef SDL_THREAD_SAFETY_ANALYSIS +static +#endif +SDL_Mutex *SDL_HIDAPI_rumble_lock; +static SDL_HIDAPI_RumbleContext rumble_context SDL_GUARDED_BY(SDL_HIDAPI_rumble_lock); + +static int SDLCALL SDL_HIDAPI_RumbleThread(void *data) +{ + SDL_HIDAPI_RumbleContext *ctx = (SDL_HIDAPI_RumbleContext *)data; + + SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH); + + while (SDL_GetAtomicInt(&ctx->running)) { + SDL_HIDAPI_RumbleRequest *request = NULL; + + SDL_WaitSemaphore(ctx->request_sem); + + SDL_LockMutex(SDL_HIDAPI_rumble_lock); + request = ctx->requests_tail; + if (request) { + if (request == ctx->requests_head) { + ctx->requests_head = NULL; + } + ctx->requests_tail = request->prev; + } + SDL_UnlockMutex(SDL_HIDAPI_rumble_lock); + + if (request) { + SDL_LockMutex(request->device->dev_lock); + if (request->device->dev) { +#ifdef DEBUG_RUMBLE + HIDAPI_DumpPacket("Rumble packet: size = %d", request->data, request->size); +#endif + SDL_hid_write(request->device->dev, request->data, request->size); + } + SDL_UnlockMutex(request->device->dev_lock); + if (request->callback) { + request->callback(request->userdata); + } + (void)SDL_AtomicDecRef(&request->device->rumble_pending); + SDL_free(request); + + // Make sure we're not starving report reads when there's lots of rumble + SDL_Delay(10); + } + } + return 0; +} + +static void SDL_HIDAPI_StopRumbleThread(SDL_HIDAPI_RumbleContext *ctx) +{ + SDL_HIDAPI_RumbleRequest *request; + + SDL_SetAtomicInt(&ctx->running, false); + + if (ctx->thread) { + int result; + + SDL_SignalSemaphore(ctx->request_sem); + SDL_WaitThread(ctx->thread, &result); + ctx->thread = NULL; + } + + SDL_LockMutex(SDL_HIDAPI_rumble_lock); + while (ctx->requests_tail) { + request = ctx->requests_tail; + if (request == ctx->requests_head) { + ctx->requests_head = NULL; + } + ctx->requests_tail = request->prev; + + if (request->callback) { + request->callback(request->userdata); + } + (void)SDL_AtomicDecRef(&request->device->rumble_pending); + SDL_free(request); + } + SDL_UnlockMutex(SDL_HIDAPI_rumble_lock); + + if (ctx->request_sem) { + SDL_DestroySemaphore(ctx->request_sem); + ctx->request_sem = NULL; + } + + if (SDL_HIDAPI_rumble_lock) { + SDL_DestroyMutex(SDL_HIDAPI_rumble_lock); + SDL_HIDAPI_rumble_lock = NULL; + } + + SDL_SetAtomicInt(&ctx->initialized, false); +} + +static bool SDL_HIDAPI_StartRumbleThread(SDL_HIDAPI_RumbleContext *ctx) +{ + SDL_HIDAPI_rumble_lock = SDL_CreateMutex(); + if (!SDL_HIDAPI_rumble_lock) { + SDL_HIDAPI_StopRumbleThread(ctx); + return false; + } + + ctx->request_sem = SDL_CreateSemaphore(0); + if (!ctx->request_sem) { + SDL_HIDAPI_StopRumbleThread(ctx); + return false; + } + + SDL_SetAtomicInt(&ctx->running, true); + ctx->thread = SDL_CreateThread(SDL_HIDAPI_RumbleThread, "HIDAPI Rumble", ctx); + if (!ctx->thread) { + SDL_HIDAPI_StopRumbleThread(ctx); + return false; + } + return true; +} + +bool SDL_HIDAPI_LockRumble(void) +{ + SDL_HIDAPI_RumbleContext *ctx = &rumble_context; + + if (SDL_CompareAndSwapAtomicInt(&ctx->initialized, false, true)) { + if (!SDL_HIDAPI_StartRumbleThread(ctx)) { + return false; + } + } + + SDL_LockMutex(SDL_HIDAPI_rumble_lock); + return true; +} + +bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size) +{ + SDL_HIDAPI_RumbleContext *ctx = &rumble_context; + SDL_HIDAPI_RumbleRequest *request, *found; + + found = NULL; + for (request = ctx->requests_tail; request; request = request->prev) { + if (request->device == device) { + found = request; + } + } + if (found) { + *data = found->data; + *size = &found->size; + *maximum_size = sizeof(found->data); + return true; + } + return false; +} + +int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size) +{ + return SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device, data, size, NULL, NULL); +} + +int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size, SDL_HIDAPI_RumbleSentCallback callback, void *userdata) +{ + SDL_HIDAPI_RumbleContext *ctx = &rumble_context; + SDL_HIDAPI_RumbleRequest *request; + + if (size > sizeof(request->data)) { + SDL_HIDAPI_UnlockRumble(); + SDL_SetError("Couldn't send rumble, size %d is greater than %d", size, (int)sizeof(request->data)); + return -1; + } + + request = (SDL_HIDAPI_RumbleRequest *)SDL_calloc(1, sizeof(*request)); + if (!request) { + SDL_HIDAPI_UnlockRumble(); + return -1; + } + request->device = device; + SDL_memcpy(request->data, data, size); + request->size = size; + request->callback = callback; + request->userdata = userdata; + + SDL_AtomicIncRef(&device->rumble_pending); + + if (ctx->requests_head) { + ctx->requests_head->prev = request; + } else { + ctx->requests_tail = request; + } + ctx->requests_head = request; + + // Make sure we unlock before posting the semaphore so the rumble thread can run immediately + SDL_HIDAPI_UnlockRumble(); + + SDL_SignalSemaphore(ctx->request_sem); + + return size; +} + +void SDL_HIDAPI_UnlockRumble(void) +{ + SDL_UnlockMutex(SDL_HIDAPI_rumble_lock); +} + +int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size) +{ + Uint8 *pending_data; + int *pending_size; + int maximum_size; + + if (size <= 0) { + SDL_SetError("Tried to send rumble with invalid size"); + return -1; + } + + if (!SDL_HIDAPI_LockRumble()) { + return -1; + } + + // check if there is a pending request for the device and update it + if (SDL_HIDAPI_GetPendingRumbleLocked(device, &pending_data, &pending_size, &maximum_size) && + size == *pending_size && data[0] == pending_data[0]) { + SDL_memcpy(pending_data, data, size); + SDL_HIDAPI_UnlockRumble(); + return size; + } + + return SDL_HIDAPI_SendRumbleAndUnlock(device, data, size); +} + +void SDL_HIDAPI_QuitRumble(void) +{ + SDL_HIDAPI_RumbleContext *ctx = &rumble_context; + + if (SDL_GetAtomicInt(&ctx->running)) { + SDL_HIDAPI_StopRumbleThread(ctx); + } +} + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.h b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.h new file mode 100644 index 0000000..ede061e --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.h @@ -0,0 +1,42 @@ +/* + 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_JOYSTICK_HIDAPI + +// Handle rumble on a separate thread so it doesn't block the application + +// Advanced API +#ifdef SDL_THREAD_SAFETY_ANALYSIS +extern SDL_Mutex *SDL_HIDAPI_rumble_lock; +#endif +bool SDL_HIDAPI_LockRumble(void) SDL_TRY_ACQUIRE(0, SDL_HIDAPI_rumble_lock); +bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size); +int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size) SDL_RELEASE(SDL_HIDAPI_rumble_lock); +typedef void (*SDL_HIDAPI_RumbleSentCallback)(void *userdata); +int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size, SDL_HIDAPI_RumbleSentCallback callback, void *userdata) SDL_RELEASE(SDL_HIDAPI_rumble_lock); +void SDL_HIDAPI_UnlockRumble(void) SDL_RELEASE(SDL_HIDAPI_rumble_lock); + +// Simple API, will replace any pending rumble with the new data +int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size); +void SDL_HIDAPI_QuitRumble(void); + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_shield.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_shield.c new file mode 100644 index 0000000..10dcea3 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_shield.c @@ -0,0 +1,578 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_SHIELD + +// Define this if you want to log all packets from the controller +// #define DEBUG_SHIELD_PROTOCOL + +#define CMD_BATTERY_STATE 0x07 +#define CMD_RUMBLE 0x39 +#define CMD_CHARGE_STATE 0x3A + +// Milliseconds between polls of battery state +#define BATTERY_POLL_INTERVAL_MS 60000 + +// Milliseconds between retransmission of rumble to keep motors running +#define RUMBLE_REFRESH_INTERVAL_MS 500 + +// Reports that are too small are dropped over Bluetooth +#define HID_REPORT_SIZE 33 + +enum +{ + SDL_GAMEPAD_BUTTON_SHIELD_SHARE = 11, + SDL_GAMEPAD_BUTTON_SHIELD_V103_TOUCHPAD, + SDL_GAMEPAD_BUTTON_SHIELD_V103_MINUS, + SDL_GAMEPAD_BUTTON_SHIELD_V103_PLUS, + SDL_GAMEPAD_NUM_SHIELD_V103_BUTTONS, + + SDL_GAMEPAD_NUM_SHIELD_V104_BUTTONS = SDL_GAMEPAD_BUTTON_SHIELD_SHARE + 1, +}; + +typedef enum +{ + k_ShieldReportIdControllerState = 0x01, + k_ShieldReportIdControllerTouch = 0x02, + k_ShieldReportIdCommandResponse = 0x03, + k_ShieldReportIdCommandRequest = 0x04, +} EShieldReportId; + +// This same report structure is used for both requests and responses +typedef struct +{ + Uint8 report_id; + Uint8 cmd; + Uint8 seq_num; + Uint8 payload[HID_REPORT_SIZE - 3]; +} ShieldCommandReport_t; +SDL_COMPILE_TIME_ASSERT(ShieldCommandReport_t, sizeof(ShieldCommandReport_t) == HID_REPORT_SIZE); + +typedef struct +{ + Uint8 seq_num; + + bool has_charging; + Uint8 charging; + bool has_battery_level; + Uint8 battery_level; + Uint64 last_battery_query_time; + + bool rumble_report_pending; + bool rumble_update_pending; + Uint8 left_motor_amplitude; + Uint8 right_motor_amplitude; + Uint64 last_rumble_time; + + Uint8 last_state[USB_PACKET_LENGTH]; +} SDL_DriverShield_Context; + +static void HIDAPI_DriverShield_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, callback, userdata); +} + +static void HIDAPI_DriverShield_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, callback, userdata); +} + +static bool HIDAPI_DriverShield_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverShield_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + return SDL_IsJoystickNVIDIASHIELDController(vendor_id, product_id); +} + +static bool HIDAPI_DriverShield_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverShield_Context *ctx; + + ctx = (SDL_DriverShield_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + device->context = ctx; + + HIDAPI_SetDeviceName(device, "NVIDIA SHIELD Controller"); + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverShield_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverShield_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static bool HIDAPI_DriverShield_SendCommand(SDL_HIDAPI_Device *device, Uint8 cmd, const void *data, int size) +{ + SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context; + ShieldCommandReport_t cmd_pkt; + + if (size > sizeof(cmd_pkt.payload)) { + return SDL_SetError("Command data exceeds HID report size"); + } + + if (!SDL_HIDAPI_LockRumble()) { + return false; + } + + cmd_pkt.report_id = k_ShieldReportIdCommandRequest; + cmd_pkt.cmd = cmd; + cmd_pkt.seq_num = ctx->seq_num++; + if (data) { + SDL_memcpy(cmd_pkt.payload, data, size); + } + + // Zero unused data in the payload + if (size != sizeof(cmd_pkt.payload)) { + SDL_memset(&cmd_pkt.payload[size], 0, sizeof(cmd_pkt.payload) - size); + } + + if (SDL_HIDAPI_SendRumbleAndUnlock(device, (Uint8 *)&cmd_pkt, sizeof(cmd_pkt)) != sizeof(cmd_pkt)) { + return SDL_SetError("Couldn't send command packet"); + } + + return true; +} + +static bool HIDAPI_DriverShield_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + ctx->rumble_report_pending = false; + ctx->rumble_update_pending = false; + ctx->left_motor_amplitude = 0; + ctx->right_motor_amplitude = 0; + ctx->last_rumble_time = 0; + SDL_zeroa(ctx->last_state); + + // Initialize the joystick capabilities + if (device->product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) { + joystick->nbuttons = SDL_GAMEPAD_NUM_SHIELD_V103_BUTTONS; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + SDL_PrivateJoystickAddTouchpad(joystick, 1); + } else { + joystick->nbuttons = SDL_GAMEPAD_NUM_SHIELD_V104_BUTTONS; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + } + + // Request battery and charging info + ctx->last_battery_query_time = SDL_GetTicks(); + HIDAPI_DriverShield_SendCommand(device, CMD_CHARGE_STATE, NULL, 0); + HIDAPI_DriverShield_SendCommand(device, CMD_BATTERY_STATE, NULL, 0); + + return true; +} + +static bool HIDAPI_DriverShield_SendNextRumble(SDL_HIDAPI_Device *device) +{ + SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context; + Uint8 rumble_data[3]; + + if (!ctx->rumble_update_pending) { + return true; + } + + rumble_data[0] = 0x01; // enable + rumble_data[1] = ctx->left_motor_amplitude; + rumble_data[2] = ctx->right_motor_amplitude; + + ctx->rumble_update_pending = false; + ctx->last_rumble_time = SDL_GetTicks(); + + return HIDAPI_DriverShield_SendCommand(device, CMD_RUMBLE, rumble_data, sizeof(rumble_data)); +} + +static bool HIDAPI_DriverShield_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + if (device->product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) { + Uint8 rumble_packet[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + rumble_packet[2] = (low_frequency_rumble >> 8); + rumble_packet[4] = (high_frequency_rumble >> 8); + + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; + + } else { + SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context; + + // The rumble motors are quite intense, so tone down the intensity like the official driver does + ctx->left_motor_amplitude = low_frequency_rumble >> 11; + ctx->right_motor_amplitude = high_frequency_rumble >> 11; + ctx->rumble_update_pending = true; + + if (ctx->rumble_report_pending) { + // We will service this after the hardware acknowledges the previous request + return true; + } + + return HIDAPI_DriverShield_SendNextRumble(device); + } +} + +static bool HIDAPI_DriverShield_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverShield_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return SDL_JOYSTICK_CAP_RUMBLE; +} + +static bool HIDAPI_DriverShield_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverShield_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + const Uint8 *data_bytes = (const Uint8 *)data; + + if (size > 1) { + // Single command byte followed by a variable length payload + return HIDAPI_DriverShield_SendCommand(device, data_bytes[0], &data_bytes[1], size - 1); + } else if (size == 1) { + // Single command byte with no payload + return HIDAPI_DriverShield_SendCommand(device, data_bytes[0], NULL, 0); + } else { + return SDL_SetError("Effect data must at least contain a command byte"); + } +} + +static bool HIDAPI_DriverShield_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size) +{ + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[3] != data[3]) { + Uint8 hat; + + switch (data[3]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + if (ctx->last_state[1] != data[1]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[1] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[1] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[1] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[1] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x80) != 0)); + } + + if (ctx->last_state[2] != data[2]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_PLUS, ((data[2] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_MINUS, ((data[2] & 0x10) != 0)); + //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x40) != 0)); + //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, ((data[2] & 0x80) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x80) != 0)); + } + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[4]) - 0x8000); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[6]) - 0x8000); + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[8]) - 0x8000); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[10]) - 0x8000); + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[12]) - 0x8000); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[14]) - 0x8000); + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +#undef clamp +#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) + +static void HIDAPI_DriverShield_HandleTouchPacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, const Uint8 *data, int size) +{ + bool touchpad_down; + float touchpad_x, touchpad_y; + Uint64 timestamp = SDL_GetTicksNS(); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_TOUCHPAD, ((data[1] & 0x01) != 0)); + + // It's a triangular pad, but just use the center as the usable touch area + touchpad_down = ((data[1] & 0x80) == 0); + touchpad_x = clamp((float)(data[2] - 0x70) / 0x50, 0.0f, 1.0f); + touchpad_y = clamp((float)(data[4] - 0x40) / 0x15, 0.0f, 1.0f); + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x, touchpad_y, touchpad_down ? 1.0f : 0.0f); +} + +static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size) +{ + Uint64 timestamp = SDL_GetTicksNS(); + + if (size < 23) { + return; + } + + if (ctx->last_state[2] != data[2]) { + Uint8 hat; + + switch (data[2]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + if (ctx->last_state[3] != data[3]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[3] & 0x80) != 0)); + } + + if (ctx->last_state[4] != data[4]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[4] & 0x01) != 0)); + } + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[9]) - 0x8000); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[11]) - 0x8000); + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[13]) - 0x8000); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[15]) - 0x8000); + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[19]) - 0x8000); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[21]) - 0x8000); + + if (ctx->last_state[17] != data[17]) { + //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, ((data[17] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[17] & 0x02) != 0)); + //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[17] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[17] & 0x01) != 0)); + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void HIDAPI_DriverShield_UpdatePowerInfo(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx) +{ + if (!ctx->has_charging || !ctx->has_battery_level) { + return; + } + + SDL_PowerState state = ctx->charging ? SDL_POWERSTATE_CHARGING : SDL_POWERSTATE_ON_BATTERY; + int percent = ctx->battery_level * 20; + SDL_SendJoystickPowerInfo(joystick, state, percent); +} + +static bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + ShieldCommandReport_t *cmd_resp_report; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_SHIELD_PROTOCOL + HIDAPI_DumpPacket("NVIDIA SHIELD packet: size = %d", data, size); +#endif + + // Byte 0 is HID report ID + switch (data[0]) { + case k_ShieldReportIdControllerState: + if (!joystick) { + break; + } + if (size == 16) { + HIDAPI_DriverShield_HandleStatePacketV103(joystick, ctx, data, size); + } else { + HIDAPI_DriverShield_HandleStatePacketV104(joystick, ctx, data, size); + } + break; + case k_ShieldReportIdControllerTouch: + if (!joystick) { + break; + } + HIDAPI_DriverShield_HandleTouchPacketV103(joystick, ctx, data, size); + break; + case k_ShieldReportIdCommandResponse: + cmd_resp_report = (ShieldCommandReport_t *)data; + switch (cmd_resp_report->cmd) { + case CMD_RUMBLE: + ctx->rumble_report_pending = false; + HIDAPI_DriverShield_SendNextRumble(device); + break; + case CMD_CHARGE_STATE: + ctx->has_charging = true; + ctx->charging = cmd_resp_report->payload[0]; + HIDAPI_DriverShield_UpdatePowerInfo(joystick, ctx); + break; + case CMD_BATTERY_STATE: + ctx->has_battery_level = true; + ctx->battery_level = cmd_resp_report->payload[2]; + HIDAPI_DriverShield_UpdatePowerInfo(joystick, ctx); + break; + } + break; + } + } + + // Ask for battery state again if we're due for an update + if (joystick && SDL_GetTicks() >= (ctx->last_battery_query_time + BATTERY_POLL_INTERVAL_MS)) { + ctx->last_battery_query_time = SDL_GetTicks(); + HIDAPI_DriverShield_SendCommand(device, CMD_BATTERY_STATE, NULL, 0); + } + + // Retransmit rumble packets if they've lasted longer than the hardware supports + if ((ctx->left_motor_amplitude != 0 || ctx->right_motor_amplitude != 0) && + SDL_GetTicks() >= (ctx->last_rumble_time + RUMBLE_REFRESH_INTERVAL_MS)) { + ctx->rumble_update_pending = true; + HIDAPI_DriverShield_SendNextRumble(device); + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverShield_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_DriverShield_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield = { + SDL_HINT_JOYSTICK_HIDAPI_SHIELD, + true, + HIDAPI_DriverShield_RegisterHints, + HIDAPI_DriverShield_UnregisterHints, + HIDAPI_DriverShield_IsEnabled, + HIDAPI_DriverShield_IsSupportedDevice, + HIDAPI_DriverShield_InitDevice, + HIDAPI_DriverShield_GetDevicePlayerIndex, + HIDAPI_DriverShield_SetDevicePlayerIndex, + HIDAPI_DriverShield_UpdateDevice, + HIDAPI_DriverShield_OpenJoystick, + HIDAPI_DriverShield_RumbleJoystick, + HIDAPI_DriverShield_RumbleJoystickTriggers, + HIDAPI_DriverShield_GetJoystickCapabilities, + HIDAPI_DriverShield_SetJoystickLED, + HIDAPI_DriverShield_SendJoystickEffect, + HIDAPI_DriverShield_SetJoystickSensorsEnabled, + HIDAPI_DriverShield_CloseJoystick, + HIDAPI_DriverShield_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_SHIELD + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_stadia.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_stadia.c new file mode 100644 index 0000000..487bf41 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_stadia.c @@ -0,0 +1,324 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_STADIA + +// Define this if you want to log all packets from the controller +// #define DEBUG_STADIA_PROTOCOL + +enum +{ + SDL_GAMEPAD_BUTTON_STADIA_CAPTURE = 11, + SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT, + SDL_GAMEPAD_NUM_STADIA_BUTTONS, +}; + +typedef struct +{ + bool rumble_supported; + Uint8 last_state[USB_PACKET_LENGTH]; +} SDL_DriverStadia_Context; + +static void HIDAPI_DriverStadia_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata); +} + +static void HIDAPI_DriverStadia_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata); +} + +static bool HIDAPI_DriverStadia_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STADIA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverStadia_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + return SDL_IsJoystickGoogleStadiaController(vendor_id, product_id); +} + +static bool HIDAPI_DriverStadia_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverStadia_Context *ctx; + + ctx = (SDL_DriverStadia_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + device->context = ctx; + + // Check whether rumble is supported + { + Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 }; + + if (SDL_hid_write(device->dev, rumble_packet, sizeof(rumble_packet)) >= 0) { + ctx->rumble_supported = true; + } + } + + HIDAPI_SetDeviceName(device, "Google Stadia Controller"); + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverStadia_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverStadia_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static bool HIDAPI_DriverStadia_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + SDL_zeroa(ctx->last_state); + + // Initialize the joystick capabilities + joystick->nbuttons = SDL_GAMEPAD_NUM_STADIA_BUTTONS; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + return true; +} + +static bool HIDAPI_DriverStadia_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context; + + if (ctx->rumble_supported) { + Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 }; + + + rumble_packet[1] = (low_frequency_rumble & 0xFF); + rumble_packet[2] = (low_frequency_rumble >> 8); + rumble_packet[3] = (high_frequency_rumble & 0xFF); + rumble_packet[4] = (high_frequency_rumble >> 8); + + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; + } else { + return SDL_Unsupported(); + } +} + +static bool HIDAPI_DriverStadia_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverStadia_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context; + Uint32 caps = 0; + + if (ctx->rumble_supported) { + caps |= SDL_JOYSTICK_CAP_RUMBLE; + } + return caps; +} + +static bool HIDAPI_DriverStadia_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverStadia_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverStadia_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void HIDAPI_DriverStadia_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverStadia_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + // The format is the same but the original FW will send 10 bytes and January '21 FW update will send 11 + if (size < 10 || data[0] != 0x03) { + // We don't know how to handle this report + return; + } + + if (ctx->last_state[1] != data[1]) { + Uint8 hat; + + switch (data[1]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + if (ctx->last_state[2] != data[2]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_CAPTURE, ((data[2] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT, ((data[2] & 0x02) != 0)); + } + + if (ctx->last_state[3] != data[3]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x01) != 0)); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), 0x01 - 0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(4); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(5); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(6); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(7); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + +#define READ_TRIGGER_AXIS(offset) \ + (Sint16)(((int)data[offset] * 257) - 32768) + { + axis = READ_TRIGGER_AXIS(8); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = READ_TRIGGER_AXIS(9); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + } +#undef READ_TRIGGER_AXIS + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverStadia_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_STADIA_PROTOCOL + HIDAPI_DumpPacket("Google Stadia packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + HIDAPI_DriverStadia_HandleStatePacket(joystick, ctx, data, size); + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverStadia_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_DriverStadia_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia = { + SDL_HINT_JOYSTICK_HIDAPI_STADIA, + true, + HIDAPI_DriverStadia_RegisterHints, + HIDAPI_DriverStadia_UnregisterHints, + HIDAPI_DriverStadia_IsEnabled, + HIDAPI_DriverStadia_IsSupportedDevice, + HIDAPI_DriverStadia_InitDevice, + HIDAPI_DriverStadia_GetDevicePlayerIndex, + HIDAPI_DriverStadia_SetDevicePlayerIndex, + HIDAPI_DriverStadia_UpdateDevice, + HIDAPI_DriverStadia_OpenJoystick, + HIDAPI_DriverStadia_RumbleJoystick, + HIDAPI_DriverStadia_RumbleJoystickTriggers, + HIDAPI_DriverStadia_GetJoystickCapabilities, + HIDAPI_DriverStadia_SetJoystickLED, + HIDAPI_DriverStadia_SendJoystickEffect, + HIDAPI_DriverStadia_SetJoystickSensorsEnabled, + HIDAPI_DriverStadia_CloseJoystick, + HIDAPI_DriverStadia_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_STADIA + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam.c new file mode 100644 index 0000000..b48d353 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam.c @@ -0,0 +1,1534 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" + +#ifdef SDL_JOYSTICK_HIDAPI_STEAM + +// Define this if you want to log all packets from the controller +// #define DEBUG_STEAM_PROTOCOL + +#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED "SDL_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED" + +#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) +// This requires prompting for Bluetooth permissions, so make sure the application really wants it +#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT false +#else +#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT) +#endif + +#define PAIRING_STATE_DURATION_SECONDS 60 + + +/*****************************************************************************************************/ + +#include "steam/controller_constants.h" +#include "steam/controller_structs.h" + +enum +{ + SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE = 11, + SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE, + SDL_GAMEPAD_NUM_STEAM_BUTTONS, +}; + +typedef struct SteamControllerStateInternal_t +{ + // Controller Type for this Controller State + Uint32 eControllerType; + + // If packet num matches that on your prior call, then the controller state hasn't been changed since + // your last call and there is no need to process it + Uint32 unPacketNum; + + // bit flags for each of the buttons + Uint64 ulButtons; + + // Left pad coordinates + short sLeftPadX; + short sLeftPadY; + + // Right pad coordinates + short sRightPadX; + short sRightPadY; + + // Center pad coordinates + short sCenterPadX; + short sCenterPadY; + + // Left analog stick coordinates + short sLeftStickX; + short sLeftStickY; + + // Right analog stick coordinates + short sRightStickX; + short sRightStickY; + + unsigned short sTriggerL; + unsigned short sTriggerR; + + short sAccelX; + short sAccelY; + short sAccelZ; + + short sGyroX; + short sGyroY; + short sGyroZ; + + float sGyroQuatW; + float sGyroQuatX; + float sGyroQuatY; + float sGyroQuatZ; + + short sGyroSteeringAngle; + + unsigned short sBatteryLevel; + + // Pressure sensor data. + unsigned short sPressurePadLeft; + unsigned short sPressurePadRight; + + unsigned short sPressureBumperLeft; + unsigned short sPressureBumperRight; + + // Internal state data + short sPrevLeftPad[2]; + short sPrevLeftStick[2]; +} SteamControllerStateInternal_t; + +// Defines for ulButtons in SteamControllerStateInternal_t +#define STEAM_RIGHT_TRIGGER_MASK 0x00000001 +#define STEAM_LEFT_TRIGGER_MASK 0x00000002 +#define STEAM_RIGHT_BUMPER_MASK 0x00000004 +#define STEAM_LEFT_BUMPER_MASK 0x00000008 +#define STEAM_BUTTON_NORTH_MASK 0x00000010 // Y +#define STEAM_BUTTON_EAST_MASK 0x00000020 // B +#define STEAM_BUTTON_WEST_MASK 0x00000040 // X +#define STEAM_BUTTON_SOUTH_MASK 0x00000080 // A +#define STEAM_DPAD_UP_MASK 0x00000100 // DPAD UP +#define STEAM_DPAD_RIGHT_MASK 0x00000200 // DPAD RIGHT +#define STEAM_DPAD_LEFT_MASK 0x00000400 // DPAD LEFT +#define STEAM_DPAD_DOWN_MASK 0x00000800 // DPAD DOWN +#define STEAM_BUTTON_MENU_MASK 0x00001000 // SELECT +#define STEAM_BUTTON_STEAM_MASK 0x00002000 // GUIDE +#define STEAM_BUTTON_ESCAPE_MASK 0x00004000 // START +#define STEAM_BUTTON_BACK_LEFT_MASK 0x00008000 +#define STEAM_BUTTON_BACK_RIGHT_MASK 0x00010000 +#define STEAM_BUTTON_LEFTPAD_CLICKED_MASK 0x00020000 +#define STEAM_BUTTON_RIGHTPAD_CLICKED_MASK 0x00040000 +#define STEAM_LEFTPAD_FINGERDOWN_MASK 0x00080000 +#define STEAM_RIGHTPAD_FINGERDOWN_MASK 0x00100000 +#define STEAM_JOYSTICK_BUTTON_MASK 0x00400000 +#define STEAM_LEFTPAD_AND_JOYSTICK_MASK 0x00800000 + +// Look for report version 0x0001, type WIRELESS (3), length >= 1 byte +#define D0G_IS_VALID_WIRELESS_EVENT(data, len) ((len) >= 5 && (data)[0] == 1 && (data)[1] == 0 && (data)[2] == 3 && (data)[3] >= 1) +#define D0G_GET_WIRELESS_EVENT_TYPE(data) ((data)[4]) +#define D0G_WIRELESS_DISCONNECTED 1 +#define D0G_WIRELESS_ESTABLISHED 2 +#define D0G_WIRELESS_NEWLYPAIRED 3 + +#define D0G_IS_WIRELESS_DISCONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) == D0G_WIRELESS_DISCONNECTED) +#define D0G_IS_WIRELESS_CONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) != D0G_WIRELESS_DISCONNECTED) + + +#define MAX_REPORT_SEGMENT_PAYLOAD_SIZE 18 +/* + * SteamControllerPacketAssembler has to be used when reading output repots from controllers. + */ +typedef struct +{ + uint8_t uBuffer[MAX_REPORT_SEGMENT_PAYLOAD_SIZE * 8 + 1]; + int nExpectedSegmentNumber; + bool bIsBle; +} SteamControllerPacketAssembler; + +#undef clamp +#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) + +#undef offsetof +#define offsetof(s, m) (size_t) & (((s *)0)->m) + +#ifdef DEBUG_STEAM_CONTROLLER +#define DPRINTF(format, ...) printf(format, ##__VA_ARGS__) +#define HEXDUMP(ptr, len) hexdump(ptr, len) +#else +#define DPRINTF(format, ...) +#define HEXDUMP(ptr, len) +#endif +#define printf SDL_Log + +#define MAX_REPORT_SEGMENT_SIZE (MAX_REPORT_SEGMENT_PAYLOAD_SIZE + 2) +#define CALC_REPORT_SEGMENT_NUM(index) ((index / MAX_REPORT_SEGMENT_PAYLOAD_SIZE) & 0x07) +#define REPORT_SEGMENT_DATA_FLAG 0x80 +#define REPORT_SEGMENT_LAST_FLAG 0x40 +#define BLE_REPORT_NUMBER 0x03 + +#define STEAMCONTROLLER_TRIGGER_MAX_ANALOG 26000 + +// Enable mouse mode when using the Steam Controller locally +#undef ENABLE_MOUSE_MODE + +// Wireless firmware quirk: the firmware intentionally signals "failure" when performing +// SET_FEATURE / GET_FEATURE when it actually means "pending radio roundtrip". The only +// way to make SET_FEATURE / GET_FEATURE work is to loop several times with a sleep. If +// it takes more than 50ms to get the response for SET_FEATURE / GET_FEATURE, we assume +// that the controller has failed. +#define RADIO_WORKAROUND_SLEEP_ATTEMPTS 50 +#define RADIO_WORKAROUND_SLEEP_DURATION_US 500 + +// This was defined by experimentation. 2000 seemed to work but to give that extra bit of margin, set to 3ms. +#define CONTROLLER_CONFIGURATION_DELAY_US 3000 + +static uint8_t GetSegmentHeader(int nSegmentNumber, bool bLastPacket) +{ + uint8_t header = REPORT_SEGMENT_DATA_FLAG; + header |= nSegmentNumber; + if (bLastPacket) { + header |= REPORT_SEGMENT_LAST_FLAG; + } + + return header; +} + +static void hexdump(const uint8_t *ptr, int len) +{ + HIDAPI_DumpPacket("Data", ptr, len); +} + +static void ResetSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler) +{ + SDL_memset(pAssembler->uBuffer, 0, sizeof(pAssembler->uBuffer)); + pAssembler->nExpectedSegmentNumber = 0; +} + +static void InitializeSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler, bool bIsBle) +{ + pAssembler->bIsBle = bIsBle; + ResetSteamControllerPacketAssembler(pAssembler); +} + +// Returns: +// <0 on error +// 0 on not ready +// Complete packet size on completion +static int WriteSegmentToSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler, const uint8_t *pSegment, int nSegmentLength) +{ + if (pAssembler->bIsBle) { + uint8_t uSegmentHeader = pSegment[1]; + int nSegmentNumber = uSegmentHeader & 0x07; + + HEXDUMP(pSegment, nSegmentLength); + + if (pSegment[0] != BLE_REPORT_NUMBER) { + // We may get keyboard/mouse input events until controller stops sending them + return 0; + } + + if (nSegmentLength != MAX_REPORT_SEGMENT_SIZE) { + printf("Bad segment size! %d\n", nSegmentLength); + hexdump(pSegment, nSegmentLength); + ResetSteamControllerPacketAssembler(pAssembler); + return -1; + } + + DPRINTF("GOT PACKET HEADER = 0x%x\n", uSegmentHeader); + + if (!(uSegmentHeader & REPORT_SEGMENT_DATA_FLAG)) { + // We get empty segments, just ignore them + return 0; + } + + if (nSegmentNumber != pAssembler->nExpectedSegmentNumber) { + ResetSteamControllerPacketAssembler(pAssembler); + + if (nSegmentNumber) { + // This happens occasionally + DPRINTF("Bad segment number, got %d, expected %d\n", + nSegmentNumber, pAssembler->nExpectedSegmentNumber); + return -1; + } + } + + SDL_memcpy(pAssembler->uBuffer + nSegmentNumber * MAX_REPORT_SEGMENT_PAYLOAD_SIZE, + pSegment + 2, // ignore header and report number + MAX_REPORT_SEGMENT_PAYLOAD_SIZE); + + if (uSegmentHeader & REPORT_SEGMENT_LAST_FLAG) { + pAssembler->nExpectedSegmentNumber = 0; + return (nSegmentNumber + 1) * MAX_REPORT_SEGMENT_PAYLOAD_SIZE; + } + + pAssembler->nExpectedSegmentNumber++; + } else { + // Just pass through + SDL_memcpy(pAssembler->uBuffer, + pSegment, + nSegmentLength); + return nSegmentLength; + } + + return 0; +} + +#define BLE_MAX_READ_RETRIES 8 + +static int SetFeatureReport(SDL_HIDAPI_Device *dev, const unsigned char uBuffer[65], int nActualDataLen) +{ + int nRet = -1; + + DPRINTF("SetFeatureReport %p %p %d\n", dev, uBuffer, nActualDataLen); + + if (dev->is_bluetooth) { + int nSegmentNumber = 0; + uint8_t uPacketBuffer[MAX_REPORT_SEGMENT_SIZE]; + const unsigned char *pBufferPtr = uBuffer + 1; + + if (nActualDataLen < 1) { + return -1; + } + + // Skip report number in data + nActualDataLen--; + + while (nActualDataLen > 0) { + int nBytesInPacket = nActualDataLen > MAX_REPORT_SEGMENT_PAYLOAD_SIZE ? MAX_REPORT_SEGMENT_PAYLOAD_SIZE : nActualDataLen; + + nActualDataLen -= nBytesInPacket; + + // Construct packet + SDL_memset(uPacketBuffer, 0, sizeof(uPacketBuffer)); + uPacketBuffer[0] = BLE_REPORT_NUMBER; + uPacketBuffer[1] = GetSegmentHeader(nSegmentNumber, nActualDataLen == 0); + SDL_memcpy(&uPacketBuffer[2], pBufferPtr, nBytesInPacket); + + pBufferPtr += nBytesInPacket; + nSegmentNumber++; + + nRet = SDL_hid_send_feature_report(dev->dev, uPacketBuffer, sizeof(uPacketBuffer)); + } + } else { + for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) { + nRet = SDL_hid_send_feature_report(dev->dev, uBuffer, 65); + if (nRet >= 0) { + break; + } + + SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000); + } + } + + DPRINTF("SetFeatureReport() ret = %d\n", nRet); + + return nRet; +} + +static int GetFeatureReport(SDL_HIDAPI_Device *dev, unsigned char uBuffer[65]) +{ + int nRet = -1; + + DPRINTF("GetFeatureReport( %p %p )\n", dev, uBuffer); + + if (dev->is_bluetooth) { + int nRetries = 0; + uint8_t uSegmentBuffer[MAX_REPORT_SEGMENT_SIZE + 1]; + uint8_t ucBytesToRead = MAX_REPORT_SEGMENT_SIZE; + uint8_t ucDataStartOffset = 0; + + SteamControllerPacketAssembler assembler; + InitializeSteamControllerPacketAssembler(&assembler, dev->is_bluetooth); + + // On Windows and macOS, BLE devices get 2 copies of the feature report ID, one that is removed by ReadFeatureReport, + // and one that's included in the buffer we receive. We pad the bytes to read and skip over the report ID + // if necessary. +#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_MACOS) + ++ucBytesToRead; + ++ucDataStartOffset; +#endif + + while (nRetries < BLE_MAX_READ_RETRIES) { + SDL_memset(uSegmentBuffer, 0, sizeof(uSegmentBuffer)); + uSegmentBuffer[0] = BLE_REPORT_NUMBER; + nRet = SDL_hid_get_feature_report(dev->dev, uSegmentBuffer, ucBytesToRead); + + DPRINTF("GetFeatureReport ble ret=%d\n", nRet); + HEXDUMP(uSegmentBuffer, nRet); + + // Zero retry counter if we got data + if (nRet > 2 && (uSegmentBuffer[ucDataStartOffset + 1] & REPORT_SEGMENT_DATA_FLAG)) { + nRetries = 0; + } else { + nRetries++; + } + + if (nRet > 0) { + int nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&assembler, + uSegmentBuffer + ucDataStartOffset, + nRet - ucDataStartOffset); + + if (nPacketLength > 0 && nPacketLength < 65) { + // Leave space for "report number" + uBuffer[0] = 0; + SDL_memcpy(uBuffer + 1, assembler.uBuffer, nPacketLength); + return nPacketLength; + } + } + } + printf("Could not get a full ble packet after %d retries\n", nRetries); + return -1; + } else { + SDL_memset(uBuffer, 0, 65); + + for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) { + nRet = SDL_hid_get_feature_report(dev->dev, uBuffer, 65); + if (nRet >= 0) { + break; + } + + SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000); + } + + DPRINTF("GetFeatureReport USB ret=%d\n", nRet); + HEXDUMP(uBuffer, nRet); + } + + return nRet; +} + +static int ReadResponse(SDL_HIDAPI_Device *dev, uint8_t uBuffer[65], int nExpectedResponse) +{ + for (int nRetries = 0; nRetries < 10; nRetries++) { + int nRet = GetFeatureReport(dev, uBuffer); + + DPRINTF("ReadResponse( %p %p 0x%x )\n", dev, uBuffer, nExpectedResponse); + + if (nRet < 0) { + continue; + } + + DPRINTF("ReadResponse got %d bytes of data: ", nRet); + HEXDUMP(uBuffer, nRet); + + if (uBuffer[1] != nExpectedResponse) { + continue; + } + + return nRet; + } + return -1; +} + +//--------------------------------------------------------------------------- +// Reset steam controller (unmap buttons and pads) and re-fetch capability bits +//--------------------------------------------------------------------------- +static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew, uint32_t *punUpdateRateUS) +{ + // Firmware quirk: Set Feature and Get Feature requests always require a 65-byte buffer. + unsigned char buf[65]; + unsigned int i; + int res = -1; + int nSettings = 0; + int nAttributesLength; + FeatureReportMsg *msg; + uint32_t unUpdateRateUS = 9000; // Good default rate + + DPRINTF("ResetSteamController hid=%p\n", dev); + + buf[0] = 0; + buf[1] = ID_GET_ATTRIBUTES_VALUES; + res = SetFeatureReport(dev, buf, 2); + if (res < 0) { + if (!bSuppressErrorSpew) { + printf("GET_ATTRIBUTES_VALUES failed for controller %p\n", dev); + } + return false; + } + + // Retrieve GET_ATTRIBUTES_VALUES result + // Wireless controller endpoints without a connected controller will return nAttrs == 0 + res = ReadResponse(dev, buf, ID_GET_ATTRIBUTES_VALUES); + if (res < 0 || buf[1] != ID_GET_ATTRIBUTES_VALUES) { + HEXDUMP(buf, res); + if (!bSuppressErrorSpew) { + printf("Bad GET_ATTRIBUTES_VALUES response for controller %p\n", dev); + } + return false; + } + + nAttributesLength = buf[2]; + if (nAttributesLength > res) { + if (!bSuppressErrorSpew) { + printf("Bad GET_ATTRIBUTES_VALUES response for controller %p\n", dev); + } + return false; + } + + msg = (FeatureReportMsg *)&buf[1]; + for (i = 0; i < (int)msg->header.length / sizeof(ControllerAttribute); ++i) { + uint8_t unAttribute = msg->payload.getAttributes.attributes[i].attributeTag; + uint32_t unValue = msg->payload.getAttributes.attributes[i].attributeValue; + + switch (unAttribute) { + case ATTRIB_UNIQUE_ID: + break; + case ATTRIB_PRODUCT_ID: + break; + case ATTRIB_CAPABILITIES: + break; + case ATTRIB_CONNECTION_INTERVAL_IN_US: + unUpdateRateUS = unValue; + break; + default: + break; + } + } + if (punUpdateRateUS) { + *punUpdateRateUS = unUpdateRateUS; + } + + // Clear digital button mappings + buf[0] = 0; + buf[1] = ID_CLEAR_DIGITAL_MAPPINGS; + res = SetFeatureReport(dev, buf, 2); + if (res < 0) { + if (!bSuppressErrorSpew) { + printf("CLEAR_DIGITAL_MAPPINGS failed for controller %p\n", dev); + } + return false; + } + + // Reset the default settings + SDL_memset(buf, 0, 65); + buf[1] = ID_LOAD_DEFAULT_SETTINGS; + buf[2] = 0; + res = SetFeatureReport(dev, buf, 3); + if (res < 0) { + if (!bSuppressErrorSpew) { + printf("LOAD_DEFAULT_SETTINGS failed for controller %p\n", dev); + } + return false; + } + + // Apply custom settings - clear trackpad modes (cancel mouse emulation), etc +#define ADD_SETTING(SETTING, VALUE) \ + buf[3 + nSettings * 3] = SETTING; \ + buf[3 + nSettings * 3 + 1] = ((uint16_t)VALUE) & 0xFF; \ + buf[3 + nSettings * 3 + 2] = ((uint16_t)VALUE) >> 8; \ + ++nSettings; + + SDL_memset(buf, 0, 65); + buf[1] = ID_SET_SETTINGS_VALUES; + ADD_SETTING(SETTING_WIRELESS_PACKET_VERSION, 2); + ADD_SETTING(SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE); +#ifdef ENABLE_MOUSE_MODE + ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE); + ADD_SETTING(SETTING_SMOOTH_ABSOLUTE_MOUSE, 1); + ADD_SETTING(SETTING_MOMENTUM_MAXIMUM_VELOCITY, 20000); // [0-20000] default 8000 + ADD_SETTING(SETTING_MOMENTUM_DECAY_AMOUNT, 50); // [0-50] default 5 +#else + ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE); + ADD_SETTING(SETTING_SMOOTH_ABSOLUTE_MOUSE, 0); +#endif + buf[2] = (unsigned char)(nSettings * 3); + + res = SetFeatureReport(dev, buf, 3 + nSettings * 3); + if (res < 0) { + if (!bSuppressErrorSpew) { + printf("SET_SETTINGS failed for controller %p\n", dev); + } + return false; + } + +#ifdef ENABLE_MOUSE_MODE + // Wait for ID_CLEAR_DIGITAL_MAPPINGS to be processed on the controller + bool bMappingsCleared = false; + int iRetry; + for (iRetry = 0; iRetry < 2; ++iRetry) { + SDL_memset(buf, 0, 65); + buf[1] = ID_GET_DIGITAL_MAPPINGS; + buf[2] = 1; // one byte - requesting from index 0 + buf[3] = 0; + res = SetFeatureReport(dev, buf, 4); + if (res < 0) { + printf("GET_DIGITAL_MAPPINGS failed for controller %p\n", dev); + return false; + } + + res = ReadResponse(dev, buf, ID_GET_DIGITAL_MAPPINGS); + if (res < 0 || buf[1] != ID_GET_DIGITAL_MAPPINGS) { + printf("Bad GET_DIGITAL_MAPPINGS response for controller %p\n", dev); + return false; + } + + // If the length of the digital mappings result is not 1 (index byte, no mappings) then clearing hasn't executed + if (buf[2] == 1 && buf[3] == 0xFF) { + bMappingsCleared = true; + break; + } + usleep(CONTROLLER_CONFIGURATION_DELAY_US); + } + + if (!bMappingsCleared && !bSuppressErrorSpew) { + printf("Warning: CLEAR_DIGITAL_MAPPINGS never completed for controller %p\n", dev); + } + + // Set our new mappings + SDL_memset(buf, 0, 65); + buf[1] = ID_SET_DIGITAL_MAPPINGS; + buf[2] = 6; // 2 settings x 3 bytes + buf[3] = IO_DIGITAL_BUTTON_RIGHT_TRIGGER; + buf[4] = DEVICE_MOUSE; + buf[5] = MOUSE_BTN_LEFT; + buf[6] = IO_DIGITAL_BUTTON_LEFT_TRIGGER; + buf[7] = DEVICE_MOUSE; + buf[8] = MOUSE_BTN_RIGHT; + + res = SetFeatureReport(dev, buf, 9); + if (res < 0) { + if (!bSuppressErrorSpew) { + printf("SET_DIGITAL_MAPPINGS failed for controller %p\n", dev); + } + return false; + } +#endif // ENABLE_MOUSE_MODE + + return true; +} + +//--------------------------------------------------------------------------- +// Read from a Steam Controller +//--------------------------------------------------------------------------- +static int ReadSteamController(SDL_hid_device *dev, uint8_t *pData, int nDataSize) +{ + SDL_memset(pData, 0, nDataSize); + pData[0] = BLE_REPORT_NUMBER; // hid_read will also overwrite this with the same value, 0x03 + return SDL_hid_read(dev, pData, nDataSize); +} + +//--------------------------------------------------------------------------- +// Set Steam Controller pairing state +//--------------------------------------------------------------------------- +static void SetPairingState(SDL_HIDAPI_Device *dev, bool bEnablePairing) +{ + unsigned char buf[65]; + SDL_memset(buf, 0, 65); + buf[1] = ID_ENABLE_PAIRING; + buf[2] = 2; // 2 payload bytes: bool + timeout + buf[3] = bEnablePairing ? 1 : 0; + buf[4] = bEnablePairing ? PAIRING_STATE_DURATION_SECONDS : 0; + SetFeatureReport(dev, buf, 5); +} + +//--------------------------------------------------------------------------- +// Commit Steam Controller pairing +//--------------------------------------------------------------------------- +static void CommitPairing(SDL_HIDAPI_Device *dev) +{ + unsigned char buf[65]; + SDL_memset(buf, 0, 65); + buf[1] = ID_DONGLE_COMMIT_DEVICE; + SetFeatureReport(dev, buf, 2); +} + +//--------------------------------------------------------------------------- +// Close a Steam Controller +//--------------------------------------------------------------------------- +static void CloseSteamController(SDL_HIDAPI_Device *dev) +{ + // Switch the Steam Controller back to lizard mode so it works with the OS + unsigned char buf[65]; + int nSettings = 0; + + // Reset digital button mappings + SDL_memset(buf, 0, 65); + buf[1] = ID_SET_DEFAULT_DIGITAL_MAPPINGS; + SetFeatureReport(dev, buf, 2); + + // Reset the default settings + SDL_memset(buf, 0, 65); + buf[1] = ID_LOAD_DEFAULT_SETTINGS; + buf[2] = 0; + SetFeatureReport(dev, buf, 3); + + // Reset mouse mode for lizard mode + SDL_memset(buf, 0, 65); + buf[1] = ID_SET_SETTINGS_VALUES; + ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE); + buf[2] = (unsigned char)(nSettings * 3); + SetFeatureReport(dev, buf, 3 + nSettings * 3); +} + +//--------------------------------------------------------------------------- +// Scale and clamp values to a range +//--------------------------------------------------------------------------- +static float RemapValClamped(float val, float A, float B, float C, float D) +{ + if (A == B) { + return (val - B) >= 0.0f ? D : C; + } else { + float cVal = (val - A) / (B - A); + cVal = clamp(cVal, 0.0f, 1.0f); + + return C + (D - C) * cVal; + } +} + +//--------------------------------------------------------------------------- +// Rotate the pad coordinates +//--------------------------------------------------------------------------- +static void RotatePad(int *pX, int *pY, float flAngleInRad) +{ + int origX = *pX, origY = *pY; + + *pX = (int)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY); + *pY = (int)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY); +} +static void RotatePadShort(short *pX, short *pY, float flAngleInRad) +{ + int origX = *pX, origY = *pY; + + *pX = (short)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY); + *pY = (short)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY); +} + +//--------------------------------------------------------------------------- +// Format the first part of the state packet +//--------------------------------------------------------------------------- +static void FormatStatePacketUntilGyro(SteamControllerStateInternal_t *pState, ValveControllerStatePacket_t *pStatePacket) +{ + int nLeftPadX; + int nLeftPadY; + int nRightPadX; + int nRightPadY; + int nPadOffset; + + // 15 degrees in rad + const float flRotationAngle = 0.261799f; + + SDL_memset(pState, 0, offsetof(SteamControllerStateInternal_t, sBatteryLevel)); + + // pState->eControllerType = m_eControllerType; + pState->eControllerType = 2; // k_eControllerType_SteamController; + pState->unPacketNum = pStatePacket->unPacketNum; + + // We have a chunk of trigger data in the packet format here, so zero it out afterwards + SDL_memcpy(&pState->ulButtons, &pStatePacket->ButtonTriggerData.ulButtons, 8); + pState->ulButtons &= ~0xFFFF000000LL; + + // The firmware uses this bit to tell us what kind of data is packed into the left two axes + if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) { + // Finger-down bit not set; "left pad" is actually trackpad + pState->sLeftPadX = pState->sPrevLeftPad[0] = pStatePacket->sLeftPadX; + pState->sLeftPadY = pState->sPrevLeftPad[1] = pStatePacket->sLeftPadY; + + if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) { + // The controller is interleaving both stick and pad data, both are active + pState->sLeftStickX = pState->sPrevLeftStick[0]; + pState->sLeftStickY = pState->sPrevLeftStick[1]; + } else { + // The stick is not active + pState->sPrevLeftStick[0] = 0; + pState->sPrevLeftStick[1] = 0; + } + } else { + // Finger-down bit not set; "left pad" is actually joystick + + // XXX there's a firmware bug where sometimes padX is 0 and padY is a large number (actually the battery voltage) + // If that happens skip this packet and report last frames stick + /* + if ( m_eControllerType == k_eControllerType_SteamControllerV2 && pStatePacket->sLeftPadY > 900 ) { + pState->sLeftStickX = pState->sPrevLeftStick[0]; + pState->sLeftStickY = pState->sPrevLeftStick[1]; + } else + */ + { + pState->sPrevLeftStick[0] = pState->sLeftStickX = pStatePacket->sLeftPadX; + pState->sPrevLeftStick[1] = pState->sLeftStickY = pStatePacket->sLeftPadY; + } + /* + if (m_eControllerType == k_eControllerType_SteamControllerV2) { + UpdateV2JoystickCap(&state); + } + */ + + if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) { + // The controller is interleaving both stick and pad data, both are active + pState->sLeftPadX = pState->sPrevLeftPad[0]; + pState->sLeftPadY = pState->sPrevLeftPad[1]; + } else { + // The trackpad is not active + pState->sPrevLeftPad[0] = 0; + pState->sPrevLeftPad[1] = 0; + + // Old controllers send trackpad click for joystick button when trackpad is not active + if (pState->ulButtons & STEAM_BUTTON_LEFTPAD_CLICKED_MASK) { + pState->ulButtons &= ~STEAM_BUTTON_LEFTPAD_CLICKED_MASK; + pState->ulButtons |= STEAM_JOYSTICK_BUTTON_MASK; + } + } + } + + // Fingerdown bit indicates if the packed left axis data was joystick or pad, + // but if we are interleaving both, the left finger is definitely on the pad. + if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) { + pState->ulButtons |= STEAM_LEFTPAD_FINGERDOWN_MASK; + } + + pState->sRightPadX = pStatePacket->sRightPadX; + pState->sRightPadY = pStatePacket->sRightPadY; + + nLeftPadX = pState->sLeftPadX; + nLeftPadY = pState->sLeftPadY; + nRightPadX = pState->sRightPadX; + nRightPadY = pState->sRightPadY; + + RotatePad(&nLeftPadX, &nLeftPadY, -flRotationAngle); + RotatePad(&nRightPadX, &nRightPadY, flRotationAngle); + + if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) { + nPadOffset = 1000; + } else { + nPadOffset = 0; + } + + pState->sLeftPadX = (short)clamp(nLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + pState->sLeftPadY = (short)clamp(nLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + + nPadOffset = 0; + if (pState->ulButtons & STEAM_RIGHTPAD_FINGERDOWN_MASK) { + nPadOffset = 1000; + } else { + nPadOffset = 0; + } + + pState->sRightPadX = (short)clamp(nRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + pState->sRightPadY = (short)clamp(nRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + + pState->sTriggerL = (unsigned short)RemapValClamped((float)((pStatePacket->ButtonTriggerData.Triggers.nLeft << 7) | pStatePacket->ButtonTriggerData.Triggers.nLeft), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16); + pState->sTriggerR = (unsigned short)RemapValClamped((float)((pStatePacket->ButtonTriggerData.Triggers.nRight << 7) | pStatePacket->ButtonTriggerData.Triggers.nRight), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16); +} + +//--------------------------------------------------------------------------- +// Update Steam Controller state from a BLE data packet, returns true if it parsed data +//--------------------------------------------------------------------------- +static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState) +{ + const float flRotationAngle = 0.261799f; + uint32_t ucOptionDataMask; + + pState->unPacketNum++; + ucOptionDataMask = (*pData++ & 0xF0); + ucOptionDataMask |= (uint32_t)(*pData++) << 8; + if (ucOptionDataMask & k_EBLEButtonChunk1) { + SDL_memcpy(&pState->ulButtons, pData, 3); + pData += 3; + } + if (ucOptionDataMask & k_EBLEButtonChunk2) { + // The middle 2 bytes of the button bits over the wire are triggers when over the wire and non-SC buttons in the internal controller state packet + pState->sTriggerL = (unsigned short)RemapValClamped((float)((pData[0] << 7) | pData[0]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16); + pState->sTriggerR = (unsigned short)RemapValClamped((float)((pData[1] << 7) | pData[1]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16); + pData += 2; + } + if (ucOptionDataMask & k_EBLEButtonChunk3) { + uint8_t *pButtonByte = (uint8_t *)&pState->ulButtons; + pButtonByte[5] = *pData++; + pButtonByte[6] = *pData++; + pButtonByte[7] = *pData++; + } + if (ucOptionDataMask & k_EBLELeftJoystickChunk) { + // This doesn't handle any of the special headcrab stuff for raw joystick which is OK for now since that FW doesn't support + // this protocol yet either + int nLength = sizeof(pState->sLeftStickX) + sizeof(pState->sLeftStickY); + SDL_memcpy(&pState->sLeftStickX, pData, nLength); + pData += nLength; + } + if (ucOptionDataMask & k_EBLELeftTrackpadChunk) { + int nLength = sizeof(pState->sLeftPadX) + sizeof(pState->sLeftPadY); + int nPadOffset; + SDL_memcpy(&pState->sLeftPadX, pData, nLength); + if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) { + nPadOffset = 1000; + } else { + nPadOffset = 0; + } + + RotatePadShort(&pState->sLeftPadX, &pState->sLeftPadY, -flRotationAngle); + pState->sLeftPadX = (short)clamp(pState->sLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + pState->sLeftPadY = (short)clamp(pState->sLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + pData += nLength; + } + if (ucOptionDataMask & k_EBLERightTrackpadChunk) { + int nLength = sizeof(pState->sRightPadX) + sizeof(pState->sRightPadY); + int nPadOffset = 0; + + SDL_memcpy(&pState->sRightPadX, pData, nLength); + + if (pState->ulButtons & STEAM_RIGHTPAD_FINGERDOWN_MASK) { + nPadOffset = 1000; + } else { + nPadOffset = 0; + } + + RotatePadShort(&pState->sRightPadX, &pState->sRightPadY, flRotationAngle); + pState->sRightPadX = (short)clamp(pState->sRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + pState->sRightPadY = (short)clamp(pState->sRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); + pData += nLength; + } + if (ucOptionDataMask & k_EBLEIMUAccelChunk) { + int nLength = sizeof(pState->sAccelX) + sizeof(pState->sAccelY) + sizeof(pState->sAccelZ); + SDL_memcpy(&pState->sAccelX, pData, nLength); + pData += nLength; + } + if (ucOptionDataMask & k_EBLEIMUGyroChunk) { + int nLength = sizeof(pState->sAccelX) + sizeof(pState->sAccelY) + sizeof(pState->sAccelZ); + SDL_memcpy(&pState->sGyroX, pData, nLength); + pData += nLength; + } + if (ucOptionDataMask & k_EBLEIMUQuatChunk) { + int nLength = sizeof(pState->sGyroQuatW) + sizeof(pState->sGyroQuatX) + sizeof(pState->sGyroQuatY) + sizeof(pState->sGyroQuatZ); + SDL_memcpy(&pState->sGyroQuatW, pData, nLength); + pData += nLength; + } + return true; +} + +//--------------------------------------------------------------------------- +// Update Steam Controller state from a data packet, returns true if it parsed data +//--------------------------------------------------------------------------- +static bool UpdateSteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState) +{ + ValveInReport_t *pInReport = (ValveInReport_t *)pData; + + if (pInReport->header.unReportVersion != k_ValveInReportMsgVersion) { + if ((pData[0] & 0x0F) == k_EBLEReportState) { + return UpdateBLESteamControllerState(pData, nDataSize, pState); + } + return false; + } + + if ((pInReport->header.ucType != ID_CONTROLLER_STATE) && + (pInReport->header.ucType != ID_CONTROLLER_BLE_STATE)) { + return false; + } + + if (pInReport->header.ucType == ID_CONTROLLER_STATE) { + ValveControllerStatePacket_t *pStatePacket = &pInReport->payload.controllerState; + + // No new data to process; indicate that we received a state packet, but otherwise do nothing. + if (pState->unPacketNum == pStatePacket->unPacketNum) { + return true; + } + + FormatStatePacketUntilGyro(pState, pStatePacket); + + pState->sAccelX = pStatePacket->sAccelX; + pState->sAccelY = pStatePacket->sAccelY; + pState->sAccelZ = pStatePacket->sAccelZ; + + pState->sGyroQuatW = pStatePacket->sGyroQuatW; + pState->sGyroQuatX = pStatePacket->sGyroQuatX; + pState->sGyroQuatY = pStatePacket->sGyroQuatY; + pState->sGyroQuatZ = pStatePacket->sGyroQuatZ; + + pState->sGyroX = pStatePacket->sGyroX; + pState->sGyroY = pStatePacket->sGyroY; + pState->sGyroZ = pStatePacket->sGyroZ; + + } else if (pInReport->header.ucType == ID_CONTROLLER_BLE_STATE) { + ValveControllerBLEStatePacket_t *pBLEStatePacket = &pInReport->payload.controllerBLEState; + ValveControllerStatePacket_t *pStatePacket = &pInReport->payload.controllerState; + + // No new data to process; indicate that we received a state packet, but otherwise do nothing. + if (pState->unPacketNum == pStatePacket->unPacketNum) { + return true; + } + + FormatStatePacketUntilGyro(pState, pStatePacket); + + switch (pBLEStatePacket->ucGyroDataType) { + case 1: + pState->sGyroQuatW = ((float)pBLEStatePacket->sGyro[0]); + pState->sGyroQuatX = ((float)pBLEStatePacket->sGyro[1]); + pState->sGyroQuatY = ((float)pBLEStatePacket->sGyro[2]); + pState->sGyroQuatZ = ((float)pBLEStatePacket->sGyro[3]); + break; + + case 2: + pState->sAccelX = pBLEStatePacket->sGyro[0]; + pState->sAccelY = pBLEStatePacket->sGyro[1]; + pState->sAccelZ = pBLEStatePacket->sGyro[2]; + break; + + case 3: + pState->sGyroX = pBLEStatePacket->sGyro[0]; + pState->sGyroY = pBLEStatePacket->sGyro[1]; + pState->sGyroZ = pBLEStatePacket->sGyro[2]; + break; + + default: + break; + } + } + + return true; +} + +/*****************************************************************************************************/ + +typedef struct +{ + SDL_HIDAPI_Device *device; + bool connected; + bool report_sensors; + uint32_t update_rate_in_us; + Uint64 sensor_timestamp; + Uint64 pairing_time; + + SteamControllerPacketAssembler m_assembler; + SteamControllerStateInternal_t m_state; + SteamControllerStateInternal_t m_last_state; +} SDL_DriverSteam_Context; + +static bool IsDongle(Uint16 product_id) +{ + return (product_id == USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE); +} + +static void HIDAPI_DriverSteam_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata); +} + +static void HIDAPI_DriverSteam_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata); +} + +static bool HIDAPI_DriverSteam_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT); +} + +static bool HIDAPI_DriverSteam_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (!SDL_IsJoystickSteamController(vendor_id, product_id)) { + return false; + } + + if (device->is_bluetooth) { + return true; + } + + if (IsDongle(product_id)) { + if (interface_number >= 1 && interface_number <= 4) { + // This is one of the wireless controller interfaces + return true; + } + } else { + if (interface_number == 2) { + // This is the controller interface (not mouse or keyboard) + return true; + } + } + return false; +} + +static void HIDAPI_DriverSteam_SetPairingState(SDL_DriverSteam_Context *ctx, bool enabled) +{ + // Only have one dongle in pairing mode at a time + static SDL_DriverSteam_Context *s_PairingContext = NULL; + + if (enabled && s_PairingContext != NULL) { + return; + } + + if (!enabled && s_PairingContext != ctx) { + return; + } + + if (ctx->connected) { + return; + } + + SetPairingState(ctx->device, enabled); + + if (enabled) { + ctx->pairing_time = SDL_GetTicks(); + s_PairingContext = ctx; + } else { + ctx->pairing_time = 0; + s_PairingContext = NULL; + } +} + +static void HIDAPI_DriverSteam_RenewPairingState(SDL_DriverSteam_Context *ctx) +{ + Uint64 now = SDL_GetTicks(); + + if (now >= ctx->pairing_time + PAIRING_STATE_DURATION_SECONDS * 1000) { + SetPairingState(ctx->device, true); + ctx->pairing_time = now; + } +} + +static void HIDAPI_DriverSteam_CommitPairing(SDL_DriverSteam_Context *ctx) +{ + CommitPairing(ctx->device); +} + +static void SDLCALL SDL_PairingEnabledHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)userdata; + bool enabled = SDL_GetStringBoolean(hint, false); + + HIDAPI_DriverSteam_SetPairingState(ctx, enabled); +} + +static bool HIDAPI_DriverSteam_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSteam_Context *ctx; + + ctx = (SDL_DriverSteam_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + device->context = ctx; + +#ifdef SDL_PLATFORM_WIN32 + if (device->serial) { + // We get a garbage serial number on Windows + SDL_free(device->serial); + device->serial = NULL; + } +#endif // SDL_PLATFORM_WIN32 + + HIDAPI_SetDeviceName(device, "Steam Controller"); + + // If this is a wireless dongle, request a wireless state update + if (IsDongle(device->product_id)) { + unsigned char buf[65]; + int res; + + buf[0] = 0; + buf[1] = ID_DONGLE_GET_WIRELESS_STATE; + res = SetFeatureReport(device, buf, 2); + if (res < 0) { + return SDL_SetError("Failed to send ID_DONGLE_GET_WIRELESS_STATE request"); + } + + for (int attempt = 0; attempt < 5; ++attempt) { + uint8_t data[128]; + + res = ReadSteamController(device->dev, data, sizeof(data)); + if (res == 0) { + SDL_Delay(1); + continue; + } + if (res < 0) { + break; + } + +#ifdef DEBUG_STEAM_PROTOCOL + HIDAPI_DumpPacket("Initial dongle packet: size = %d", data, res); +#endif + + if (D0G_IS_WIRELESS_CONNECT(data, res)) { + ctx->connected = true; + break; + } else if (D0G_IS_WIRELESS_DISCONNECT(data, res)) { + ctx->connected = false; + break; + } + } + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED, + SDL_PairingEnabledHintChanged, ctx); + } else { + // Wired and BLE controllers are always connected if HIDAPI can see them + ctx->connected = true; + } + + if (ctx->connected) { + return HIDAPI_JoystickConnected(device, NULL); + } else { + // We will enumerate any attached controllers in UpdateDevice() + return true; + } +} + +static int HIDAPI_DriverSteam_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverSteam_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static bool SetHomeLED(SDL_HIDAPI_Device *device, Uint8 value) +{ + unsigned char buf[65]; + int nSettings = 0; + + SDL_memset(buf, 0, 65); + buf[1] = ID_SET_SETTINGS_VALUES; + ADD_SETTING(SETTING_LED_USER_BRIGHTNESS, value); + buf[2] = (unsigned char)(nSettings * 3); + if (SetFeatureReport(device, buf, 3 + nSettings * 3) < 0) { + return SDL_SetError("Couldn't write feature report"); + } + return true; +} + +static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)userdata; + + if (hint && *hint) { + int value; + + if (SDL_strchr(hint, '.') != NULL) { + value = (int)(100.0f * SDL_atof(hint)); + if (value > 255) { + value = 255; + } + } else if (SDL_GetStringBoolean(hint, true)) { + value = 100; + } else { + value = 0; + } + SetHomeLED(ctx->device, (Uint8)value); + } +} + +static bool HIDAPI_DriverSteam_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; + float update_rate_in_hz = 0.0f; + + SDL_AssertJoysticksLocked(); + + ctx->report_sensors = false; + SDL_zero(ctx->m_assembler); + SDL_zero(ctx->m_state); + SDL_zero(ctx->m_last_state); + + if (!ResetSteamController(device, false, &ctx->update_rate_in_us)) { + SDL_SetError("Couldn't reset controller"); + return false; + } + if (ctx->update_rate_in_us > 0) { + update_rate_in_hz = 1000000.0f / ctx->update_rate_in_us; + } + + InitializeSteamControllerPacketAssembler(&ctx->m_assembler, device->is_bluetooth); + + // Initialize the joystick capabilities + joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_BUTTONS; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + if (IsDongle(device->product_id)) { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + } + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz); + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED, + SDL_HomeLEDHintChanged, ctx); + + return true; +} + +static bool HIDAPI_DriverSteam_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + // You should use the full Steam Input API for rumble support + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSteam_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverSteam_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + // You should use the full Steam Input API for extended capabilities + return 0; +} + +static bool HIDAPI_DriverSteam_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + // You should use the full Steam Input API for LED support + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSteam_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + if (size == 65) { + if (SetFeatureReport(device, data, size) < 0) { + return SDL_SetError("Couldn't write feature report"); + } + return true; + } + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSteam_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; + unsigned char buf[65]; + int nSettings = 0; + + SDL_memset(buf, 0, 65); + buf[1] = ID_SET_SETTINGS_VALUES; + if (enabled) { + ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO); + } else { + ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_OFF); + } + buf[2] = (unsigned char)(nSettings * 3); + if (SetFeatureReport(device, buf, 3 + nSettings * 3) < 0) { + return SDL_SetError("Couldn't write feature report"); + } + + ctx->report_sensors = enabled; + + return true; +} + +static bool ControllerConnected(SDL_HIDAPI_Device *device, SDL_Joystick **joystick) +{ + SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; + + if (!HIDAPI_JoystickConnected(device, NULL)) { + return false; + } + + // We'll automatically accept this controller if we're in pairing mode + HIDAPI_DriverSteam_CommitPairing(ctx); + + *joystick = SDL_GetJoystickFromID(device->joysticks[0]); + ctx->connected = true; + return true; +} + +static void ControllerDisconnected(SDL_HIDAPI_Device *device, SDL_Joystick **joystick) +{ + SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; + + if (device->joysticks) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + ctx->connected = false; + *joystick = NULL; +} + +static bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; + SDL_Joystick *joystick = NULL; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } + + if (ctx->pairing_time) { + HIDAPI_DriverSteam_RenewPairingState(ctx); + } + + for (;;) { + uint8_t data[128]; + int r, nPacketLength; + const Uint8 *pPacket; + + r = ReadSteamController(device->dev, data, sizeof(data)); + if (r == 0) { + break; + } + if (r < 0) { + // Failed to read from controller + ControllerDisconnected(device, &joystick); + return false; + } + +#ifdef DEBUG_STEAM_PROTOCOL + HIDAPI_DumpPacket("Steam Controller packet: size = %d", data, r); +#endif + + nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&ctx->m_assembler, data, r); + pPacket = ctx->m_assembler.uBuffer; + + if (nPacketLength > 0 && UpdateSteamControllerState(pPacket, nPacketLength, &ctx->m_state)) { + Uint64 timestamp = SDL_GetTicksNS(); + + if (!ctx->connected) { + // Maybe we missed a wireless status packet? + ControllerConnected(device, &joystick); + } + + if (!joystick) { + continue; + } + + if (ctx->m_state.ulButtons != ctx->m_last_state.ulButtons) { + Uint8 hat = 0; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, + ((ctx->m_state.ulButtons & STEAM_BUTTON_SOUTH_MASK) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, + ((ctx->m_state.ulButtons & STEAM_BUTTON_EAST_MASK) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, + ((ctx->m_state.ulButtons & STEAM_BUTTON_WEST_MASK) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, + ((ctx->m_state.ulButtons & STEAM_BUTTON_NORTH_MASK) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + ((ctx->m_state.ulButtons & STEAM_LEFT_BUMPER_MASK) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + ((ctx->m_state.ulButtons & STEAM_RIGHT_BUMPER_MASK) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, + ((ctx->m_state.ulButtons & STEAM_BUTTON_MENU_MASK) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, + ((ctx->m_state.ulButtons & STEAM_BUTTON_ESCAPE_MASK) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, + ((ctx->m_state.ulButtons & STEAM_BUTTON_STEAM_MASK) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, + ((ctx->m_state.ulButtons & STEAM_JOYSTICK_BUTTON_MASK) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE, + ((ctx->m_state.ulButtons & STEAM_BUTTON_BACK_LEFT_MASK) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE, + ((ctx->m_state.ulButtons & STEAM_BUTTON_BACK_RIGHT_MASK) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, + ((ctx->m_state.ulButtons & STEAM_BUTTON_RIGHTPAD_CLICKED_MASK) != 0)); + + if (ctx->m_state.ulButtons & STEAM_DPAD_UP_MASK) { + hat |= SDL_HAT_UP; + } + if (ctx->m_state.ulButtons & STEAM_DPAD_DOWN_MASK) { + hat |= SDL_HAT_DOWN; + } + if (ctx->m_state.ulButtons & STEAM_DPAD_LEFT_MASK) { + hat |= SDL_HAT_LEFT; + } + if (ctx->m_state.ulButtons & STEAM_DPAD_RIGHT_MASK) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (int)ctx->m_state.sTriggerL * 2 - 32768); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (int)ctx->m_state.sTriggerR * 2 - 32768); + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, ctx->m_state.sLeftStickX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~ctx->m_state.sLeftStickY); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, ctx->m_state.sRightPadX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~ctx->m_state.sRightPadY); + + if (ctx->report_sensors) { + float values[3]; + + ctx->sensor_timestamp += SDL_US_TO_NS(ctx->update_rate_in_us); + + values[0] = (ctx->m_state.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + values[1] = (ctx->m_state.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + values[2] = (ctx->m_state.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp, values, 3); + + values[0] = (ctx->m_state.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + values[1] = (ctx->m_state.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + values[2] = (-ctx->m_state.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp, values, 3); + } + + ctx->m_last_state = ctx->m_state; + } else if (!ctx->connected && D0G_IS_WIRELESS_CONNECT(pPacket, nPacketLength)) { + ControllerConnected(device, &joystick); + } else if (ctx->connected && D0G_IS_WIRELESS_DISCONNECT(pPacket, nPacketLength)) { + ControllerDisconnected(device, &joystick); + } + } + return true; +} + +static void HIDAPI_DriverSteam_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED, + SDL_HomeLEDHintChanged, ctx); + + CloseSteamController(device); +} + +static void HIDAPI_DriverSteam_FreeDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; + + if (IsDongle(device->product_id)) { + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED, + SDL_PairingEnabledHintChanged, ctx); + + HIDAPI_DriverSteam_SetPairingState(ctx, false); + } +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam = { + SDL_HINT_JOYSTICK_HIDAPI_STEAM, + true, + HIDAPI_DriverSteam_RegisterHints, + HIDAPI_DriverSteam_UnregisterHints, + HIDAPI_DriverSteam_IsEnabled, + HIDAPI_DriverSteam_IsSupportedDevice, + HIDAPI_DriverSteam_InitDevice, + HIDAPI_DriverSteam_GetDevicePlayerIndex, + HIDAPI_DriverSteam_SetDevicePlayerIndex, + HIDAPI_DriverSteam_UpdateDevice, + HIDAPI_DriverSteam_OpenJoystick, + HIDAPI_DriverSteam_RumbleJoystick, + HIDAPI_DriverSteam_RumbleJoystickTriggers, + HIDAPI_DriverSteam_GetJoystickCapabilities, + HIDAPI_DriverSteam_SetJoystickLED, + HIDAPI_DriverSteam_SendJoystickEffect, + HIDAPI_DriverSteam_SetSensorsEnabled, + HIDAPI_DriverSteam_CloseJoystick, + HIDAPI_DriverSteam_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_STEAM + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam_hori.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam_hori.c new file mode 100644 index 0000000..0cd192d --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam_hori.c @@ -0,0 +1,415 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" +#include "../SDL_joystick_c.h" + +#ifdef SDL_JOYSTICK_HIDAPI_STEAM_HORI + +/* Define this if you want to log all packets from the controller */ +/*#define DEBUG_HORI_PROTOCOL*/ + +#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) + +enum +{ + SDL_GAMEPAD_BUTTON_HORI_QAM = 11, + SDL_GAMEPAD_BUTTON_HORI_FR, + SDL_GAMEPAD_BUTTON_HORI_FL, + SDL_GAMEPAD_BUTTON_HORI_M1, + SDL_GAMEPAD_BUTTON_HORI_M2, + SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, + SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, + SDL_GAMEPAD_NUM_HORI_BUTTONS +}; + +typedef struct +{ + Uint8 last_state[USB_PACKET_LENGTH]; + Uint64 sensor_ticks; + Uint32 last_tick; + bool wireless; + bool serial_needs_init; +} SDL_DriverSteamHori_Context; + +static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device); + +static void HIDAPI_DriverSteamHori_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata); +} + +static void HIDAPI_DriverSteamHori_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata); +} + +static bool HIDAPI_DriverSteamHori_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverSteamHori_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + return SDL_IsJoystickHoriSteamController(vendor_id, product_id); +} + +static bool HIDAPI_DriverSteamHori_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSteamHori_Context *ctx; + + ctx = (SDL_DriverSteamHori_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + + device->context = ctx; + ctx->serial_needs_init = true; + + HIDAPI_SetDeviceName(device, "Wireless HORIPAD For Steam"); + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverSteamHori_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverSteamHori_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static bool HIDAPI_DriverSteamHori_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + SDL_zeroa(ctx->last_state); + + /* Initialize the joystick capabilities */ + joystick->nbuttons = SDL_GAMEPAD_NUM_HORI_BUTTONS; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT; + + if (ctx->wireless && device->serial) { + joystick->serial = SDL_strdup(device->serial); + ctx->serial_needs_init = false; + } else if (!ctx->wireless) { + // Need to actual read from the device to init the serial + HIDAPI_DriverSteamHori_UpdateDevice(device); + } + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + + return true; +} + +static bool HIDAPI_DriverSteamHori_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + // Device doesn't support rumble + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSteamHori_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverSteamHori_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return 0; +} + +static bool HIDAPI_DriverSteamHori_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSteamHori_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return true; +} + +#undef clamp +#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) + +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) +#endif + +//--------------------------------------------------------------------------- +// Scale and clamp values to a range +//--------------------------------------------------------------------------- +static float RemapValClamped(float val, float A, float B, float C, float D) +{ + if (A == B) { + return (val - B) >= 0.0f ? D : C; + } else { + float cVal = (val - A) / (B - A); + cVal = clamp(cVal, 0.0f, 1.0f); + + return C + (D - C) * cVal; + } +} + +#define REPORT_HEADER_USB 0x07 +#define REPORT_HEADER_BT 0x00 + +static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSteamHori_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + // Make sure it's gamepad state and not OTA FW update info + if (data[0] != REPORT_HEADER_USB && data[0] != REPORT_HEADER_BT) { + /* We don't know how to handle this report */ + return; + } + + #define READ_STICK_AXIS(offset) \ + (data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), -0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(1); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(2); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(3); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(4); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + + if (ctx->last_state[5] != data[5]) { + Uint8 hat; + + switch (data[5] & 0xF) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_QAM, ((data[5] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x80) != 0)); + + } + + if (ctx->last_state[6] != data[6]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[6] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M1 /* M1 */, ((data[6] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[6] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[6] & 0x08) != 0)); + + // TODO: can we handle the digital trigger mode? The data seems to come through analog regardless of the trigger state + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[6] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x80) != 0)); + } + + if (ctx->last_state[7] != data[7]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[7] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[7] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[7] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M2, ((data[7] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, ((data[7] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, ((data[7] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FR, ((data[7] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0)); + } + + if (!ctx->wireless && ctx->serial_needs_init) { + char serial[18]; + (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + data[38], data[39], data[40], data[41], data[42], data[43]); + + joystick->serial = SDL_strdup(serial); + ctx->serial_needs_init = false; + } + +#define READ_TRIGGER_AXIS(offset) \ + (Sint16)(((int)data[offset] * 257) - 32768) + { + axis = READ_TRIGGER_AXIS(8); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + axis = READ_TRIGGER_AXIS(9); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + } +#undef READ_TRIGGER_AXIS + + if (1) { + Uint64 sensor_timestamp; + float imu_data[3]; + + /* 16-bit timestamp */ + Uint32 delta; + Uint16 tick = LOAD16(data[10], + data[11]); + if (ctx->last_tick < tick) { + delta = (tick - ctx->last_tick); + } else { + delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1); + } + + ctx->last_tick = tick; + ctx->sensor_ticks += delta; + + /* Sensor timestamp is in 1us units, but there seems to be some issues with the values reported from the device */ + sensor_timestamp = timestamp; // if the values were good we woudl call SDL_US_TO_NS(ctx->sensor_ticks); + + const float accelScale = SDL_STANDARD_GRAVITY * 8 / 32768.0f; + const float gyroScale = DEG2RAD(2048); + + imu_data[1] = RemapValClamped(-1.0f * LOAD16(data[12], data[13]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); + imu_data[2] = RemapValClamped(-1.0f * LOAD16(data[14], data[15]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); + imu_data[0] = RemapValClamped(-1.0f * LOAD16(data[16], data[17]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); + + + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, imu_data, 3); + + // SDL_Log("%u %f, %f, %f ", data[0], imu_data[0], imu_data[1], imu_data[2] ); + imu_data[2] = LOAD16(data[18], data[19]) * accelScale; + imu_data[1] = -1 * LOAD16(data[20], data[21]) * accelScale; + imu_data[0] = LOAD16(data[22], data[23]) * accelScale; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, imu_data, 3); + } + + if (ctx->last_state[24] != data[24]) { + bool bCharging = (data[24] & 0x10) != 0; + int percent = (data[24] & 0xF) * 10; + SDL_PowerState state; + if (bCharging) { + state = SDL_POWERSTATE_CHARGING; + } else if (ctx->wireless) { + state = SDL_POWERSTATE_ON_BATTERY; + } else { + state = SDL_POWERSTATE_CHARGED; + } + + SDL_SendJoystickPowerInfo(joystick, state, percent); + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_HORI_PROTOCOL + HIDAPI_DumpPacket("Google Hori packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + HIDAPI_DriverSteamHori_HandleStatePacket(joystick, ctx, data, size); + } + + if (size < 0) { + /* Read error, device is disconnected */ + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverSteamHori_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_DriverSteamHori_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori = { + SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, + true, + HIDAPI_DriverSteamHori_RegisterHints, + HIDAPI_DriverSteamHori_UnregisterHints, + HIDAPI_DriverSteamHori_IsEnabled, + HIDAPI_DriverSteamHori_IsSupportedDevice, + HIDAPI_DriverSteamHori_InitDevice, + HIDAPI_DriverSteamHori_GetDevicePlayerIndex, + HIDAPI_DriverSteamHori_SetDevicePlayerIndex, + HIDAPI_DriverSteamHori_UpdateDevice, + HIDAPI_DriverSteamHori_OpenJoystick, + HIDAPI_DriverSteamHori_RumbleJoystick, + HIDAPI_DriverSteamHori_RumbleJoystickTriggers, + HIDAPI_DriverSteamHori_GetJoystickCapabilities, + HIDAPI_DriverSteamHori_SetJoystickLED, + HIDAPI_DriverSteamHori_SendJoystickEffect, + HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled, + HIDAPI_DriverSteamHori_CloseJoystick, + HIDAPI_DriverSteamHori_FreeDevice, +}; + +#endif /* SDL_JOYSTICK_HIDAPI_STEAM_HORI */ + +#endif /* SDL_JOYSTICK_HIDAPI */ diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steamdeck.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steamdeck.c new file mode 100644 index 0000000..75a13cc --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steamdeck.c @@ -0,0 +1,451 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2023 Max Maisel + + 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_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" + +#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK + +/*****************************************************************************************************/ + +#include "steam/controller_constants.h" +#include "steam/controller_structs.h" + +enum +{ + SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM = 11, + SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1, + SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1, + SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2, + SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2, + SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS, +}; + +typedef enum +{ + STEAMDECK_LBUTTON_R2 = 0x00000001, + STEAMDECK_LBUTTON_L2 = 0x00000002, + STEAMDECK_LBUTTON_R = 0x00000004, + STEAMDECK_LBUTTON_L = 0x00000008, + STEAMDECK_LBUTTON_Y = 0x00000010, + STEAMDECK_LBUTTON_B = 0x00000020, + STEAMDECK_LBUTTON_X = 0x00000040, + STEAMDECK_LBUTTON_A = 0x00000080, + STEAMDECK_LBUTTON_DPAD_UP = 0x00000100, + STEAMDECK_LBUTTON_DPAD_RIGHT = 0x00000200, + STEAMDECK_LBUTTON_DPAD_LEFT = 0x00000400, + STEAMDECK_LBUTTON_DPAD_DOWN = 0x00000800, + STEAMDECK_LBUTTON_VIEW = 0x00001000, + STEAMDECK_LBUTTON_STEAM = 0x00002000, + STEAMDECK_LBUTTON_MENU = 0x00004000, + STEAMDECK_LBUTTON_L5 = 0x00008000, + STEAMDECK_LBUTTON_R5 = 0x00010000, + STEAMDECK_LBUTTON_LEFT_PAD = 0x00020000, + STEAMDECK_LBUTTON_RIGHT_PAD = 0x00040000, + STEAMDECK_LBUTTON_L3 = 0x00400000, + STEAMDECK_LBUTTON_R3 = 0x04000000, + + STEAMDECK_HBUTTON_L4 = 0x00000200, + STEAMDECK_HBUTTON_R4 = 0x00000400, + STEAMDECK_HBUTTON_QAM = 0x00040000, +} SteamDeckButtons; + +typedef struct +{ + Uint32 update_rate_us; + Uint32 sensor_timestamp_us; + Uint64 last_button_state; + Uint8 watchdog_counter; +} SDL_DriverSteamDeck_Context; + +static bool DisableDeckLizardMode(SDL_hid_device *dev) +{ + int rc; + Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 }; + FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); + + msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS; + + rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) + return false; + + msg->header.type = ID_SET_SETTINGS_VALUES; + msg->header.length = 5 * sizeof(ControllerSetting); + msg->payload.setSettingsValues.settings[0].settingNum = SETTING_SMOOTH_ABSOLUTE_MOUSE; + msg->payload.setSettingsValues.settings[0].settingValue = 0; + msg->payload.setSettingsValues.settings[1].settingNum = SETTING_LEFT_TRACKPAD_MODE; + msg->payload.setSettingsValues.settings[1].settingValue = TRACKPAD_NONE; + msg->payload.setSettingsValues.settings[2].settingNum = SETTING_RIGHT_TRACKPAD_MODE; // disable mouse + msg->payload.setSettingsValues.settings[2].settingValue = TRACKPAD_NONE; + msg->payload.setSettingsValues.settings[3].settingNum = SETTING_LEFT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad + msg->payload.setSettingsValues.settings[3].settingValue = 0xFFFF; + msg->payload.setSettingsValues.settings[4].settingNum = SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad + msg->payload.setSettingsValues.settings[4].settingValue = 0xFFFF; + + rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) + return false; + + // There may be a lingering report read back after changing settings. + // Discard it. + SDL_hid_get_feature_report(dev, buffer, sizeof(buffer)); + + return true; +} + +static bool FeedDeckLizardWatchdog(SDL_hid_device *dev) +{ + int rc; + Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 }; + FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); + + msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS; + + rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) + return false; + + msg->header.type = ID_SET_SETTINGS_VALUES; + msg->header.length = 1 * sizeof(ControllerSetting); + msg->payload.setSettingsValues.settings[0].settingNum = SETTING_RIGHT_TRACKPAD_MODE; + msg->payload.setSettingsValues.settings[0].settingValue = TRACKPAD_NONE; + + rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) + return false; + + // There may be a lingering report read back after changing settings. + // Discard it. + SDL_hid_get_feature_report(dev, buffer, sizeof(buffer)); + + return true; +} + +static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device, + SDL_Joystick *joystick, + ValveInReport_t *pInReport) +{ + float values[3]; + SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context; + Uint64 timestamp = SDL_GetTicksNS(); + + if (pInReport->payload.deckState.ulButtons != ctx->last_button_state) { + Uint8 hat = 0; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_A) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_B) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_X) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_Y) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_VIEW) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_MENU) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_STEAM) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM, + ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_QAM) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L3) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R3) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1, + ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_R4) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1, + ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_L4) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R5) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2, + ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L5) != 0)); + + if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_UP) { + hat |= SDL_HAT_UP; + } + if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_DOWN) { + hat |= SDL_HAT_DOWN; + } + if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_LEFT) { + hat |= SDL_HAT_LEFT; + } + if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_RIGHT) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + ctx->last_button_state = pInReport->payload.deckState.ulButtons; + } + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, + (int)pInReport->payload.deckState.sTriggerRawL * 2 - 32768); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, + (int)pInReport->payload.deckState.sTriggerRawR * 2 - 32768); + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, + pInReport->payload.deckState.sLeftStickX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, + -pInReport->payload.deckState.sLeftStickY); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, + pInReport->payload.deckState.sRightStickX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, + -pInReport->payload.deckState.sRightStickY); + + ctx->sensor_timestamp_us += ctx->update_rate_us; + + values[0] = (pInReport->payload.deckState.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + values[1] = (pInReport->payload.deckState.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + values[2] = (-pInReport->payload.deckState.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_us, values, 3); + + values[0] = (pInReport->payload.deckState.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + values[1] = (pInReport->payload.deckState.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + values[2] = (-pInReport->payload.deckState.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_us, values, 3); +} + +/*****************************************************************************************************/ + +static void HIDAPI_DriverSteamDeck_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata); +} + +static void HIDAPI_DriverSteamDeck_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata); +} + +static bool HIDAPI_DriverSteamDeck_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverSteamDeck_IsSupportedDevice( + SDL_HIDAPI_Device *device, + const char *name, + SDL_GamepadType type, + Uint16 vendor_id, + Uint16 product_id, + Uint16 version, + int interface_number, + int interface_class, + int interface_subclass, + int interface_protocol) +{ + return SDL_IsJoystickSteamDeck(vendor_id, product_id); +} + +static bool HIDAPI_DriverSteamDeck_InitDevice(SDL_HIDAPI_Device *device) +{ + int size; + Uint8 data[64]; + SDL_DriverSteamDeck_Context *ctx; + + ctx = (SDL_DriverSteamDeck_Context *)SDL_calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return false; + } + + // Always 1kHz according to USB descriptor, but actually about 4 ms. + ctx->update_rate_us = 4000; + + device->context = ctx; + + // Read a report to see if this is the correct endpoint. + // Mouse, Keyboard and Controller have the same VID/PID but + // only the controller hidraw device receives hid reports. + size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16); + if (size == 0) + return false; + + if (!DisableDeckLizardMode(device->dev)) + return false; + + HIDAPI_SetDeviceName(device, "Steam Deck"); + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverSteamDeck_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverSteamDeck_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context; + SDL_Joystick *joystick = NULL; + int r; + uint8_t data[64]; + ValveInReport_t *pInReport = (ValveInReport_t *)data; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + if (joystick == NULL) { + return false; + } + } else { + return false; + } + + if (ctx->watchdog_counter++ > 200) { + ctx->watchdog_counter = 0; + if (!FeedDeckLizardWatchdog(device->dev)) + return false; + } + + SDL_memset(data, 0, sizeof(data)); + + do { + r = SDL_hid_read(device->dev, data, sizeof(data)); + + if (r < 0) { + // Failed to read from controller + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + return false; + } else if (r == 64 && + pInReport->header.unReportVersion == k_ValveInReportMsgVersion && + pInReport->header.ucType == ID_CONTROLLER_DECK_STATE && + pInReport->header.ucLength == 64) { + HIDAPI_DriverSteamDeck_HandleState(device, joystick, pInReport); + } + } while (r > 0); + + return true; +} + +static bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context; + float update_rate_in_hz = 1.0f / (float)(ctx->update_rate_us) * 1.0e6f; + + SDL_AssertJoysticksLocked(); + + // Initialize the joystick capabilities + joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz); + + return true; +} + +static bool HIDAPI_DriverSteamDeck_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + int rc; + Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 }; + FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); + + msg->header.type = ID_TRIGGER_RUMBLE_CMD; + msg->payload.simpleRumble.unRumbleType = 0; + msg->payload.simpleRumble.unIntensity = HAPTIC_INTENSITY_SYSTEM; + msg->payload.simpleRumble.unLeftMotorSpeed = low_frequency_rumble; + msg->payload.simpleRumble.unRightMotorSpeed = high_frequency_rumble; + msg->payload.simpleRumble.nLeftGain = 2; + msg->payload.simpleRumble.nRightGain = 0; + + rc = SDL_hid_send_feature_report(device->dev, buffer, sizeof(buffer)); + if (rc != sizeof(buffer)) + return false; + return true; +} + +static bool HIDAPI_DriverSteamDeck_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverSteamDeck_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return SDL_JOYSTICK_CAP_RUMBLE; +} + +static bool HIDAPI_DriverSteamDeck_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSteamDeck_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSteamDeck_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + // On steam deck, sensors are enabled by default. Nothing to do here. + return true; +} + +static void HIDAPI_DriverSteamDeck_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + // Lizard mode id automatically re-enabled by watchdog. Nothing to do here. +} + +static void HIDAPI_DriverSteamDeck_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck = { + SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, + true, + HIDAPI_DriverSteamDeck_RegisterHints, + HIDAPI_DriverSteamDeck_UnregisterHints, + HIDAPI_DriverSteamDeck_IsEnabled, + HIDAPI_DriverSteamDeck_IsSupportedDevice, + HIDAPI_DriverSteamDeck_InitDevice, + HIDAPI_DriverSteamDeck_GetDevicePlayerIndex, + HIDAPI_DriverSteamDeck_SetDevicePlayerIndex, + HIDAPI_DriverSteamDeck_UpdateDevice, + HIDAPI_DriverSteamDeck_OpenJoystick, + HIDAPI_DriverSteamDeck_RumbleJoystick, + HIDAPI_DriverSteamDeck_RumbleJoystickTriggers, + HIDAPI_DriverSteamDeck_GetJoystickCapabilities, + HIDAPI_DriverSteamDeck_SetJoystickLED, + HIDAPI_DriverSteamDeck_SendJoystickEffect, + HIDAPI_DriverSteamDeck_SetSensorsEnabled, + HIDAPI_DriverSteamDeck_CloseJoystick, + HIDAPI_DriverSteamDeck_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_STEAMDECK + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_switch.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_switch.c new file mode 100644 index 0000000..0e7b823 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_switch.c @@ -0,0 +1,2859 @@ +/* + 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. +*/ +/* This driver supports the Nintendo Switch Pro controller. + Code and logic contributed by Valve Corporation under the SDL zlib license. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" +#include "SDL_hidapi_nintendo.h" + +#ifdef SDL_JOYSTICK_HIDAPI_SWITCH + +// Define this if you want to log all packets from the controller +// #define DEBUG_SWITCH_PROTOCOL + +// Define this to get log output for rumble logic +// #define DEBUG_RUMBLE + +/* The initialization sequence doesn't appear to work correctly on Windows unless + the reads and writes are on the same thread. + + ... and now I can't reproduce this, so I'm leaving it in, but disabled for now. + */ +// #define SWITCH_SYNCHRONOUS_WRITES + +/* How often you can write rumble commands to the controller. + If you send commands more frequently than this, you can turn off the controller + in Bluetooth mode, or the motors can miss the command in USB mode. + */ +#define RUMBLE_WRITE_FREQUENCY_MS 30 + +// How often you have to refresh a long duration rumble to keep the motors running +#define RUMBLE_REFRESH_FREQUENCY_MS 50 + +#define SWITCH_GYRO_SCALE 14.2842f +#define SWITCH_ACCEL_SCALE 4096.f + +#define SWITCH_GYRO_SCALE_OFFSET 13371.0f +#define SWITCH_GYRO_SCALE_MULT 936.0f +#define SWITCH_ACCEL_SCALE_OFFSET 16384.0f +#define SWITCH_ACCEL_SCALE_MULT 4.0f + +enum +{ + SDL_GAMEPAD_BUTTON_SWITCH_SHARE = 11, + SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, + SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, + SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, + SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, + SDL_GAMEPAD_NUM_SWITCH_BUTTONS, +}; + +typedef enum +{ + k_eSwitchInputReportIDs_SubcommandReply = 0x21, + k_eSwitchInputReportIDs_FullControllerState = 0x30, + k_eSwitchInputReportIDs_FullControllerAndMcuState = 0x31, + k_eSwitchInputReportIDs_SimpleControllerState = 0x3F, + k_eSwitchInputReportIDs_CommandAck = 0x81, +} ESwitchInputReportIDs; + +typedef enum +{ + k_eSwitchOutputReportIDs_RumbleAndSubcommand = 0x01, + k_eSwitchOutputReportIDs_Rumble = 0x10, + k_eSwitchOutputReportIDs_Proprietary = 0x80, +} ESwitchOutputReportIDs; + +typedef enum +{ + k_eSwitchSubcommandIDs_BluetoothManualPair = 0x01, + k_eSwitchSubcommandIDs_RequestDeviceInfo = 0x02, + k_eSwitchSubcommandIDs_SetInputReportMode = 0x03, + k_eSwitchSubcommandIDs_SetHCIState = 0x06, + k_eSwitchSubcommandIDs_SPIFlashRead = 0x10, + k_eSwitchSubcommandIDs_SetPlayerLights = 0x30, + k_eSwitchSubcommandIDs_SetHomeLight = 0x38, + k_eSwitchSubcommandIDs_EnableIMU = 0x40, + k_eSwitchSubcommandIDs_SetIMUSensitivity = 0x41, + k_eSwitchSubcommandIDs_EnableVibration = 0x48, +} ESwitchSubcommandIDs; + +typedef enum +{ + k_eSwitchProprietaryCommandIDs_Status = 0x01, + k_eSwitchProprietaryCommandIDs_Handshake = 0x02, + k_eSwitchProprietaryCommandIDs_HighSpeed = 0x03, + k_eSwitchProprietaryCommandIDs_ForceUSB = 0x04, + k_eSwitchProprietaryCommandIDs_ClearUSB = 0x05, + k_eSwitchProprietaryCommandIDs_ResetMCU = 0x06, +} ESwitchProprietaryCommandIDs; + +#define k_unSwitchOutputPacketDataLength 49 +#define k_unSwitchMaxOutputPacketLength 64 +#define k_unSwitchBluetoothPacketLength k_unSwitchOutputPacketDataLength +#define k_unSwitchUSBPacketLength k_unSwitchMaxOutputPacketLength + +#define k_unSPIStickFactoryCalibrationStartOffset 0x603D +#define k_unSPIStickFactoryCalibrationEndOffset 0x604E +#define k_unSPIStickFactoryCalibrationLength (k_unSPIStickFactoryCalibrationEndOffset - k_unSPIStickFactoryCalibrationStartOffset + 1) + +#define k_unSPIStickUserCalibrationStartOffset 0x8010 +#define k_unSPIStickUserCalibrationEndOffset 0x8025 +#define k_unSPIStickUserCalibrationLength (k_unSPIStickUserCalibrationEndOffset - k_unSPIStickUserCalibrationStartOffset + 1) + +#define k_unSPIIMUScaleStartOffset 0x6020 +#define k_unSPIIMUScaleEndOffset 0x6037 +#define k_unSPIIMUScaleLength (k_unSPIIMUScaleEndOffset - k_unSPIIMUScaleStartOffset + 1) + +#define k_unSPIIMUUserScaleStartOffset 0x8026 +#define k_unSPIIMUUserScaleEndOffset 0x8039 +#define k_unSPIIMUUserScaleLength (k_unSPIIMUUserScaleEndOffset - k_unSPIIMUUserScaleStartOffset + 1) + +#pragma pack(1) +typedef struct +{ + Uint8 rgucButtons[2]; + Uint8 ucStickHat; + Uint8 rgucJoystickLeft[2]; + Uint8 rgucJoystickRight[2]; +} SwitchInputOnlyControllerStatePacket_t; + +typedef struct +{ + Uint8 rgucButtons[2]; + Uint8 ucStickHat; + Sint16 sJoystickLeft[2]; + Sint16 sJoystickRight[2]; +} SwitchSimpleStatePacket_t; + +typedef struct +{ + Uint8 ucCounter; + Uint8 ucBatteryAndConnection; + Uint8 rgucButtons[3]; + Uint8 rgucJoystickLeft[3]; + Uint8 rgucJoystickRight[3]; + Uint8 ucVibrationCode; +} SwitchControllerStatePacket_t; + +typedef struct +{ + SwitchControllerStatePacket_t controllerState; + + struct + { + Sint16 sAccelX; + Sint16 sAccelY; + Sint16 sAccelZ; + + Sint16 sGyroX; + Sint16 sGyroY; + Sint16 sGyroZ; + } imuState[3]; +} SwitchStatePacket_t; + +typedef struct +{ + Uint32 unAddress; + Uint8 ucLength; +} SwitchSPIOpData_t; + +typedef struct +{ + SwitchControllerStatePacket_t m_controllerState; + + Uint8 ucSubcommandAck; + Uint8 ucSubcommandID; + +#define k_unSubcommandDataBytes 35 + union + { + Uint8 rgucSubcommandData[k_unSubcommandDataBytes]; + + struct + { + SwitchSPIOpData_t opData; + Uint8 rgucReadData[k_unSubcommandDataBytes - sizeof(SwitchSPIOpData_t)]; + } spiReadData; + + struct + { + Uint8 rgucFirmwareVersion[2]; + Uint8 ucDeviceType; + Uint8 ucFiller1; + Uint8 rgucMACAddress[6]; + Uint8 ucFiller2; + Uint8 ucColorLocation; + } deviceInfo; + + struct + { + SwitchSPIOpData_t opData; + Uint8 rgucLeftCalibration[9]; + Uint8 rgucRightCalibration[9]; + } stickFactoryCalibration; + + struct + { + SwitchSPIOpData_t opData; + Uint8 rgucLeftMagic[2]; + Uint8 rgucLeftCalibration[9]; + Uint8 rgucRightMagic[2]; + Uint8 rgucRightCalibration[9]; + } stickUserCalibration; + }; +} SwitchSubcommandInputPacket_t; + +typedef struct +{ + Uint8 ucPacketType; + Uint8 ucCommandID; + Uint8 ucFiller; + + Uint8 ucDeviceType; + Uint8 rgucMACAddress[6]; +} SwitchProprietaryStatusPacket_t; + +typedef struct +{ + Uint8 rgucData[4]; +} SwitchRumbleData_t; + +typedef struct +{ + Uint8 ucPacketType; + Uint8 ucPacketNumber; + SwitchRumbleData_t rumbleData[2]; +} SwitchCommonOutputPacket_t; + +typedef struct +{ + SwitchCommonOutputPacket_t commonData; + + Uint8 ucSubcommandID; + Uint8 rgucSubcommandData[k_unSwitchOutputPacketDataLength - sizeof(SwitchCommonOutputPacket_t) - 1]; +} SwitchSubcommandOutputPacket_t; + +typedef struct +{ + Uint8 ucPacketType; + Uint8 ucProprietaryID; + + Uint8 rgucProprietaryData[k_unSwitchOutputPacketDataLength - 1 - 1]; +} SwitchProprietaryOutputPacket_t; +#pragma pack() + +/* Enhanced report hint mode: + * "0": enhanced features are never used + * "1": enhanced features are always used + * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it. + */ +typedef enum +{ + SWITCH_ENHANCED_REPORT_HINT_OFF, + SWITCH_ENHANCED_REPORT_HINT_ON, + SWITCH_ENHANCED_REPORT_HINT_AUTO +} HIDAPI_Switch_EnhancedReportHint; + +typedef struct +{ + SDL_HIDAPI_Device *device; + SDL_Joystick *joystick; + bool m_bInputOnly; + bool m_bUseButtonLabels; + bool m_bPlayerLights; + int m_nPlayerIndex; + bool m_bSyncWrite; + int m_nMaxWriteAttempts; + ESwitchDeviceInfoControllerType m_eControllerType; + Uint8 m_nInitialInputMode; + Uint8 m_nCurrentInputMode; + Uint8 m_rgucMACAddress[6]; + Uint8 m_nCommandNumber; + HIDAPI_Switch_EnhancedReportHint m_eEnhancedReportHint; + bool m_bEnhancedMode; + bool m_bEnhancedModeAvailable; + SwitchCommonOutputPacket_t m_RumblePacket; + Uint8 m_rgucReadBuffer[k_unSwitchMaxOutputPacketLength]; + bool m_bRumbleActive; + Uint64 m_ulRumbleSent; + bool m_bRumblePending; + bool m_bRumbleZeroPending; + Uint32 m_unRumblePending; + bool m_bSensorsSupported; + bool m_bReportSensors; + bool m_bHasSensorData; + Uint64 m_ulLastInput; + Uint64 m_ulLastIMUReset; + Uint64 m_ulIMUSampleTimestampNS; + Uint32 m_unIMUSamples; + Uint64 m_ulIMUUpdateIntervalNS; + Uint64 m_ulTimestampNS; + bool m_bVerticalMode; + + SwitchInputOnlyControllerStatePacket_t m_lastInputOnlyState; + SwitchSimpleStatePacket_t m_lastSimpleState; + SwitchStatePacket_t m_lastFullState; + + struct StickCalibrationData + { + struct + { + Sint16 sCenter; + Sint16 sMin; + Sint16 sMax; + } axis[2]; + } m_StickCalData[2]; + + struct StickExtents + { + struct + { + Sint16 sMin; + Sint16 sMax; + } axis[2]; + } m_StickExtents[2], m_SimpleStickExtents[2]; + + struct IMUScaleData + { + float fAccelScaleX; + float fAccelScaleY; + float fAccelScaleZ; + + float fGyroScaleX; + float fGyroScaleY; + float fGyroScaleZ; + } m_IMUScaleData; +} SDL_DriverSwitch_Context; + +static int ReadInput(SDL_DriverSwitch_Context *ctx) +{ + int result; + + // Make sure we don't try to read at the same time a write is happening + if (SDL_GetAtomicInt(&ctx->device->rumble_pending) > 0) { + return 0; + } + + result = SDL_hid_read_timeout(ctx->device->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0); + + // See if we can guess the initial input mode + if (result > 0 && !ctx->m_bInputOnly && !ctx->m_nInitialInputMode) { + switch (ctx->m_rgucReadBuffer[0]) { + case k_eSwitchInputReportIDs_FullControllerState: + case k_eSwitchInputReportIDs_FullControllerAndMcuState: + case k_eSwitchInputReportIDs_SimpleControllerState: + ctx->m_nInitialInputMode = ctx->m_rgucReadBuffer[0]; + break; + default: + break; + } + } + return result; +} + +static int WriteOutput(SDL_DriverSwitch_Context *ctx, const Uint8 *data, int size) +{ +#ifdef SWITCH_SYNCHRONOUS_WRITES + return SDL_hid_write(ctx->device->dev, data, size); +#else + // Use the rumble thread for general asynchronous writes + if (!SDL_HIDAPI_LockRumble()) { + return -1; + } + return SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size); +#endif // SWITCH_SYNCHRONOUS_WRITES +} + +static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID) +{ + // Average response time for messages is ~30ms + Uint64 endTicks = SDL_GetTicks() + 100; + + int nRead = 0; + while ((nRead = ReadInput(ctx)) != -1) { + if (nRead > 0) { + if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) { + SwitchSubcommandInputPacket_t *reply = (SwitchSubcommandInputPacket_t *)&ctx->m_rgucReadBuffer[1]; + if (reply->ucSubcommandID == expectedID && (reply->ucSubcommandAck & 0x80)) { + return reply; + } + } + } else { + SDL_Delay(1); + } + + if (SDL_GetTicks() >= endTicks) { + break; + } + } + return NULL; +} + +static bool ReadProprietaryReply(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs expectedID) +{ + // Average response time for messages is ~30ms + Uint64 endTicks = SDL_GetTicks() + 100; + + int nRead = 0; + while ((nRead = ReadInput(ctx)) != -1) { + if (nRead > 0) { + if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_CommandAck && ctx->m_rgucReadBuffer[1] == expectedID) { + return true; + } + } else { + SDL_Delay(1); + } + + if (SDL_GetTicks() >= endTicks) { + break; + } + } + return false; +} + +static void ConstructSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, const Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandOutputPacket_t *outPacket) +{ + SDL_memset(outPacket, 0, sizeof(*outPacket)); + + outPacket->commonData.ucPacketType = k_eSwitchOutputReportIDs_RumbleAndSubcommand; + outPacket->commonData.ucPacketNumber = ctx->m_nCommandNumber; + + SDL_memcpy(outPacket->commonData.rumbleData, ctx->m_RumblePacket.rumbleData, sizeof(ctx->m_RumblePacket.rumbleData)); + + outPacket->ucSubcommandID = ucCommandID; + if (pBuf) { + SDL_memcpy(outPacket->rgucSubcommandData, pBuf, ucLen); + } + + ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF; +} + +static bool WritePacket(SDL_DriverSwitch_Context *ctx, void *pBuf, Uint8 ucLen) +{ + Uint8 rgucBuf[k_unSwitchMaxOutputPacketLength]; + const size_t unWriteSize = ctx->device->is_bluetooth ? k_unSwitchBluetoothPacketLength : k_unSwitchUSBPacketLength; + + if (ucLen > k_unSwitchOutputPacketDataLength) { + return false; + } + + if (ucLen < unWriteSize) { + SDL_memcpy(rgucBuf, pBuf, ucLen); + SDL_memset(rgucBuf + ucLen, 0, unWriteSize - ucLen); + pBuf = rgucBuf; + ucLen = (Uint8)unWriteSize; + } + if (ctx->m_bSyncWrite) { + return SDL_hid_write(ctx->device->dev, (Uint8 *)pBuf, ucLen) >= 0; + } else { + return WriteOutput(ctx, (Uint8 *)pBuf, ucLen) >= 0; + } +} + +static bool WriteSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, const Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandInputPacket_t **ppReply) +{ + SwitchSubcommandInputPacket_t *reply = NULL; + int nTries; + + for (nTries = 1; !reply && nTries <= ctx->m_nMaxWriteAttempts; ++nTries) { + SwitchSubcommandOutputPacket_t commandPacket; + ConstructSubcommand(ctx, ucCommandID, pBuf, ucLen, &commandPacket); + + if (!WritePacket(ctx, &commandPacket, sizeof(commandPacket))) { + continue; + } + + reply = ReadSubcommandReply(ctx, ucCommandID); + } + + if (ppReply) { + *ppReply = reply; + } + return reply != NULL; +} + +static bool WriteProprietary(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs ucCommand, Uint8 *pBuf, Uint8 ucLen, bool waitForReply) +{ + int nTries; + + for (nTries = 1; nTries <= ctx->m_nMaxWriteAttempts; ++nTries) { + SwitchProprietaryOutputPacket_t packet; + + if ((!pBuf && ucLen > 0) || ucLen > sizeof(packet.rgucProprietaryData)) { + return false; + } + + SDL_zero(packet); + packet.ucPacketType = k_eSwitchOutputReportIDs_Proprietary; + packet.ucProprietaryID = ucCommand; + if (pBuf) { + SDL_memcpy(packet.rgucProprietaryData, pBuf, ucLen); + } + + if (!WritePacket(ctx, &packet, sizeof(packet))) { + continue; + } + + if (!waitForReply || ReadProprietaryReply(ctx, ucCommand)) { + // SDL_Log("Succeeded%s after %d tries", ctx->m_bSyncWrite ? " (sync)" : "", nTries); + return true; + } + } + // SDL_Log("Failed%s after %d tries", ctx->m_bSyncWrite ? " (sync)" : "", nTries); + return false; +} + +static Uint8 EncodeRumbleHighAmplitude(Uint16 amplitude) +{ + /* More information about these values can be found here: + * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md + */ + Uint16 hfa[101][2] = { { 0, 0x0 }, { 514, 0x2 }, { 775, 0x4 }, { 921, 0x6 }, { 1096, 0x8 }, { 1303, 0x0a }, { 1550, 0x0c }, { 1843, 0x0e }, { 2192, 0x10 }, { 2606, 0x12 }, { 3100, 0x14 }, { 3686, 0x16 }, { 4383, 0x18 }, { 5213, 0x1a }, { 6199, 0x1c }, { 7372, 0x1e }, { 7698, 0x20 }, { 8039, 0x22 }, { 8395, 0x24 }, { 8767, 0x26 }, { 9155, 0x28 }, { 9560, 0x2a }, { 9984, 0x2c }, { 10426, 0x2e }, { 10887, 0x30 }, { 11369, 0x32 }, { 11873, 0x34 }, { 12398, 0x36 }, { 12947, 0x38 }, { 13520, 0x3a }, { 14119, 0x3c }, { 14744, 0x3e }, { 15067, 0x40 }, { 15397, 0x42 }, { 15734, 0x44 }, { 16079, 0x46 }, { 16431, 0x48 }, { 16790, 0x4a }, { 17158, 0x4c }, { 17534, 0x4e }, { 17918, 0x50 }, { 18310, 0x52 }, { 18711, 0x54 }, { 19121, 0x56 }, { 19540, 0x58 }, { 19967, 0x5a }, { 20405, 0x5c }, { 20851, 0x5e }, { 21308, 0x60 }, { 21775, 0x62 }, { 22251, 0x64 }, { 22739, 0x66 }, { 23236, 0x68 }, { 23745, 0x6a }, { 24265, 0x6c }, { 24797, 0x6e }, { 25340, 0x70 }, { 25894, 0x72 }, { 26462, 0x74 }, { 27041, 0x76 }, { 27633, 0x78 }, { 28238, 0x7a }, { 28856, 0x7c }, { 29488, 0x7e }, { 30134, 0x80 }, { 30794, 0x82 }, { 31468, 0x84 }, { 32157, 0x86 }, { 32861, 0x88 }, { 33581, 0x8a }, { 34316, 0x8c }, { 35068, 0x8e }, { 35836, 0x90 }, { 36620, 0x92 }, { 37422, 0x94 }, { 38242, 0x96 }, { 39079, 0x98 }, { 39935, 0x9a }, { 40809, 0x9c }, { 41703, 0x9e }, { 42616, 0xa0 }, { 43549, 0xa2 }, { 44503, 0xa4 }, { 45477, 0xa6 }, { 46473, 0xa8 }, { 47491, 0xaa }, { 48531, 0xac }, { 49593, 0xae }, { 50679, 0xb0 }, { 51789, 0xb2 }, { 52923, 0xb4 }, { 54082, 0xb6 }, { 55266, 0xb8 }, { 56476, 0xba }, { 57713, 0xbc }, { 58977, 0xbe }, { 60268, 0xc0 }, { 61588, 0xc2 }, { 62936, 0xc4 }, { 64315, 0xc6 }, { 65535, 0xc8 } }; + int index = 0; + for (; index < 101; index++) { + if (amplitude <= hfa[index][0]) { + return (Uint8)hfa[index][1]; + } + } + return (Uint8)hfa[100][1]; +} + +static Uint16 EncodeRumbleLowAmplitude(Uint16 amplitude) +{ + /* More information about these values can be found here: + * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md + */ + Uint16 lfa[101][2] = { { 0, 0x0040 }, { 514, 0x8040 }, { 775, 0x0041 }, { 921, 0x8041 }, { 1096, 0x0042 }, { 1303, 0x8042 }, { 1550, 0x0043 }, { 1843, 0x8043 }, { 2192, 0x0044 }, { 2606, 0x8044 }, { 3100, 0x0045 }, { 3686, 0x8045 }, { 4383, 0x0046 }, { 5213, 0x8046 }, { 6199, 0x0047 }, { 7372, 0x8047 }, { 7698, 0x0048 }, { 8039, 0x8048 }, { 8395, 0x0049 }, { 8767, 0x8049 }, { 9155, 0x004a }, { 9560, 0x804a }, { 9984, 0x004b }, { 10426, 0x804b }, { 10887, 0x004c }, { 11369, 0x804c }, { 11873, 0x004d }, { 12398, 0x804d }, { 12947, 0x004e }, { 13520, 0x804e }, { 14119, 0x004f }, { 14744, 0x804f }, { 15067, 0x0050 }, { 15397, 0x8050 }, { 15734, 0x0051 }, { 16079, 0x8051 }, { 16431, 0x0052 }, { 16790, 0x8052 }, { 17158, 0x0053 }, { 17534, 0x8053 }, { 17918, 0x0054 }, { 18310, 0x8054 }, { 18711, 0x0055 }, { 19121, 0x8055 }, { 19540, 0x0056 }, { 19967, 0x8056 }, { 20405, 0x0057 }, { 20851, 0x8057 }, { 21308, 0x0058 }, { 21775, 0x8058 }, { 22251, 0x0059 }, { 22739, 0x8059 }, { 23236, 0x005a }, { 23745, 0x805a }, { 24265, 0x005b }, { 24797, 0x805b }, { 25340, 0x005c }, { 25894, 0x805c }, { 26462, 0x005d }, { 27041, 0x805d }, { 27633, 0x005e }, { 28238, 0x805e }, { 28856, 0x005f }, { 29488, 0x805f }, { 30134, 0x0060 }, { 30794, 0x8060 }, { 31468, 0x0061 }, { 32157, 0x8061 }, { 32861, 0x0062 }, { 33581, 0x8062 }, { 34316, 0x0063 }, { 35068, 0x8063 }, { 35836, 0x0064 }, { 36620, 0x8064 }, { 37422, 0x0065 }, { 38242, 0x8065 }, { 39079, 0x0066 }, { 39935, 0x8066 }, { 40809, 0x0067 }, { 41703, 0x8067 }, { 42616, 0x0068 }, { 43549, 0x8068 }, { 44503, 0x0069 }, { 45477, 0x8069 }, { 46473, 0x006a }, { 47491, 0x806a }, { 48531, 0x006b }, { 49593, 0x806b }, { 50679, 0x006c }, { 51789, 0x806c }, { 52923, 0x006d }, { 54082, 0x806d }, { 55266, 0x006e }, { 56476, 0x806e }, { 57713, 0x006f }, { 58977, 0x806f }, { 60268, 0x0070 }, { 61588, 0x8070 }, { 62936, 0x0071 }, { 64315, 0x8071 }, { 65535, 0x0072 } }; + int index = 0; + for (; index < 101; index++) { + if (amplitude <= lfa[index][0]) { + return lfa[index][1]; + } + } + return lfa[100][1]; +} + +static void SetNeutralRumble(SwitchRumbleData_t *pRumble) +{ + pRumble->rgucData[0] = 0x00; + pRumble->rgucData[1] = 0x01; + pRumble->rgucData[2] = 0x40; + pRumble->rgucData[3] = 0x40; +} + +static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp) +{ + if (ucHighFreqAmp > 0 || usLowFreqAmp > 0) { + // High-band frequency and low-band amplitude are actually nine-bits each so they + // take a bit from the high-band amplitude and low-band frequency bytes respectively + pRumble->rgucData[0] = usHighFreq & 0xFF; + pRumble->rgucData[1] = ucHighFreqAmp | ((usHighFreq >> 8) & 0x01); + + pRumble->rgucData[2] = ucLowFreq | ((usLowFreqAmp >> 8) & 0x80); + pRumble->rgucData[3] = usLowFreqAmp & 0xFF; + +#ifdef DEBUG_RUMBLE + SDL_Log("Freq: %.2X %.2X %.2X, Amp: %.2X %.2X %.2X", + usHighFreq & 0xFF, ((usHighFreq >> 8) & 0x01), ucLowFreq, + ucHighFreqAmp, ((usLowFreqAmp >> 8) & 0x80), usLowFreqAmp & 0xFF); +#endif + } else { + SetNeutralRumble(pRumble); + } +} + +static bool WriteRumble(SDL_DriverSwitch_Context *ctx) +{ + /* Write into m_RumblePacket rather than a temporary buffer to allow the current rumble state + * to be retained for subsequent rumble or subcommand packets sent to the controller + */ + ctx->m_RumblePacket.ucPacketType = k_eSwitchOutputReportIDs_Rumble; + ctx->m_RumblePacket.ucPacketNumber = ctx->m_nCommandNumber; + ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF; + + // Refresh the rumble state periodically + ctx->m_ulRumbleSent = SDL_GetTicks(); + + return WritePacket(ctx, (Uint8 *)&ctx->m_RumblePacket, sizeof(ctx->m_RumblePacket)); +} + +static ESwitchDeviceInfoControllerType CalculateControllerType(SDL_DriverSwitch_Context *ctx, ESwitchDeviceInfoControllerType eControllerType) +{ + SDL_HIDAPI_Device *device = ctx->device; + + // The N64 controller reports as a Pro controller over USB + if (eControllerType == k_eSwitchDeviceInfoControllerType_ProController && + device->product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER) { + eControllerType = k_eSwitchDeviceInfoControllerType_N64; + } + + if (eControllerType == k_eSwitchDeviceInfoControllerType_Unknown) { + // This might be a Joy-Con that's missing from a charging grip slot + if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) { + if (device->interface_number == 1) { + eControllerType = k_eSwitchDeviceInfoControllerType_JoyConLeft; + } else { + eControllerType = k_eSwitchDeviceInfoControllerType_JoyConRight; + } + } + } + return eControllerType; +} + +static bool BReadDeviceInfo(SDL_DriverSwitch_Context *ctx) +{ + SwitchSubcommandInputPacket_t *reply = NULL; + + if (ctx->device->is_bluetooth) { + if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_RequestDeviceInfo, NULL, 0, &reply)) { + // Byte 2: Controller ID (1=LJC, 2=RJC, 3=Pro) + ctx->m_eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)reply->deviceInfo.ucDeviceType); + + // Bytes 4-9: MAC address (big-endian) + SDL_memcpy(ctx->m_rgucMACAddress, reply->deviceInfo.rgucMACAddress, sizeof(ctx->m_rgucMACAddress)); + + return true; + } + } else { + if (WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Status, NULL, 0, true)) { + SwitchProprietaryStatusPacket_t *status = (SwitchProprietaryStatusPacket_t *)&ctx->m_rgucReadBuffer[0]; + size_t i; + + ctx->m_eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)status->ucDeviceType); + + for (i = 0; i < sizeof(ctx->m_rgucMACAddress); ++i) { + ctx->m_rgucMACAddress[i] = status->rgucMACAddress[sizeof(ctx->m_rgucMACAddress) - i - 1]; + } + + return true; + } + } + return false; +} + +static bool BTrySetupUSB(SDL_DriverSwitch_Context *ctx) +{ + /* We have to send a connection handshake to the controller when communicating over USB + * before we're able to send it other commands. Luckily this command is not supported + * over Bluetooth, so we can use the controller's lack of response as a way to + * determine if the connection is over USB or Bluetooth + */ + if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, true)) { + return false; + } + if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_HighSpeed, NULL, 0, true)) { + // The 8BitDo M30 and SF30 Pro don't respond to this command, but otherwise work correctly + // return false; + } + if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, true)) { + // This fails on the right Joy-Con when plugged into the charging grip + // return false; + } + if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, false)) { + return false; + } + return true; +} + +static bool SetVibrationEnabled(SDL_DriverSwitch_Context *ctx, Uint8 enabled) +{ + return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_EnableVibration, &enabled, sizeof(enabled), NULL); +} +static bool SetInputMode(SDL_DriverSwitch_Context *ctx, Uint8 input_mode) +{ +#ifdef FORCE_SIMPLE_REPORTS + input_mode = k_eSwitchInputReportIDs_SimpleControllerState; +#endif +#ifdef FORCE_FULL_REPORTS + input_mode = k_eSwitchInputReportIDs_FullControllerState; +#endif + + if (input_mode == ctx->m_nCurrentInputMode) { + return true; + } else { + ctx->m_nCurrentInputMode = input_mode; + + return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetInputReportMode, &input_mode, sizeof(input_mode), NULL); + } +} + +static bool SetHomeLED(SDL_DriverSwitch_Context *ctx, Uint8 brightness) +{ + Uint8 ucLedIntensity = 0; + Uint8 rgucBuffer[4]; + + if (brightness > 0) { + if (brightness < 65) { + ucLedIntensity = (brightness + 5) / 10; + } else { + ucLedIntensity = (Uint8)SDL_ceilf(0xF * SDL_powf((float)brightness / 100.f, 2.13f)); + } + } + + rgucBuffer[0] = (0x0 << 4) | 0x1; // 0 mini cycles (besides first), cycle duration 8ms + rgucBuffer[1] = ((ucLedIntensity & 0xF) << 4) | 0x0; // LED start intensity (0x0-0xF), 0 cycles (LED stays on at start intensity after first cycle) + rgucBuffer[2] = ((ucLedIntensity & 0xF) << 4) | 0x0; // First cycle LED intensity, 0x0 intensity for second cycle + rgucBuffer[3] = (0x0 << 4) | 0x0; // 8ms fade transition to first cycle, 8ms first cycle LED duration + + return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetHomeLight, rgucBuffer, sizeof(rgucBuffer), NULL); +} + +static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata; + + if (hint && *hint) { + int value; + + if (SDL_strchr(hint, '.') != NULL) { + value = (int)(100.0f * SDL_atof(hint)); + if (value > 255) { + value = 255; + } + } else if (SDL_GetStringBoolean(hint, true)) { + value = 100; + } else { + value = 0; + } + SetHomeLED(ctx, (Uint8)value); + } +} + +static void UpdateSlotLED(SDL_DriverSwitch_Context *ctx) +{ + if (!ctx->m_bInputOnly) { + Uint8 led_data = 0; + + if (ctx->m_bPlayerLights && ctx->m_nPlayerIndex >= 0) { + led_data = (1 << (ctx->m_nPlayerIndex % 4)); + } + WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetPlayerLights, &led_data, sizeof(led_data), NULL); + } +} + +static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata; + bool bPlayerLights = SDL_GetStringBoolean(hint, true); + + if (bPlayerLights != ctx->m_bPlayerLights) { + ctx->m_bPlayerLights = bPlayerLights; + + UpdateSlotLED(ctx); + HIDAPI_UpdateDeviceProperties(ctx->device); + } +} + +static void GetInitialInputMode(SDL_DriverSwitch_Context *ctx) +{ + if (!ctx->m_nInitialInputMode) { + // This will set the initial input mode if it can + ReadInput(ctx); + } +} + +static Uint8 GetDefaultInputMode(SDL_DriverSwitch_Context *ctx) +{ + Uint8 input_mode; + + // Determine the desired input mode + if (ctx->m_nInitialInputMode) { + input_mode = ctx->m_nInitialInputMode; + } else { + if (ctx->device->is_bluetooth) { + input_mode = k_eSwitchInputReportIDs_SimpleControllerState; + } else { + input_mode = k_eSwitchInputReportIDs_FullControllerState; + } + } + + switch (ctx->m_eEnhancedReportHint) { + case SWITCH_ENHANCED_REPORT_HINT_OFF: + input_mode = k_eSwitchInputReportIDs_SimpleControllerState; + break; + case SWITCH_ENHANCED_REPORT_HINT_ON: + if (input_mode == k_eSwitchInputReportIDs_SimpleControllerState) { + input_mode = k_eSwitchInputReportIDs_FullControllerState; + } + break; + case SWITCH_ENHANCED_REPORT_HINT_AUTO: + /* Joy-Con controllers switch their thumbsticks into D-pad mode in simple mode, + * so let's enable full controller state for them. + */ + if (ctx->device->vendor_id == USB_VENDOR_NINTENDO && + (ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT || + ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT)) { + input_mode = k_eSwitchInputReportIDs_FullControllerState; + } + break; + } + + // Wired controllers break if they are put into simple controller state + if (input_mode == k_eSwitchInputReportIDs_SimpleControllerState && + !ctx->device->is_bluetooth) { + input_mode = k_eSwitchInputReportIDs_FullControllerState; + } + return input_mode; +} + +static Uint8 GetSensorInputMode(SDL_DriverSwitch_Context *ctx) +{ + Uint8 input_mode; + + // Determine the desired input mode + if (!ctx->m_nInitialInputMode || + ctx->m_nInitialInputMode == k_eSwitchInputReportIDs_SimpleControllerState) { + input_mode = k_eSwitchInputReportIDs_FullControllerState; + } else { + input_mode = ctx->m_nInitialInputMode; + } + return input_mode; +} + +static void UpdateInputMode(SDL_DriverSwitch_Context *ctx) +{ + Uint8 input_mode; + + if (ctx->m_bReportSensors) { + input_mode = GetSensorInputMode(ctx); + } else { + input_mode = GetDefaultInputMode(ctx); + } + SetInputMode(ctx, input_mode); +} + +static void SetEnhancedModeAvailable(SDL_DriverSwitch_Context *ctx) +{ + if (ctx->m_bEnhancedModeAvailable) { + return; + } + ctx->m_bEnhancedModeAvailable = true; + + if (ctx->m_bSensorsSupported) { + // Use the right sensor in the combined Joy-Con pair + if (!ctx->device->parent || + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 200.0f); + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 200.0f); + } + if (ctx->device->parent && + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO_L, 200.0f); + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL_L, 200.0f); + } + if (ctx->device->parent && + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO_R, 200.0f); + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL_R, 200.0f); + } + } +} + +static void SetEnhancedReportHint(SDL_DriverSwitch_Context *ctx, HIDAPI_Switch_EnhancedReportHint eEnhancedReportHint) +{ + ctx->m_eEnhancedReportHint = eEnhancedReportHint; + + switch (eEnhancedReportHint) { + case SWITCH_ENHANCED_REPORT_HINT_OFF: + ctx->m_bEnhancedMode = false; + break; + case SWITCH_ENHANCED_REPORT_HINT_ON: + SetEnhancedModeAvailable(ctx); + ctx->m_bEnhancedMode = true; + break; + case SWITCH_ENHANCED_REPORT_HINT_AUTO: + SetEnhancedModeAvailable(ctx); + break; + } + + UpdateInputMode(ctx); +} + +static void UpdateEnhancedModeOnEnhancedReport(SDL_DriverSwitch_Context *ctx) +{ + if (ctx->m_eEnhancedReportHint == SWITCH_ENHANCED_REPORT_HINT_AUTO) { + SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON); + } +} + +static void UpdateEnhancedModeOnApplicationUsage(SDL_DriverSwitch_Context *ctx) +{ + if (ctx->m_eEnhancedReportHint == SWITCH_ENHANCED_REPORT_HINT_AUTO) { + SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON); + } +} + +static void SDLCALL SDL_EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata; + + if (hint && SDL_strcasecmp(hint, "auto") == 0) { + SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_AUTO); + } else if (SDL_GetStringBoolean(hint, true)) { + SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON); + } else { + SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_OFF); + } +} + +static bool SetIMUEnabled(SDL_DriverSwitch_Context *ctx, bool enabled) +{ + Uint8 imu_data = enabled ? 1 : 0; + return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_EnableIMU, &imu_data, sizeof(imu_data), NULL); +} + +static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx) +{ + Uint8 *pLeftStickCal; + Uint8 *pRightStickCal; + size_t stick, axis; + SwitchSubcommandInputPacket_t *user_reply = NULL; + SwitchSubcommandInputPacket_t *factory_reply = NULL; + SwitchSPIOpData_t readUserParams; + SwitchSPIOpData_t readFactoryParams; + + // Read User Calibration Info + readUserParams.unAddress = k_unSPIStickUserCalibrationStartOffset; + readUserParams.ucLength = k_unSPIStickUserCalibrationLength; + + // This isn't readable on all controllers, so ignore failure + WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readUserParams, sizeof(readUserParams), &user_reply); + + // Read Factory Calibration Info + readFactoryParams.unAddress = k_unSPIStickFactoryCalibrationStartOffset; + readFactoryParams.ucLength = k_unSPIStickFactoryCalibrationLength; + + const int MAX_ATTEMPTS = 3; + for (int attempt = 0; ; ++attempt) { + if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readFactoryParams, sizeof(readFactoryParams), &factory_reply)) { + return false; + } + + if (factory_reply->stickFactoryCalibration.opData.unAddress == k_unSPIStickFactoryCalibrationStartOffset) { + // We successfully read the calibration data + break; + } + + if (attempt == MAX_ATTEMPTS) { + return false; + } + } + + // Automatically select the user calibration if magic bytes are set + if (user_reply && user_reply->stickUserCalibration.rgucLeftMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucLeftMagic[1] == 0xA1) { + pLeftStickCal = user_reply->stickUserCalibration.rgucLeftCalibration; + } else { + pLeftStickCal = factory_reply->stickFactoryCalibration.rgucLeftCalibration; + } + + if (user_reply && user_reply->stickUserCalibration.rgucRightMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucRightMagic[1] == 0xA1) { + pRightStickCal = user_reply->stickUserCalibration.rgucRightCalibration; + } else { + pRightStickCal = factory_reply->stickFactoryCalibration.rgucRightCalibration; + } + + /* Stick calibration values are 12-bits each and are packed by bit + * For whatever reason the fields are in a different order for each stick + * Left: X-Max, Y-Max, X-Center, Y-Center, X-Min, Y-Min + * Right: X-Center, Y-Center, X-Min, Y-Min, X-Max, Y-Max + */ + + // Left stick + ctx->m_StickCalData[0].axis[0].sMax = ((pLeftStickCal[1] << 8) & 0xF00) | pLeftStickCal[0]; // X Axis max above center + ctx->m_StickCalData[0].axis[1].sMax = (pLeftStickCal[2] << 4) | (pLeftStickCal[1] >> 4); // Y Axis max above center + ctx->m_StickCalData[0].axis[0].sCenter = ((pLeftStickCal[4] << 8) & 0xF00) | pLeftStickCal[3]; // X Axis center + ctx->m_StickCalData[0].axis[1].sCenter = (pLeftStickCal[5] << 4) | (pLeftStickCal[4] >> 4); // Y Axis center + ctx->m_StickCalData[0].axis[0].sMin = ((pLeftStickCal[7] << 8) & 0xF00) | pLeftStickCal[6]; // X Axis min below center + ctx->m_StickCalData[0].axis[1].sMin = (pLeftStickCal[8] << 4) | (pLeftStickCal[7] >> 4); // Y Axis min below center + + // Right stick + ctx->m_StickCalData[1].axis[0].sCenter = ((pRightStickCal[1] << 8) & 0xF00) | pRightStickCal[0]; // X Axis center + ctx->m_StickCalData[1].axis[1].sCenter = (pRightStickCal[2] << 4) | (pRightStickCal[1] >> 4); // Y Axis center + ctx->m_StickCalData[1].axis[0].sMin = ((pRightStickCal[4] << 8) & 0xF00) | pRightStickCal[3]; // X Axis min below center + ctx->m_StickCalData[1].axis[1].sMin = (pRightStickCal[5] << 4) | (pRightStickCal[4] >> 4); // Y Axis min below center + ctx->m_StickCalData[1].axis[0].sMax = ((pRightStickCal[7] << 8) & 0xF00) | pRightStickCal[6]; // X Axis max above center + ctx->m_StickCalData[1].axis[1].sMax = (pRightStickCal[8] << 4) | (pRightStickCal[7] >> 4); // Y Axis max above center + + // Filter out any values that were uninitialized (0xFFF) in the SPI read + for (stick = 0; stick < 2; ++stick) { + for (axis = 0; axis < 2; ++axis) { + if (ctx->m_StickCalData[stick].axis[axis].sCenter == 0xFFF) { + ctx->m_StickCalData[stick].axis[axis].sCenter = 2048; + } + if (ctx->m_StickCalData[stick].axis[axis].sMax == 0xFFF) { + ctx->m_StickCalData[stick].axis[axis].sMax = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sCenter * 0.7f); + } + if (ctx->m_StickCalData[stick].axis[axis].sMin == 0xFFF) { + ctx->m_StickCalData[stick].axis[axis].sMin = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sCenter * 0.7f); + } + } + } + + for (stick = 0; stick < 2; ++stick) { + for (axis = 0; axis < 2; ++axis) { + ctx->m_StickExtents[stick].axis[axis].sMin = -(Sint16)(ctx->m_StickCalData[stick].axis[axis].sMin * 0.7f); + ctx->m_StickExtents[stick].axis[axis].sMax = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sMax * 0.7f); + } + } + + for (stick = 0; stick < 2; ++stick) { + for (axis = 0; axis < 2; ++axis) { + ctx->m_SimpleStickExtents[stick].axis[axis].sMin = (Sint16)(SDL_MIN_SINT16 * 0.5f); + ctx->m_SimpleStickExtents[stick].axis[axis].sMax = (Sint16)(SDL_MAX_SINT16 * 0.5f); + } + } + + return true; +} + +static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx) +{ + SwitchSubcommandInputPacket_t *reply = NULL; + + // Read Calibration Info + SwitchSPIOpData_t readParams; + readParams.unAddress = k_unSPIIMUScaleStartOffset; + readParams.ucLength = k_unSPIIMUScaleLength; + + if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply)) { + Uint8 *pIMUScale; + Sint16 sAccelRawX, sAccelRawY, sAccelRawZ, sGyroRawX, sGyroRawY, sGyroRawZ; + + // IMU scale gives us multipliers for converting raw values to real world values + pIMUScale = reply->spiReadData.rgucReadData; + + sAccelRawX = (pIMUScale[1] << 8) | pIMUScale[0]; + sAccelRawY = (pIMUScale[3] << 8) | pIMUScale[2]; + sAccelRawZ = (pIMUScale[5] << 8) | pIMUScale[4]; + + sGyroRawX = (pIMUScale[13] << 8) | pIMUScale[12]; + sGyroRawY = (pIMUScale[15] << 8) | pIMUScale[14]; + sGyroRawZ = (pIMUScale[17] << 8) | pIMUScale[16]; + + // Check for user calibration data. If it's present and set, it'll override the factory settings + readParams.unAddress = k_unSPIIMUUserScaleStartOffset; + readParams.ucLength = k_unSPIIMUUserScaleLength; + if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply) && (pIMUScale[0] | pIMUScale[1] << 8) == 0xA1B2) { + pIMUScale = reply->spiReadData.rgucReadData; + + sAccelRawX = (pIMUScale[3] << 8) | pIMUScale[2]; + sAccelRawY = (pIMUScale[5] << 8) | pIMUScale[4]; + sAccelRawZ = (pIMUScale[7] << 8) | pIMUScale[6]; + + sGyroRawX = (pIMUScale[15] << 8) | pIMUScale[14]; + sGyroRawY = (pIMUScale[17] << 8) | pIMUScale[16]; + sGyroRawZ = (pIMUScale[19] << 8) | pIMUScale[18]; + } + + // Accelerometer scale + ctx->m_IMUScaleData.fAccelScaleX = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawX) * SDL_STANDARD_GRAVITY; + ctx->m_IMUScaleData.fAccelScaleY = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawY) * SDL_STANDARD_GRAVITY; + ctx->m_IMUScaleData.fAccelScaleZ = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawZ) * SDL_STANDARD_GRAVITY; + + // Gyro scale + ctx->m_IMUScaleData.fGyroScaleX = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawX) * SDL_PI_F / 180.0f; + ctx->m_IMUScaleData.fGyroScaleY = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawY) * SDL_PI_F / 180.0f; + ctx->m_IMUScaleData.fGyroScaleZ = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawZ) * SDL_PI_F / 180.0f; + + } else { + // Use default values + const float accelScale = SDL_STANDARD_GRAVITY / SWITCH_ACCEL_SCALE; + const float gyroScale = SDL_PI_F / 180.0f / SWITCH_GYRO_SCALE; + + ctx->m_IMUScaleData.fAccelScaleX = accelScale; + ctx->m_IMUScaleData.fAccelScaleY = accelScale; + ctx->m_IMUScaleData.fAccelScaleZ = accelScale; + + ctx->m_IMUScaleData.fGyroScaleX = gyroScale; + ctx->m_IMUScaleData.fGyroScaleY = gyroScale; + ctx->m_IMUScaleData.fGyroScaleZ = gyroScale; + } + return true; +} + +static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue) +{ + sRawValue -= ctx->m_StickCalData[nStick].axis[nAxis].sCenter; + + if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) { + ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue; + } + if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) { + ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue; + } + + return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, ctx->m_StickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16); +} + +static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue) +{ + // 0x8000 is the neutral value for all joystick axes + const Uint16 usJoystickCenter = 0x8000; + + sRawValue -= usJoystickCenter; + + if (sRawValue > ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax) { + ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax = sRawValue; + } + if (sRawValue < ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin) { + ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin = sRawValue; + } + + return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16); +} + +static Uint8 RemapButton(SDL_DriverSwitch_Context *ctx, Uint8 button) +{ + if (ctx->m_bUseButtonLabels) { + // Use button labels instead of positions, e.g. Nintendo Online Classic controllers + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + return SDL_GAMEPAD_BUTTON_EAST; + case SDL_GAMEPAD_BUTTON_EAST: + return SDL_GAMEPAD_BUTTON_SOUTH; + case SDL_GAMEPAD_BUTTON_WEST: + return SDL_GAMEPAD_BUTTON_NORTH; + case SDL_GAMEPAD_BUTTON_NORTH: + return SDL_GAMEPAD_BUTTON_WEST; + default: + break; + } + } + return button; +} + +static int GetMaxWriteAttempts(SDL_HIDAPI_Device *device) +{ + if (device->vendor_id == USB_VENDOR_NINTENDO && + device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) { + // This device is a little slow and we know we're always on USB + return 20; + } else { + return 5; + } +} + +static ESwitchDeviceInfoControllerType ReadJoyConControllerType(SDL_HIDAPI_Device *device) +{ + ESwitchDeviceInfoControllerType eControllerType = k_eSwitchDeviceInfoControllerType_Unknown; + const int MAX_ATTEMPTS = 1; // Don't try too long, in case this is a zombie Bluetooth controller + int attempts = 0; + + // Create enough of a context to read the controller type from the device + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)SDL_calloc(1, sizeof(*ctx)); + if (ctx) { + ctx->device = device; + ctx->m_bSyncWrite = true; + ctx->m_nMaxWriteAttempts = GetMaxWriteAttempts(device); + + for ( ; ; ) { + ++attempts; + if (device->is_bluetooth) { + SwitchSubcommandInputPacket_t *reply = NULL; + + if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_RequestDeviceInfo, NULL, 0, &reply)) { + eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)reply->deviceInfo.ucDeviceType); + } + } else { + if (WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Status, NULL, 0, true)) { + SwitchProprietaryStatusPacket_t *status = (SwitchProprietaryStatusPacket_t *)&ctx->m_rgucReadBuffer[0]; + + eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)status->ucDeviceType); + } + } + if (eControllerType == k_eSwitchDeviceInfoControllerType_Unknown && attempts < MAX_ATTEMPTS) { + // Wait a bit and try again + SDL_Delay(100); + continue; + } + break; + } + SDL_free(ctx); + } + return eControllerType; +} + +static bool HasHomeLED(SDL_DriverSwitch_Context *ctx) +{ + Uint16 vendor_id = ctx->device->vendor_id; + Uint16 product_id = ctx->device->product_id; + + // The Power A Nintendo Switch Pro controllers don't have a Home LED + if (vendor_id == 0 && product_id == 0) { + return false; + } + + // HORI Wireless Switch Pad + if (vendor_id == 0x0f0d && product_id == 0x00f6) { + return false; + } + + // Third party controllers don't have a home LED and will shut off if we try to set it + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_Unknown || + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_LicProController) { + return false; + } + + // The Nintendo Online classic controllers don't have a Home LED + if (vendor_id == USB_VENDOR_NINTENDO && + ctx->m_eControllerType > k_eSwitchDeviceInfoControllerType_ProController) { + return false; + } + + return true; +} + +static bool AlwaysUsesLabels(Uint16 vendor_id, Uint16 product_id, ESwitchDeviceInfoControllerType eControllerType) +{ + // Some controllers don't have a diamond button configuration, so should always use labels + if (SDL_IsJoystickGameCube(vendor_id, product_id)) { + return true; + } + switch (eControllerType) { + case k_eSwitchDeviceInfoControllerType_HVCLeft: + case k_eSwitchDeviceInfoControllerType_HVCRight: + case k_eSwitchDeviceInfoControllerType_NESLeft: + case k_eSwitchDeviceInfoControllerType_NESRight: + case k_eSwitchDeviceInfoControllerType_N64: + case k_eSwitchDeviceInfoControllerType_SEGA_Genesis: + return true; + default: + return false; + } +} + +static void HIDAPI_DriverNintendoClassic_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, callback, userdata); +} + +static void HIDAPI_DriverNintendoClassic_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, callback, userdata); +} + +static bool HIDAPI_DriverNintendoClassic_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverNintendoClassic_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_NINTENDO) { + if (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) { + if (SDL_strncmp(name, "NES Controller", 14) == 0 || + SDL_strncmp(name, "HVC Controller", 14) == 0) { + return true; + } + } + + if (product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER) { + return true; + } + + if (product_id == USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER) { + return true; + } + + if (product_id == USB_PRODUCT_NINTENDO_SNES_CONTROLLER) { + return true; + } + } + + return false; +} + +static void HIDAPI_DriverJoyCons_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, callback, userdata); +} + +static void HIDAPI_DriverJoyCons_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, callback, userdata); +} + +static bool HIDAPI_DriverJoyCons_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverJoyCons_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_NINTENDO) { + if (product_id == USB_PRODUCT_NINTENDO_SWITCH_PRO && device && device->dev) { + // This might be a Kinvoca Joy-Con that reports VID/PID as a Switch Pro controller + ESwitchDeviceInfoControllerType eControllerType = ReadJoyConControllerType(device); + if (eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || + eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + return true; + } + } + + if (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT || + product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT || + product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) { + return true; + } + } + return false; +} + +static void HIDAPI_DriverSwitch_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, callback, userdata); +} + +static void HIDAPI_DriverSwitch_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, callback, userdata); +} + +static bool HIDAPI_DriverSwitch_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverSwitch_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + /* The HORI Wireless Switch Pad enumerates as a HID device when connected via USB + with the same VID/PID as when connected over Bluetooth but doesn't actually + support communication over USB. The most reliable way to block this without allowing the + controller to continually attempt to reconnect is to filter it out by manufacturer/product string. + Note that the controller does have a different product string when connected over Bluetooth. + */ + if (SDL_strcmp(name, "HORI Wireless Switch Pad") == 0) { + return false; + } + + // If it's handled by another driver, it's not handled here + if (HIDAPI_DriverNintendoClassic_IsSupportedDevice(device, name, type, vendor_id, product_id, version, interface_number, interface_class, interface_subclass, interface_protocol) || + HIDAPI_DriverJoyCons_IsSupportedDevice(device, name, type, vendor_id, product_id, version, interface_number, interface_class, interface_subclass, interface_protocol)) { + return false; + } + + return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO); +} + +static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; + + if (ctx->m_bInputOnly) { + if (SDL_IsJoystickGameCube(device->vendor_id, device->product_id)) { + device->type = SDL_GAMEPAD_TYPE_STANDARD; + } + } else { + char serial[18]; + + switch (ctx->m_eControllerType) { + case k_eSwitchDeviceInfoControllerType_JoyConLeft: + HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (L)"); + HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT); + device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT; + break; + case k_eSwitchDeviceInfoControllerType_JoyConRight: + HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (R)"); + HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT); + device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT; + break; + case k_eSwitchDeviceInfoControllerType_ProController: + case k_eSwitchDeviceInfoControllerType_LicProController: + HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller"); + HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_PRO); + device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; + break; + case k_eSwitchDeviceInfoControllerType_HVCLeft: + HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (1)"); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_HVCRight: + HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (2)"); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_NESLeft: + HIDAPI_SetDeviceName(device, "Nintendo NES Controller (L)"); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_NESRight: + HIDAPI_SetDeviceName(device, "Nintendo NES Controller (R)"); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_SNES: + HIDAPI_SetDeviceName(device, "Nintendo SNES Controller"); + HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SNES_CONTROLLER); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_N64: + HIDAPI_SetDeviceName(device, "Nintendo N64 Controller"); + HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_N64_CONTROLLER); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_SEGA_Genesis: + HIDAPI_SetDeviceName(device, "Nintendo SEGA Genesis Controller"); + HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER); + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + case k_eSwitchDeviceInfoControllerType_Unknown: + // We couldn't read the device info for this controller, might not be fully compliant + if (device->vendor_id == USB_VENDOR_NINTENDO) { + switch (device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT: + ctx->m_eControllerType = k_eSwitchDeviceInfoControllerType_JoyConLeft; + HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (L)"); + device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT; + break; + case USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT: + ctx->m_eControllerType = k_eSwitchDeviceInfoControllerType_JoyConRight; + HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (R)"); + device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT; + break; + case USB_PRODUCT_NINTENDO_SWITCH_PRO: + ctx->m_eControllerType = k_eSwitchDeviceInfoControllerType_ProController; + HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller"); + device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; + break; + default: + break; + } + } + return; + default: + device->type = SDL_GAMEPAD_TYPE_STANDARD; + break; + } + device->guid.data[15] = ctx->m_eControllerType; + + (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + ctx->m_rgucMACAddress[0], + ctx->m_rgucMACAddress[1], + ctx->m_rgucMACAddress[2], + ctx->m_rgucMACAddress[3], + ctx->m_rgucMACAddress[4], + ctx->m_rgucMACAddress[5]); + HIDAPI_SetDeviceSerial(device, serial); + } +} + +static bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch_Context *ctx; + + ctx = (SDL_DriverSwitch_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + device->context = ctx; + + ctx->m_nMaxWriteAttempts = GetMaxWriteAttempts(device); + ctx->m_bSyncWrite = true; + + // Find out whether or not we can send output reports + ctx->m_bInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(device->vendor_id, device->product_id); + if (!ctx->m_bInputOnly) { + // Initialize rumble data, important for reading device info on the MOBAPAD M073 + SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); + SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); + + BReadDeviceInfo(ctx); + } + UpdateDeviceIdentity(device); + + // Prefer the USB device over the Bluetooth device + if (device->is_bluetooth) { + if (HIDAPI_HasConnectedUSBDevice(device->serial)) { + return true; + } + } else { + HIDAPI_DisconnectBluetoothDevice(device->serial); + } + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverSwitch_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverSwitch_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; + + if (!ctx->joystick) { + return; + } + + ctx->m_nPlayerIndex = player_index; + + UpdateSlotLED(ctx); +} + +static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + ctx->joystick = joystick; + + ctx->m_bSyncWrite = true; + + if (!ctx->m_bInputOnly) { +#ifdef SDL_PLATFORM_MACOS + // Wait for the OS to finish its handshake with the controller + SDL_Delay(250); +#endif + GetInitialInputMode(ctx); + ctx->m_nCurrentInputMode = ctx->m_nInitialInputMode; + + // Initialize rumble data + SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); + SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); + + if (!device->is_bluetooth) { + if (!BTrySetupUSB(ctx)) { + SDL_SetError("Couldn't setup USB mode"); + return false; + } + } + + if (!LoadStickCalibration(ctx)) { + SDL_SetError("Couldn't load stick calibration"); + return false; + } + + if (ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_HVCLeft && + ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_HVCRight && + ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESLeft && + ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESRight && + ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SNES && + ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_N64 && + ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis) { + if (LoadIMUCalibration(ctx)) { + ctx->m_bSensorsSupported = true; + } + } + + // Enable vibration + SetVibrationEnabled(ctx, 1); + + // Set desired input mode + SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_EnhancedReportsChanged, ctx); + + // Start sending USB reports + if (!device->is_bluetooth) { + // ForceUSB doesn't generate an ACK, so don't wait for a reply + if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, false)) { + SDL_SetError("Couldn't start USB reports"); + return false; + } + } + + // Set the LED state + if (HasHomeLED(ctx)) { + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED, + SDL_HomeLEDHintChanged, ctx); + } else { + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, + SDL_HomeLEDHintChanged, ctx); + } + } + } + + if (AlwaysUsesLabels(device->vendor_id, device->product_id, ctx->m_eControllerType)) { + ctx->m_bUseButtonLabels = true; + } + + // Initialize player index (needed for setting LEDs) + ctx->m_nPlayerIndex = SDL_GetJoystickPlayerIndex(joystick); + ctx->m_bPlayerLights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, true); + UpdateSlotLED(ctx); + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + // Initialize the joystick capabilities + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH_BUTTONS; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + // Set up for input + ctx->m_bSyncWrite = false; + ctx->m_ulLastIMUReset = ctx->m_ulLastInput = SDL_GetTicks(); + ctx->m_ulIMUUpdateIntervalNS = SDL_MS_TO_NS(5); // Start off at 5 ms update rate + + // Set up for vertical mode + ctx->m_bVerticalMode = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false); + + return true; +} + +static bool HIDAPI_DriverSwitch_ActuallyRumbleJoystick(SDL_DriverSwitch_Context *ctx, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + /* Experimentally determined rumble values. These will only matter on some controllers as tested ones + * seem to disregard these and just use any non-zero rumble values as a binary flag for constant rumble + * + * More information about these values can be found here: + * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md + */ + const Uint16 k_usHighFreq = 0x0074; + const Uint8 k_ucHighFreqAmp = EncodeRumbleHighAmplitude(high_frequency_rumble); + const Uint8 k_ucLowFreq = 0x3D; + const Uint16 k_usLowFreqAmp = EncodeRumbleLowAmplitude(low_frequency_rumble); + + if (low_frequency_rumble || high_frequency_rumble) { + EncodeRumble(&ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); + EncodeRumble(&ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); + } else { + SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); + SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); + } + + ctx->m_bRumbleActive = (low_frequency_rumble || high_frequency_rumble); + + if (!WriteRumble(ctx)) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static bool HIDAPI_DriverSwitch_SendPendingRumble(SDL_DriverSwitch_Context *ctx) +{ + if (SDL_GetTicks() < (ctx->m_ulRumbleSent + RUMBLE_WRITE_FREQUENCY_MS)) { + return true; + } + + if (ctx->m_bRumblePending) { + Uint16 low_frequency_rumble = (Uint16)(ctx->m_unRumblePending >> 16); + Uint16 high_frequency_rumble = (Uint16)ctx->m_unRumblePending; + +#ifdef DEBUG_RUMBLE + SDL_Log("Sent pending rumble %d/%d, %d ms after previous rumble", low_frequency_rumble, high_frequency_rumble, SDL_GetTicks() - ctx->m_ulRumbleSent); +#endif + ctx->m_bRumblePending = false; + ctx->m_unRumblePending = 0; + + return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, low_frequency_rumble, high_frequency_rumble); + } + + if (ctx->m_bRumbleZeroPending) { + ctx->m_bRumbleZeroPending = false; + +#ifdef DEBUG_RUMBLE + SDL_Log("Sent pending zero rumble, %d ms after previous rumble", SDL_GetTicks() - ctx->m_ulRumbleSent); +#endif + return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, 0, 0); + } + + return true; +} + +static bool HIDAPI_DriverSwitch_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; + + if (ctx->m_bInputOnly) { + return SDL_Unsupported(); + } + + if (device->parent) { + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { + // Just handle low frequency rumble + high_frequency_rumble = 0; + } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + // Just handle high frequency rumble + low_frequency_rumble = 0; + } + } + + if (ctx->m_bRumblePending) { + if (!HIDAPI_DriverSwitch_SendPendingRumble(ctx)) { + return false; + } + } + + if (SDL_GetTicks() < (ctx->m_ulRumbleSent + RUMBLE_WRITE_FREQUENCY_MS)) { + if (low_frequency_rumble || high_frequency_rumble) { + Uint32 unRumblePending = ((Uint32)low_frequency_rumble << 16) | high_frequency_rumble; + + // Keep the highest rumble intensity in the given interval + if (unRumblePending > ctx->m_unRumblePending) { + ctx->m_unRumblePending = unRumblePending; + } + ctx->m_bRumblePending = true; + ctx->m_bRumbleZeroPending = false; + } else { + // When rumble is complete, turn it off + ctx->m_bRumbleZeroPending = true; + } + return true; + } + +#ifdef DEBUG_RUMBLE + SDL_Log("Sent rumble %d/%d", low_frequency_rumble, high_frequency_rumble); +#endif + + return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, low_frequency_rumble, high_frequency_rumble); +} + +static bool HIDAPI_DriverSwitch_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverSwitch_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; + Uint32 result = 0; + + if (ctx->m_bPlayerLights && !ctx->m_bInputOnly) { + result |= SDL_JOYSTICK_CAP_PLAYER_LED; + } + + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_ProController && !ctx->m_bInputOnly) { + // Doesn't have an RGB LED, so don't return SDL_JOYSTICK_CAP_RGB_LED here + result |= SDL_JOYSTICK_CAP_RUMBLE; + } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + result |= SDL_JOYSTICK_CAP_RUMBLE; + } + return result; +} + +static bool HIDAPI_DriverSwitch_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSwitch_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; + + if (size == sizeof(SwitchCommonOutputPacket_t)) { + const SwitchCommonOutputPacket_t *packet = (SwitchCommonOutputPacket_t *)data; + + if (packet->ucPacketType != k_eSwitchOutputReportIDs_Rumble) { + return SDL_SetError("Unknown Nintendo Switch Pro effect type"); + } + + SDL_copyp(&ctx->m_RumblePacket.rumbleData[0], &packet->rumbleData[0]); + SDL_copyp(&ctx->m_RumblePacket.rumbleData[1], &packet->rumbleData[1]); + if (!WriteRumble(ctx)) { + return false; + } + + // This overwrites any internal rumble + ctx->m_bRumblePending = false; + ctx->m_bRumbleZeroPending = false; + return true; + } else if (size >= 2 && size <= 256) { + const Uint8 *payload = (const Uint8 *)data; + ESwitchSubcommandIDs cmd = (ESwitchSubcommandIDs)payload[0]; + + if (cmd == k_eSwitchSubcommandIDs_SetInputReportMode && !device->is_bluetooth) { + // Going into simple mode over USB disables input reports, so don't do that + return true; + } + if (cmd == k_eSwitchSubcommandIDs_SetHomeLight && !HasHomeLED(ctx)) { + // Setting the home LED when it's not supported can cause the controller to reset + return true; + } + + if (!WriteSubcommand(ctx, cmd, &payload[1], (Uint8)(size - 1), NULL)) { + return false; + } + return true; + } + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSwitch_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; + + UpdateEnhancedModeOnApplicationUsage(ctx); + + if (!ctx->m_bSensorsSupported || (enabled && !ctx->m_bEnhancedMode)) { + return SDL_Unsupported(); + } + + ctx->m_bReportSensors = enabled; + ctx->m_unIMUSamples = 0; + ctx->m_ulIMUSampleTimestampNS = SDL_GetTicksNS(); + + UpdateInputMode(ctx); + SetIMUEnabled(ctx, enabled); + + return true; +} + +static void HandleInputOnlyControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchInputOnlyControllerStatePacket_t *packet) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (packet->rgucButtons[0] != ctx->m_lastInputOnlyState.rgucButtons[0]) { + Uint8 data = packet->rgucButtons[0]; + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0)); + } + + if (packet->rgucButtons[1] != ctx->m_lastInputOnlyState.rgucButtons[1]) { + Uint8 data = packet->rgucButtons[1]; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0)); + } + + if (packet->ucStickHat != ctx->m_lastInputOnlyState.ucStickHat) { + Uint8 hat; + + switch (packet->ucStickHat) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + axis = (packet->rgucButtons[0] & 0x40) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + axis = (packet->rgucButtons[0] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + if (packet->rgucJoystickLeft[0] != ctx->m_lastInputOnlyState.rgucJoystickLeft[0]) { + axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickLeft[0], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + } + + if (packet->rgucJoystickLeft[1] != ctx->m_lastInputOnlyState.rgucJoystickLeft[1]) { + axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickLeft[1], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + } + + if (packet->rgucJoystickRight[0] != ctx->m_lastInputOnlyState.rgucJoystickRight[0]) { + axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickRight[0], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + } + + if (packet->rgucJoystickRight[1] != ctx->m_lastInputOnlyState.rgucJoystickRight[1]) { + axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickRight[1], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } + + ctx->m_lastInputOnlyState = *packet; +} + +static void HandleCombinedSimpleControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet) +{ + if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { + Uint8 data = packet->rgucButtons[0]; + Uint8 hat = 0; + + if (data & 0x01) { + hat |= SDL_HAT_LEFT; + } + if (data & 0x02) { + hat |= SDL_HAT_DOWN; + } + if (data & 0x04) { + hat |= SDL_HAT_UP; + } + if (data & 0x08) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x20) != 0)); + } + + if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) { + Uint8 data = packet->rgucButtons[1]; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x40) != 0)); + } + + Sint16 axis = (packet->rgucButtons[1] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) { + switch (packet->ucStickHat) { + case 0: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); + break; + case 1: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); + break; + case 2: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); + break; + case 3: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); + break; + case 4: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); + break; + case 5: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); + break; + case 6: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); + break; + case 7: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); + break; + default: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); + break; + } + } +} + +static void HandleCombinedSimpleControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet) +{ + if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { + Uint8 data = packet->rgucButtons[0]; + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x20) != 0)); + } + + if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) { + Uint8 data = packet->rgucButtons[1]; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x40) != 0)); + } + + Sint16 axis = (packet->rgucButtons[1] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) { + switch (packet->ucStickHat) { + case 0: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, 0); + break; + case 1: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MIN); + break; + case 2: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, 0); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MIN); + break; + case 3: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MIN); + break; + case 4: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, 0); + break; + case 5: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MAX); + break; + case 6: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, 0); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MAX); + break; + case 7: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MAX); + break; + default: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, 0); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, 0); + break; + } + } +} + +static void HandleMiniSimpleControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet) +{ + if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { + Uint8 data = packet->rgucButtons[0]; + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0)); + } + + if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) { + Uint8 data = packet->rgucButtons[1]; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x80) != 0)); + } + + if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) { + switch (packet->ucStickHat) { + case 0: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); + break; + case 1: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); + break; + case 2: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); + break; + case 3: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); + break; + case 4: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); + break; + case 5: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); + break; + case 6: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); + break; + case 7: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); + break; + default: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); + break; + } + } +} + +static void HandleMiniSimpleControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet) +{ + if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { + Uint8 data = packet->rgucButtons[0]; + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0)); + } + + if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) { + Uint8 data = packet->rgucButtons[1]; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x80) != 0)); + } + + if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) { + switch (packet->ucStickHat) { + case 0: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); + break; + case 1: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); + break; + case 2: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); + break; + case 3: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); + break; + case 4: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); + break; + case 5: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); + break; + case 6: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); + break; + case 7: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); + break; + default: + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); + break; + } + } +} + +static void HandleSimpleControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet) +{ + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { + if (ctx->device->parent || ctx->m_bVerticalMode) { + HandleCombinedSimpleControllerStateL(timestamp, joystick, ctx, packet); + } else { + HandleMiniSimpleControllerStateL(timestamp, joystick, ctx, packet); + } + } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + if (ctx->device->parent || ctx->m_bVerticalMode) { + HandleCombinedSimpleControllerStateR(timestamp, joystick, ctx, packet); + } else { + HandleMiniSimpleControllerStateR(timestamp, joystick, ctx, packet); + } + } else { + Sint16 axis; + + if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { + Uint8 data = packet->rgucButtons[0]; + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0)); + } + + if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) { + Uint8 data = packet->rgucButtons[1]; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0)); + } + + if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) { + Uint8 hat; + + switch (packet->ucStickHat) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + axis = (packet->rgucButtons[0] & 0x40) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + axis = ((packet->rgucButtons[0] & 0x80) || (packet->rgucButtons[1] & 0x80)) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + axis = ApplySimpleStickCalibration(ctx, 0, 0, packet->sJoystickLeft[0]); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + + axis = ApplySimpleStickCalibration(ctx, 0, 1, packet->sJoystickLeft[1]); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + + axis = ApplySimpleStickCalibration(ctx, 1, 0, packet->sJoystickRight[0]); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + + axis = ApplySimpleStickCalibration(ctx, 1, 1, packet->sJoystickRight[1]); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } + + ctx->m_lastSimpleState = *packet; +} + +static void SendSensorUpdate(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SDL_SensorType type, Uint64 sensor_timestamp, const Sint16 *values) +{ + float data[3]; + + /* Note the order of components has been shuffled to match PlayStation controllers, + * since that's our de facto standard from already supporting those controllers, and + * users will want consistent axis mappings across devices. + */ + if (type == SDL_SENSOR_GYRO || type == SDL_SENSOR_GYRO_L || type == SDL_SENSOR_GYRO_R) { + data[0] = -(ctx->m_IMUScaleData.fGyroScaleY * (float)values[1]); + data[1] = ctx->m_IMUScaleData.fGyroScaleZ * (float)values[2]; + data[2] = -(ctx->m_IMUScaleData.fGyroScaleX * (float)values[0]); + } else { + data[0] = -(ctx->m_IMUScaleData.fAccelScaleY * (float)values[1]); + data[1] = ctx->m_IMUScaleData.fAccelScaleZ * (float)values[2]; + data[2] = -(ctx->m_IMUScaleData.fAccelScaleX * (float)values[0]); + } + + // Right Joy-Con flips some axes, so let's flip them back for consistency + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + data[0] = -data[0]; + data[1] = -data[1]; + } + + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft && + !ctx->device->parent && !ctx->m_bVerticalMode) { + // Mini-gamepad mode, swap some axes around + float tmp = data[2]; + data[2] = -data[0]; + data[0] = tmp; + } + + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight && + !ctx->device->parent && !ctx->m_bVerticalMode) { + // Mini-gamepad mode, swap some axes around + float tmp = data[2]; + data[2] = data[0]; + data[0] = -tmp; + } + + SDL_SendJoystickSensor(timestamp, joystick, type, sensor_timestamp, data, 3); +} + +static void HandleCombinedControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) +{ + Sint16 axis; + + if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { + Uint8 data = packet->controllerState.rgucButtons[1]; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0)); + } + + if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) { + Uint8 data = packet->controllerState.rgucButtons[2]; + Uint8 hat = 0; + + if (data & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data & 0x02) { + hat |= SDL_HAT_UP; + } + if (data & 0x04) { + hat |= SDL_HAT_RIGHT; + } + if (data & 0x08) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x40) != 0)); + axis = (data & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + } + + axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8); + axis = ApplyStickCalibration(ctx, 0, 0, axis); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + + axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4); + axis = ApplyStickCalibration(ctx, 0, 1, axis); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); +} + +static void HandleCombinedControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) +{ + Sint16 axis; + + if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { + Uint8 data = packet->controllerState.rgucButtons[0]; + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x40) != 0)); + axis = (data & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + } + + if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { + Uint8 data = packet->controllerState.rgucButtons[1]; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); + } + + axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8); + axis = ApplyStickCalibration(ctx, 1, 0, axis); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + + axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4); + axis = ApplyStickCalibration(ctx, 1, 1, axis); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); +} + +static void HandleMiniControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) +{ + Sint16 axis; + + if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { + Uint8 data = packet->controllerState.rgucButtons[1]; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x20) != 0)); + } + + if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) { + Uint8 data = packet->controllerState.rgucButtons[2]; + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x80) != 0)); + } + + axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8); + axis = ApplyStickCalibration(ctx, 0, 0, axis); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); + + axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4); + axis = ApplyStickCalibration(ctx, 0, 1, axis); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, ~axis); +} + +static void HandleMiniControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) +{ + Sint16 axis; + + if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { + Uint8 data = packet->controllerState.rgucButtons[0]; + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x80) != 0)); + } + + if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { + Uint8 data = packet->controllerState.rgucButtons[1]; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); + } + + axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8); + axis = ApplyStickCalibration(ctx, 1, 0, axis); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + + axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4); + axis = ApplyStickCalibration(ctx, 1, 1, axis); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); +} + +static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) SDL_NO_THREAD_SAFETY_ANALYSIS // We unlock and lock the device lock to be able to change IMU state +{ + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { + if (ctx->device->parent || ctx->m_bVerticalMode) { + HandleCombinedControllerStateL(timestamp, joystick, ctx, packet); + } else { + HandleMiniControllerStateL(timestamp, joystick, ctx, packet); + } + } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + if (ctx->device->parent || ctx->m_bVerticalMode) { + HandleCombinedControllerStateR(timestamp, joystick, ctx, packet); + } else { + HandleMiniControllerStateR(timestamp, joystick, ctx, packet); + } + } else { + Sint16 axis; + + if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { + Uint8 data = packet->controllerState.rgucButtons[0]; + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x40) != 0)); + } + + if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { + Uint8 data = packet->controllerState.rgucButtons[1]; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0)); + } + + if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) { + Uint8 data = packet->controllerState.rgucButtons[2]; + Uint8 hat = 0; + + if (data & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data & 0x02) { + hat |= SDL_HAT_UP; + } + if (data & 0x04) { + hat |= SDL_HAT_RIGHT; + } + if (data & 0x08) { + hat |= SDL_HAT_LEFT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x40) != 0)); + } + + axis = (packet->controllerState.rgucButtons[0] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + axis = (packet->controllerState.rgucButtons[2] & 0x80) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8); + axis = ApplyStickCalibration(ctx, 0, 0, axis); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + + axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4); + axis = ApplyStickCalibration(ctx, 0, 1, axis); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); + + axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8); + axis = ApplyStickCalibration(ctx, 1, 0, axis); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + + axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4); + axis = ApplyStickCalibration(ctx, 1, 1, axis); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); + } + + /* High nibble of battery/connection byte is battery level, low nibble is connection status (always 0 on 8BitDo Pro 2) + * LSB of connection nibble is USB/Switch connection status + * LSB of the battery nibble is used to report charging. + * The battery level is reported from 0(empty)-8(full) + */ + SDL_PowerState state; + int charging = (packet->controllerState.ucBatteryAndConnection & 0x10); + int level = (packet->controllerState.ucBatteryAndConnection & 0xE0) >> 4; + int percent = (int)SDL_roundf((level / 8.0f) * 100.0f); + + if (charging) { + if (level == 8) { + state = SDL_POWERSTATE_CHARGED; + } else { + state = SDL_POWERSTATE_CHARGING; + } + } else { + state = SDL_POWERSTATE_ON_BATTERY; + } + SDL_SendJoystickPowerInfo(joystick, state, percent); + + if (ctx->m_bReportSensors) { + bool bHasSensorData = (packet->imuState[0].sAccelZ != 0 || + packet->imuState[0].sAccelY != 0 || + packet->imuState[0].sAccelX != 0); + if (bHasSensorData) { + const Uint32 IMU_UPDATE_RATE_SAMPLE_FREQUENCY = 1000; + Uint64 sensor_timestamp[3]; + + ctx->m_bHasSensorData = true; + + // We got three IMU samples, calculate the IMU update rate and timestamps + ctx->m_unIMUSamples += 3; + if (ctx->m_unIMUSamples >= IMU_UPDATE_RATE_SAMPLE_FREQUENCY) { + Uint64 now = SDL_GetTicksNS(); + Uint64 elapsed = (now - ctx->m_ulIMUSampleTimestampNS); + + if (elapsed > 0) { + ctx->m_ulIMUUpdateIntervalNS = elapsed / ctx->m_unIMUSamples; + } + ctx->m_unIMUSamples = 0; + ctx->m_ulIMUSampleTimestampNS = now; + } + + ctx->m_ulTimestampNS += ctx->m_ulIMUUpdateIntervalNS; + sensor_timestamp[0] = ctx->m_ulTimestampNS; + ctx->m_ulTimestampNS += ctx->m_ulIMUUpdateIntervalNS; + sensor_timestamp[1] = ctx->m_ulTimestampNS; + ctx->m_ulTimestampNS += ctx->m_ulIMUUpdateIntervalNS; + sensor_timestamp[2] = ctx->m_ulTimestampNS; + + if (!ctx->device->parent || + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[0], &packet->imuState[2].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[0], &packet->imuState[2].sAccelX); + + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[1], &packet->imuState[1].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[1], &packet->imuState[1].sAccelX); + + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[2], &packet->imuState[0].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[2], &packet->imuState[0].sAccelX); + } + + if (ctx->device->parent && + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[0], &packet->imuState[2].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[0], &packet->imuState[2].sAccelX); + + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[1], &packet->imuState[1].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[1], &packet->imuState[1].sAccelX); + + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[2], &packet->imuState[0].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[2], &packet->imuState[0].sAccelX); + } + if (ctx->device->parent && + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[0], &packet->imuState[2].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[0], &packet->imuState[2].sAccelX); + + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[1], &packet->imuState[1].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[1], &packet->imuState[1].sAccelX); + + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[2], &packet->imuState[0].sGyroX); + SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[2], &packet->imuState[0].sAccelX); + } + + } else if (ctx->m_bHasSensorData) { + // Uh oh, someone turned off the IMU? + const int IMU_RESET_DELAY_MS = 3000; + Uint64 now = SDL_GetTicks(); + + if (now >= (ctx->m_ulLastIMUReset + IMU_RESET_DELAY_MS)) { + SDL_HIDAPI_Device *device = ctx->device; + + if (device->updating) { + SDL_UnlockMutex(device->dev_lock); + } + + SetIMUEnabled(ctx, true); + + if (device->updating) { + SDL_LockMutex(device->dev_lock); + } + ctx->m_ulLastIMUReset = now; + } + + } else { + // We have never gotten IMU data, probably not supported on this device + } + } + + ctx->m_lastFullState = *packet; +} + +static bool HIDAPI_DriverSwitch_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; + SDL_Joystick *joystick = NULL; + int size; + int packet_count = 0; + Uint64 now = SDL_GetTicks(); + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } + + while ((size = ReadInput(ctx)) > 0) { +#ifdef DEBUG_SWITCH_PROTOCOL + HIDAPI_DumpPacket("Nintendo Switch packet: size = %d", ctx->m_rgucReadBuffer, size); +#endif + ++packet_count; + ctx->m_ulLastInput = now; + + if (!joystick) { + continue; + } + + if (ctx->m_bInputOnly) { + HandleInputOnlyControllerState(joystick, ctx, (SwitchInputOnlyControllerStatePacket_t *)&ctx->m_rgucReadBuffer[0]); + } else { + if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) { + continue; + } + + ctx->m_nCurrentInputMode = ctx->m_rgucReadBuffer[0]; + + switch (ctx->m_rgucReadBuffer[0]) { + case k_eSwitchInputReportIDs_SimpleControllerState: + HandleSimpleControllerState(joystick, ctx, (SwitchSimpleStatePacket_t *)&ctx->m_rgucReadBuffer[1]); + break; + case k_eSwitchInputReportIDs_FullControllerState: + case k_eSwitchInputReportIDs_FullControllerAndMcuState: + // This is the extended report, we can enable sensors now in auto mode + UpdateEnhancedModeOnEnhancedReport(ctx); + + HandleFullControllerState(joystick, ctx, (SwitchStatePacket_t *)&ctx->m_rgucReadBuffer[1]); + break; + default: + break; + } + } + } + + if (joystick) { + if (packet_count == 0) { + if (!ctx->m_bInputOnly && !device->is_bluetooth && + ctx->device->product_id != USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) { + const int INPUT_WAIT_TIMEOUT_MS = 100; + if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) { + // Steam may have put the controller back into non-reporting mode + bool wasSyncWrite = ctx->m_bSyncWrite; + + ctx->m_bSyncWrite = true; + WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, false); + ctx->m_bSyncWrite = wasSyncWrite; + } + } else if (device->is_bluetooth && + ctx->m_nCurrentInputMode != k_eSwitchInputReportIDs_SimpleControllerState) { + const int INPUT_WAIT_TIMEOUT_MS = 3000; + if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) { + // Bluetooth may have disconnected, try reopening the controller + size = -1; + } + } + } + + if (ctx->m_bRumblePending || ctx->m_bRumbleZeroPending) { + HIDAPI_DriverSwitch_SendPendingRumble(ctx); + } else if (ctx->m_bRumbleActive && + now >= (ctx->m_ulRumbleSent + RUMBLE_REFRESH_FREQUENCY_MS)) { +#ifdef DEBUG_RUMBLE + SDL_Log("Sent continuing rumble, %d ms after previous rumble", now - ctx->m_ulRumbleSent); +#endif + WriteRumble(ctx); + } + } + + // Reconnect the Bluetooth device once the USB device is gone + if (device->num_joysticks == 0 && device->is_bluetooth && packet_count > 0 && + !device->parent && + !HIDAPI_HasConnectedUSBDevice(device->serial)) { + HIDAPI_JoystickConnected(device, NULL); + } + + if (size < 0 && device->num_joysticks > 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverSwitch_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; + + if (!ctx->m_bInputOnly) { + // Restore simple input mode for other applications + if (!ctx->m_nInitialInputMode || + ctx->m_nInitialInputMode == k_eSwitchInputReportIDs_SimpleControllerState) { + SetInputMode(ctx, k_eSwitchInputReportIDs_SimpleControllerState); + } + } + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_EnhancedReportsChanged, ctx); + + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED, + SDL_HomeLEDHintChanged, ctx); + } else { + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, + SDL_HomeLEDHintChanged, ctx); + } + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + ctx->joystick = NULL; + + ctx->m_bReportSensors = false; + ctx->m_bEnhancedMode = false; + ctx->m_bEnhancedModeAvailable = false; +} + +static void HIDAPI_DriverSwitch_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic = { + SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, + true, + HIDAPI_DriverNintendoClassic_RegisterHints, + HIDAPI_DriverNintendoClassic_UnregisterHints, + HIDAPI_DriverNintendoClassic_IsEnabled, + HIDAPI_DriverNintendoClassic_IsSupportedDevice, + HIDAPI_DriverSwitch_InitDevice, + HIDAPI_DriverSwitch_GetDevicePlayerIndex, + HIDAPI_DriverSwitch_SetDevicePlayerIndex, + HIDAPI_DriverSwitch_UpdateDevice, + HIDAPI_DriverSwitch_OpenJoystick, + HIDAPI_DriverSwitch_RumbleJoystick, + HIDAPI_DriverSwitch_RumbleJoystickTriggers, + HIDAPI_DriverSwitch_GetJoystickCapabilities, + HIDAPI_DriverSwitch_SetJoystickLED, + HIDAPI_DriverSwitch_SendJoystickEffect, + HIDAPI_DriverSwitch_SetJoystickSensorsEnabled, + HIDAPI_DriverSwitch_CloseJoystick, + HIDAPI_DriverSwitch_FreeDevice, +}; + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons = { + SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, + true, + HIDAPI_DriverJoyCons_RegisterHints, + HIDAPI_DriverJoyCons_UnregisterHints, + HIDAPI_DriverJoyCons_IsEnabled, + HIDAPI_DriverJoyCons_IsSupportedDevice, + HIDAPI_DriverSwitch_InitDevice, + HIDAPI_DriverSwitch_GetDevicePlayerIndex, + HIDAPI_DriverSwitch_SetDevicePlayerIndex, + HIDAPI_DriverSwitch_UpdateDevice, + HIDAPI_DriverSwitch_OpenJoystick, + HIDAPI_DriverSwitch_RumbleJoystick, + HIDAPI_DriverSwitch_RumbleJoystickTriggers, + HIDAPI_DriverSwitch_GetJoystickCapabilities, + HIDAPI_DriverSwitch_SetJoystickLED, + HIDAPI_DriverSwitch_SendJoystickEffect, + HIDAPI_DriverSwitch_SetJoystickSensorsEnabled, + HIDAPI_DriverSwitch_CloseJoystick, + HIDAPI_DriverSwitch_FreeDevice, +}; + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch = { + SDL_HINT_JOYSTICK_HIDAPI_SWITCH, + true, + HIDAPI_DriverSwitch_RegisterHints, + HIDAPI_DriverSwitch_UnregisterHints, + HIDAPI_DriverSwitch_IsEnabled, + HIDAPI_DriverSwitch_IsSupportedDevice, + HIDAPI_DriverSwitch_InitDevice, + HIDAPI_DriverSwitch_GetDevicePlayerIndex, + HIDAPI_DriverSwitch_SetDevicePlayerIndex, + HIDAPI_DriverSwitch_UpdateDevice, + HIDAPI_DriverSwitch_OpenJoystick, + HIDAPI_DriverSwitch_RumbleJoystick, + HIDAPI_DriverSwitch_RumbleJoystickTriggers, + HIDAPI_DriverSwitch_GetJoystickCapabilities, + HIDAPI_DriverSwitch_SetJoystickLED, + HIDAPI_DriverSwitch_SendJoystickEffect, + HIDAPI_DriverSwitch_SetJoystickSensorsEnabled, + HIDAPI_DriverSwitch_CloseJoystick, + HIDAPI_DriverSwitch_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_SWITCH + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_wii.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_wii.c new file mode 100644 index 0000000..fb3e164 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_wii.c @@ -0,0 +1,1617 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" +#include "SDL_hidapi_nintendo.h" + +#ifdef SDL_JOYSTICK_HIDAPI_WII + +// Define this if you want to log all packets from the controller +// #define DEBUG_WII_PROTOCOL + +#define ENABLE_CONTINUOUS_REPORTING true + +#define INPUT_WAIT_TIMEOUT_MS (3 * 1000) +#define MOTION_PLUS_UPDATE_TIME_MS (8 * 1000) +#define STATUS_UPDATE_TIME_MS (15 * 60 * 1000) + +#define WII_EXTENSION_NONE 0x2E2E +#define WII_EXTENSION_UNINITIALIZED 0xFFFF +#define WII_EXTENSION_NUNCHUK 0x0000 +#define WII_EXTENSION_GAMEPAD 0x0101 +#define WII_EXTENSION_WIIUPRO 0x0120 +#define WII_EXTENSION_MOTIONPLUS_MASK 0xF0FF +#define WII_EXTENSION_MOTIONPLUS_ID 0x0005 + +#define WII_MOTIONPLUS_MODE_NONE 0x00 +#define WII_MOTIONPLUS_MODE_STANDARD 0x04 +#define WII_MOTIONPLUS_MODE_NUNCHUK 0x05 +#define WII_MOTIONPLUS_MODE_GAMEPAD 0x07 + +typedef enum +{ + k_eWiiInputReportIDs_Status = 0x20, + k_eWiiInputReportIDs_ReadMemory = 0x21, + k_eWiiInputReportIDs_Acknowledge = 0x22, + k_eWiiInputReportIDs_ButtonData0 = 0x30, + k_eWiiInputReportIDs_ButtonData1 = 0x31, + k_eWiiInputReportIDs_ButtonData2 = 0x32, + k_eWiiInputReportIDs_ButtonData3 = 0x33, + k_eWiiInputReportIDs_ButtonData4 = 0x34, + k_eWiiInputReportIDs_ButtonData5 = 0x35, + k_eWiiInputReportIDs_ButtonData6 = 0x36, + k_eWiiInputReportIDs_ButtonData7 = 0x37, + k_eWiiInputReportIDs_ButtonDataD = 0x3D, + k_eWiiInputReportIDs_ButtonDataE = 0x3E, + k_eWiiInputReportIDs_ButtonDataF = 0x3F, +} EWiiInputReportIDs; + +typedef enum +{ + k_eWiiOutputReportIDs_Rumble = 0x10, + k_eWiiOutputReportIDs_LEDs = 0x11, + k_eWiiOutputReportIDs_DataReportingMode = 0x12, + k_eWiiOutputReportIDs_IRCameraEnable = 0x13, + k_eWiiOutputReportIDs_SpeakerEnable = 0x14, + k_eWiiOutputReportIDs_StatusRequest = 0x15, + k_eWiiOutputReportIDs_WriteMemory = 0x16, + k_eWiiOutputReportIDs_ReadMemory = 0x17, + k_eWiiOutputReportIDs_SpeakerData = 0x18, + k_eWiiOutputReportIDs_SpeakerMute = 0x19, + k_eWiiOutputReportIDs_IRCameraEnable2 = 0x1a, +} EWiiOutputReportIDs; + +typedef enum +{ + k_eWiiPlayerLEDs_P1 = 0x10, + k_eWiiPlayerLEDs_P2 = 0x20, + k_eWiiPlayerLEDs_P3 = 0x40, + k_eWiiPlayerLEDs_P4 = 0x80, +} EWiiPlayerLEDs; + +typedef enum +{ + k_eWiiCommunicationState_None, // No special communications happening + k_eWiiCommunicationState_CheckMotionPlusStage1, // Sent standard extension identify request + k_eWiiCommunicationState_CheckMotionPlusStage2, // Sent Motion Plus extension identify request +} EWiiCommunicationState; + +typedef enum +{ + k_eWiiButtons_A = SDL_GAMEPAD_BUTTON_MISC1, + k_eWiiButtons_B, + k_eWiiButtons_One, + k_eWiiButtons_Two, + k_eWiiButtons_Plus, + k_eWiiButtons_Minus, + k_eWiiButtons_Home, + k_eWiiButtons_DPad_Up, + k_eWiiButtons_DPad_Down, + k_eWiiButtons_DPad_Left, + k_eWiiButtons_DPad_Right, + k_eWiiButtons_Max +} EWiiButtons; + +#define k_unWiiPacketDataLength 22 + +typedef struct +{ + Uint8 rgucBaseButtons[2]; + Uint8 rgucAccelerometer[3]; + Uint8 rgucExtension[21]; + bool hasBaseButtons; + bool hasAccelerometer; + Uint8 ucNExtensionBytes; +} WiiButtonData; + +typedef struct +{ + Uint16 min; + Uint16 max; + Uint16 center; + Uint16 deadzone; +} StickCalibrationData; + +typedef struct +{ + SDL_HIDAPI_Device *device; + SDL_Joystick *joystick; + Uint64 timestamp; + EWiiCommunicationState m_eCommState; + EWiiExtensionControllerType m_eExtensionControllerType; + bool m_bPlayerLights; + int m_nPlayerIndex; + bool m_bRumbleActive; + bool m_bMotionPlusPresent; + Uint8 m_ucMotionPlusMode; + bool m_bReportSensors; + Uint8 m_rgucReadBuffer[k_unWiiPacketDataLength]; + Uint64 m_ulLastInput; + Uint64 m_ulLastStatus; + Uint64 m_ulNextMotionPlusCheck; + bool m_bDisconnected; + + StickCalibrationData m_StickCalibrationData[6]; +} SDL_DriverWii_Context; + +static void HIDAPI_DriverWii_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII, callback, userdata); +} + +static void HIDAPI_DriverWii_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII, callback, userdata); +} + +static bool HIDAPI_DriverWii_IsEnabled(void) +{ +#if 1 // This doesn't work with the dolphinbar, so don't enable by default right now + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII, false); +#else + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, + SDL_HIDAPI_DEFAULT)); +#endif +} + +static bool HIDAPI_DriverWii_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_NINTENDO && + (product_id == USB_PRODUCT_NINTENDO_WII_REMOTE || + product_id == USB_PRODUCT_NINTENDO_WII_REMOTE2)) { + return true; + } + return false; +} + +static int ReadInput(SDL_DriverWii_Context *ctx) +{ + int size; + + // Make sure we don't try to read at the same time a write is happening + if (SDL_GetAtomicInt(&ctx->device->rumble_pending) > 0) { + return 0; + } + + size = SDL_hid_read_timeout(ctx->device->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0); +#ifdef DEBUG_WII_PROTOCOL + if (size > 0) { + HIDAPI_DumpPacket("Wii packet: size = %d", ctx->m_rgucReadBuffer, size); + } +#endif + return size; +} + +static bool WriteOutput(SDL_DriverWii_Context *ctx, const Uint8 *data, int size, bool sync) +{ +#ifdef DEBUG_WII_PROTOCOL + if (size > 0) { + HIDAPI_DumpPacket("Wii write packet: size = %d", data, size); + } +#endif + if (sync) { + return SDL_hid_write(ctx->device->dev, data, size) >= 0; + } else { + // Use the rumble thread for general asynchronous writes + if (!SDL_HIDAPI_LockRumble()) { + return false; + } + return SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size) >= 0; + } +} + +static bool ReadInputSync(SDL_DriverWii_Context *ctx, EWiiInputReportIDs expectedID, bool (*isMine)(const Uint8 *)) +{ + Uint64 endTicks = SDL_GetTicks() + 250; // Seeing successful reads after about 200 ms + + int nRead = 0; + while ((nRead = ReadInput(ctx)) != -1) { + if (nRead > 0) { + if (ctx->m_rgucReadBuffer[0] == expectedID && (!isMine || isMine(ctx->m_rgucReadBuffer))) { + return true; + } + } else { + if (SDL_GetTicks() >= endTicks) { + break; + } + SDL_Delay(1); + } + } + SDL_SetError("Read timed out"); + return false; +} + +static bool IsWriteMemoryResponse(const Uint8 *data) +{ + return data[3] == k_eWiiOutputReportIDs_WriteMemory; +} + +static bool WriteRegister(SDL_DriverWii_Context *ctx, Uint32 address, const Uint8 *data, int size, bool sync) +{ + Uint8 writeRequest[k_unWiiPacketDataLength]; + + SDL_zeroa(writeRequest); + writeRequest[0] = k_eWiiOutputReportIDs_WriteMemory; + writeRequest[1] = (Uint8)(0x04 | (Uint8)ctx->m_bRumbleActive); + writeRequest[2] = (address >> 16) & 0xff; + writeRequest[3] = (address >> 8) & 0xff; + writeRequest[4] = address & 0xff; + writeRequest[5] = (Uint8)size; + SDL_assert(size > 0 && size <= 16); + SDL_memcpy(writeRequest + 6, data, size); + + if (!WriteOutput(ctx, writeRequest, sizeof(writeRequest), sync)) { + return false; + } + if (sync) { + // Wait for response + if (!ReadInputSync(ctx, k_eWiiInputReportIDs_Acknowledge, IsWriteMemoryResponse)) { + return false; + } + if (ctx->m_rgucReadBuffer[4]) { + SDL_SetError("Write memory failed: %u", ctx->m_rgucReadBuffer[4]); + return false; + } + } + return true; +} + +static bool ReadRegister(SDL_DriverWii_Context *ctx, Uint32 address, int size, bool sync) +{ + Uint8 readRequest[7]; + + readRequest[0] = k_eWiiOutputReportIDs_ReadMemory; + readRequest[1] = (Uint8)(0x04 | (Uint8)ctx->m_bRumbleActive); + readRequest[2] = (address >> 16) & 0xff; + readRequest[3] = (address >> 8) & 0xff; + readRequest[4] = address & 0xff; + readRequest[5] = (size >> 8) & 0xff; + readRequest[6] = size & 0xff; + + SDL_assert(size > 0 && size <= 0xffff); + + if (!WriteOutput(ctx, readRequest, sizeof(readRequest), sync)) { + return false; + } + if (sync) { + SDL_assert(size <= 16); // Only waiting for one packet is supported right now + // Wait for response + if (!ReadInputSync(ctx, k_eWiiInputReportIDs_ReadMemory, NULL)) { + return false; + } + } + return true; +} + +static bool SendExtensionIdentify(SDL_DriverWii_Context *ctx, bool sync) +{ + return ReadRegister(ctx, 0xA400FE, 2, sync); +} + +static bool ParseExtensionIdentifyResponse(SDL_DriverWii_Context *ctx, Uint16 *extension) +{ + int i; + + if (ctx->m_rgucReadBuffer[0] != k_eWiiInputReportIDs_ReadMemory) { + SDL_SetError("Unexpected extension response type"); + return false; + } + + if (ctx->m_rgucReadBuffer[4] != 0x00 || ctx->m_rgucReadBuffer[5] != 0xFE) { + SDL_SetError("Unexpected extension response address"); + return false; + } + + if (ctx->m_rgucReadBuffer[3] != 0x10) { + Uint8 error = (ctx->m_rgucReadBuffer[3] & 0xF); + + if (error == 7) { + // The extension memory isn't mapped + *extension = WII_EXTENSION_NONE; + return true; + } + + if (error) { + SDL_SetError("Failed to read extension type: %u", error); + } else { + SDL_SetError("Unexpected read length when reading extension type: %d", (ctx->m_rgucReadBuffer[3] >> 4) + 1); + } + return false; + } + + *extension = 0; + for (i = 6; i < 8; i++) { + *extension = *extension << 8 | ctx->m_rgucReadBuffer[i]; + } + return true; +} + +static EWiiExtensionControllerType GetExtensionType(Uint16 extension_id) +{ + switch (extension_id) { + case WII_EXTENSION_NONE: + return k_eWiiExtensionControllerType_None; + case WII_EXTENSION_NUNCHUK: + return k_eWiiExtensionControllerType_Nunchuk; + case WII_EXTENSION_GAMEPAD: + return k_eWiiExtensionControllerType_Gamepad; + case WII_EXTENSION_WIIUPRO: + return k_eWiiExtensionControllerType_WiiUPro; + default: + return k_eWiiExtensionControllerType_Unknown; + } +} + +static bool SendExtensionReset(SDL_DriverWii_Context *ctx, bool sync) +{ + bool result = true; + { + Uint8 data = 0x55; + result = result && WriteRegister(ctx, 0xA400F0, &data, sizeof(data), sync); + } + // This write will fail if there is no extension connected, that's fine + { + Uint8 data = 0x00; + (void)WriteRegister(ctx, 0xA400FB, &data, sizeof(data), sync); + } + return result; +} + +static bool GetMotionPlusState(SDL_DriverWii_Context *ctx, bool *connected, Uint8 *mode) +{ + Uint16 extension; + + if (connected) { + *connected = false; + } + if (mode) { + *mode = 0; + } + + if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) { + // The Wii U Pro controller never has the Motion Plus extension + return true; + } + + if (SendExtensionIdentify(ctx, true) && + ParseExtensionIdentifyResponse(ctx, &extension)) { + if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) { + // Motion Plus is currently active + if (connected) { + *connected = true; + } + if (mode) { + *mode = (extension >> 8); + } + return true; + } + } + + if (ReadRegister(ctx, 0xA600FE, 2, true) && + ParseExtensionIdentifyResponse(ctx, &extension)) { + if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) { + // Motion Plus is currently connected + if (connected) { + *connected = true; + } + } + return true; + } + + // Failed to read the register or parse the response + return false; +} + +static bool NeedsPeriodicMotionPlusCheck(SDL_DriverWii_Context *ctx, bool status_update) +{ + if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) { + // The Wii U Pro controller never has the Motion Plus extension + return false; + } + + if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE && !status_update) { + // We'll get a status update when Motion Plus is disconnected + return false; + } + + return true; +} + +static void SchedulePeriodicMotionPlusCheck(SDL_DriverWii_Context *ctx) +{ + ctx->m_ulNextMotionPlusCheck = SDL_GetTicks() + MOTION_PLUS_UPDATE_TIME_MS; +} + +static void CheckMotionPlusConnection(SDL_DriverWii_Context *ctx) +{ + SendExtensionIdentify(ctx, false); + + ctx->m_eCommState = k_eWiiCommunicationState_CheckMotionPlusStage1; +} + +static void ActivateMotionPlusWithMode(SDL_DriverWii_Context *ctx, Uint8 mode) +{ +#ifdef SDL_PLATFORM_LINUX + /* Linux drivers maintain a lot of state around the Motion Plus + * extension, so don't mess with it here. + */ +#else + WriteRegister(ctx, 0xA600FE, &mode, sizeof(mode), true); + + ctx->m_ucMotionPlusMode = mode; +#endif // LINUX +} + +static void ActivateMotionPlus(SDL_DriverWii_Context *ctx) +{ + Uint8 mode = WII_MOTIONPLUS_MODE_STANDARD; + + // Pick the pass-through mode based on the connected controller + if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) { + mode = WII_MOTIONPLUS_MODE_NUNCHUK; + } else if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Gamepad) { + mode = WII_MOTIONPLUS_MODE_GAMEPAD; + } + ActivateMotionPlusWithMode(ctx, mode); +} + +static void DeactivateMotionPlus(SDL_DriverWii_Context *ctx) +{ + Uint8 data = 0x55; + WriteRegister(ctx, 0xA400F0, &data, sizeof(data), true); + + // Wait for the deactivation status message + ReadInputSync(ctx, k_eWiiInputReportIDs_Status, NULL); + + ctx->m_ucMotionPlusMode = WII_MOTIONPLUS_MODE_NONE; +} + +static void UpdatePowerLevelWii(SDL_Joystick *joystick, Uint8 batteryLevelByte) +{ + int percent; + if (batteryLevelByte > 178) { + percent = 100; + } else if (batteryLevelByte > 51) { + percent = 70; + } else if (batteryLevelByte > 13) { + percent = 20; + } else { + percent = 5; + } + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent); +} + +static void UpdatePowerLevelWiiU(SDL_Joystick *joystick, Uint8 extensionBatteryByte) +{ + bool charging = !(extensionBatteryByte & 0x08); + bool pluggedIn = !(extensionBatteryByte & 0x04); + Uint8 batteryLevel = extensionBatteryByte >> 4; + + if (pluggedIn) { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; + } else { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + } + + /* Not sure if all Wii U Pro controllers act like this, but on mine + * 4, 3, and 2 are held for about 20 hours each + * 1 is held for about 6 hours + * 0 is held for about 2 hours + * No value above 4 has been observed. + */ + SDL_PowerState state; + int percent; + if (charging) { + state = SDL_POWERSTATE_CHARGING; + } else if (pluggedIn) { + state = SDL_POWERSTATE_CHARGED; + } else { + state = SDL_POWERSTATE_ON_BATTERY; + } + if (batteryLevel >= 4) { + percent = 100; + } else if (batteryLevel == 3) { + percent = 70; + } else if (batteryLevel == 2) { + percent = 40; + } else if (batteryLevel == 1) { + percent = 10; + } else { + percent = 3; + } + SDL_SendJoystickPowerInfo(joystick, state, percent); +} + +static EWiiInputReportIDs GetButtonPacketType(SDL_DriverWii_Context *ctx) +{ + switch (ctx->m_eExtensionControllerType) { + case k_eWiiExtensionControllerType_WiiUPro: + return k_eWiiInputReportIDs_ButtonDataD; + case k_eWiiExtensionControllerType_Nunchuk: + case k_eWiiExtensionControllerType_Gamepad: + if (ctx->m_bReportSensors) { + return k_eWiiInputReportIDs_ButtonData5; + } else { + return k_eWiiInputReportIDs_ButtonData2; + } + default: + if (ctx->m_bReportSensors) { + return k_eWiiInputReportIDs_ButtonData5; + } else { + return k_eWiiInputReportIDs_ButtonData0; + } + } +} + +static bool RequestButtonPacketType(SDL_DriverWii_Context *ctx, EWiiInputReportIDs type) +{ + Uint8 data[3]; + Uint8 tt = (Uint8)ctx->m_bRumbleActive; + + // Continuous reporting off, tt & 4 == 0 + if (ENABLE_CONTINUOUS_REPORTING) { + tt |= 4; + } + + data[0] = k_eWiiOutputReportIDs_DataReportingMode; + data[1] = tt; + data[2] = type; + return WriteOutput(ctx, data, sizeof(data), false); +} + +static void ResetButtonPacketType(SDL_DriverWii_Context *ctx) +{ + RequestButtonPacketType(ctx, GetButtonPacketType(ctx)); +} + +static void InitStickCalibrationData(SDL_DriverWii_Context *ctx) +{ + int i; + switch (ctx->m_eExtensionControllerType) { + case k_eWiiExtensionControllerType_WiiUPro: + for (i = 0; i < 4; i++) { + ctx->m_StickCalibrationData[i].min = 1000; + ctx->m_StickCalibrationData[i].max = 3000; + ctx->m_StickCalibrationData[i].center = 0; + ctx->m_StickCalibrationData[i].deadzone = 100; + } + break; + case k_eWiiExtensionControllerType_Gamepad: + for (i = 0; i < 4; i++) { + ctx->m_StickCalibrationData[i].min = i < 2 ? 9 : 5; + ctx->m_StickCalibrationData[i].max = i < 2 ? 54 : 26; + ctx->m_StickCalibrationData[i].center = 0; + ctx->m_StickCalibrationData[i].deadzone = i < 2 ? 4 : 2; + } + break; + case k_eWiiExtensionControllerType_Nunchuk: + for (i = 0; i < 2; i++) { + ctx->m_StickCalibrationData[i].min = 40; + ctx->m_StickCalibrationData[i].max = 215; + ctx->m_StickCalibrationData[i].center = 0; + ctx->m_StickCalibrationData[i].deadzone = 10; + } + break; + default: + break; + } +} + +static void InitializeExtension(SDL_DriverWii_Context *ctx) +{ + SendExtensionReset(ctx, true); + InitStickCalibrationData(ctx); + ResetButtonPacketType(ctx); +} + +static void UpdateSlotLED(SDL_DriverWii_Context *ctx) +{ + Uint8 leds; + Uint8 data[2]; + + // The lowest bit needs to have the rumble status + leds = (Uint8)ctx->m_bRumbleActive; + + if (ctx->m_bPlayerLights) { + // Use the same LED codes as Smash 8-player for 5-7 + if (ctx->m_nPlayerIndex == 0 || ctx->m_nPlayerIndex > 3) { + leds |= k_eWiiPlayerLEDs_P1; + } + if (ctx->m_nPlayerIndex == 1 || ctx->m_nPlayerIndex == 4) { + leds |= k_eWiiPlayerLEDs_P2; + } + if (ctx->m_nPlayerIndex == 2 || ctx->m_nPlayerIndex == 5) { + leds |= k_eWiiPlayerLEDs_P3; + } + if (ctx->m_nPlayerIndex == 3 || ctx->m_nPlayerIndex == 6) { + leds |= k_eWiiPlayerLEDs_P4; + } + // Turn on all lights for other player indexes + if (ctx->m_nPlayerIndex < 0 || ctx->m_nPlayerIndex > 6) { + leds |= k_eWiiPlayerLEDs_P1 | k_eWiiPlayerLEDs_P2 | k_eWiiPlayerLEDs_P3 | k_eWiiPlayerLEDs_P4; + } + } + + data[0] = k_eWiiOutputReportIDs_LEDs; + data[1] = leds; + WriteOutput(ctx, data, sizeof(data), false); +} + +static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)userdata; + bool bPlayerLights = SDL_GetStringBoolean(hint, true); + + if (bPlayerLights != ctx->m_bPlayerLights) { + ctx->m_bPlayerLights = bPlayerLights; + + UpdateSlotLED(ctx); + } +} + +static EWiiExtensionControllerType ReadExtensionControllerType(SDL_HIDAPI_Device *device) +{ + SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; + EWiiExtensionControllerType eExtensionControllerType = k_eWiiExtensionControllerType_Unknown; + const int MAX_ATTEMPTS = 20; + int attempts = 0; + + // Create enough of a context to read the controller type from the device + for (attempts = 0; attempts < MAX_ATTEMPTS; ++attempts) { + Uint16 extension; + if (SendExtensionIdentify(ctx, true) && + ParseExtensionIdentifyResponse(ctx, &extension)) { + Uint8 motion_plus_mode = 0; + if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) { + motion_plus_mode = (Uint8)(extension >> 8); + } + if (motion_plus_mode || extension == WII_EXTENSION_UNINITIALIZED) { + SendExtensionReset(ctx, true); + if (SendExtensionIdentify(ctx, true)) { + ParseExtensionIdentifyResponse(ctx, &extension); + } + } + + eExtensionControllerType = GetExtensionType(extension); + + // Reset the Motion Plus controller if needed + if (motion_plus_mode) { + ActivateMotionPlusWithMode(ctx, motion_plus_mode); + } + break; + } + } + return eExtensionControllerType; +} + +static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) +{ + SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; + + switch (ctx->m_eExtensionControllerType) { + case k_eWiiExtensionControllerType_None: + HIDAPI_SetDeviceName(device, "Nintendo Wii Remote"); + break; + case k_eWiiExtensionControllerType_Nunchuk: + HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Nunchuk"); + break; + case k_eWiiExtensionControllerType_Gamepad: + HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Classic Controller"); + break; + case k_eWiiExtensionControllerType_WiiUPro: + HIDAPI_SetDeviceName(device, "Nintendo Wii U Pro Controller"); + break; + default: + HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Unknown Extension"); + break; + } + device->guid.data[15] = ctx->m_eExtensionControllerType; +} + +static bool HIDAPI_DriverWii_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverWii_Context *ctx; + + ctx = (SDL_DriverWii_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + device->context = ctx; + + if (device->vendor_id == USB_VENDOR_NINTENDO) { + ctx->m_eExtensionControllerType = ReadExtensionControllerType(device); + + UpdateDeviceIdentity(device); + } + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverWii_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverWii_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; + + if (!ctx->joystick) { + return; + } + + ctx->m_nPlayerIndex = player_index; + + UpdateSlotLED(ctx); +} + +static bool HIDAPI_DriverWii_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + ctx->joystick = joystick; + + InitializeExtension(ctx); + + GetMotionPlusState(ctx, &ctx->m_bMotionPlusPresent, &ctx->m_ucMotionPlusMode); + + if (NeedsPeriodicMotionPlusCheck(ctx, false)) { + SchedulePeriodicMotionPlusCheck(ctx); + } + + if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_None || + ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f); + if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_L, 100.0f); + } + + if (ctx->m_bMotionPlusPresent) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 100.0f); + } + } + + // Initialize player index (needed for setting LEDs) + ctx->m_nPlayerIndex = SDL_GetJoystickPlayerIndex(joystick); + ctx->m_bPlayerLights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED, true); + UpdateSlotLED(ctx); + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + // Initialize the joystick capabilities + if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) { + joystick->nbuttons = 15; + } else { + // Maximum is Classic Controller + Wiimote + joystick->nbuttons = k_eWiiButtons_Max; + } + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + + ctx->m_ulLastInput = SDL_GetTicks(); + + return true; +} + +static bool HIDAPI_DriverWii_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; + bool active = (low_frequency_rumble || high_frequency_rumble); + + if (active != ctx->m_bRumbleActive) { + Uint8 data[2]; + + data[0] = k_eWiiOutputReportIDs_Rumble; + data[1] = (Uint8)active; + WriteOutput(ctx, data, sizeof(data), false); + + ctx->m_bRumbleActive = active; + } + return true; +} + +static bool HIDAPI_DriverWii_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverWii_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return SDL_JOYSTICK_CAP_RUMBLE; +} + +static bool HIDAPI_DriverWii_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverWii_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverWii_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; + + if (enabled != ctx->m_bReportSensors) { + ctx->m_bReportSensors = enabled; + + if (ctx->m_bMotionPlusPresent) { + if (enabled) { + ActivateMotionPlus(ctx); + } else { + DeactivateMotionPlus(ctx); + } + } + + ResetButtonPacketType(ctx); + } + return true; +} + +static void PostStickCalibrated(Uint64 timestamp, SDL_Joystick *joystick, StickCalibrationData *calibration, Uint8 axis, Uint16 data) +{ + Sint16 value = 0; + if (!calibration->center) { + // Center on first read + calibration->center = data; + return; + } + if (data < calibration->min) { + calibration->min = data; + } + if (data > calibration->max) { + calibration->max = data; + } + if (data < calibration->center - calibration->deadzone) { + Uint16 zero = calibration->center - calibration->deadzone; + Uint16 range = zero - calibration->min; + Uint16 distance = zero - data; + float fvalue = (float)distance / (float)range; + value = (Sint16)(fvalue * SDL_JOYSTICK_AXIS_MIN); + } else if (data > calibration->center + calibration->deadzone) { + Uint16 zero = calibration->center + calibration->deadzone; + Uint16 range = calibration->max - zero; + Uint16 distance = data - zero; + float fvalue = (float)distance / (float)range; + value = (Sint16)(fvalue * SDL_JOYSTICK_AXIS_MAX); + } + if (axis == SDL_GAMEPAD_AXIS_LEFTY || axis == SDL_GAMEPAD_AXIS_RIGHTY) { + if (value) { + value = ~value; + } + } + SDL_SendJoystickAxis(timestamp, joystick, axis, value); +} + +/* Send button data to SDL + *`defs` is a mapping for each bit to which button it represents. 0xFF indicates an unused bit + *`data` is the button data from the controller + *`size` is the number of bytes in `data` and the number of arrays of 8 mappings in `defs` + *`on` is the joystick value to be sent if a bit is on + *`off` is the joystick value to be sent if a bit is off + */ +static void PostPackedButtonData(Uint64 timestamp, SDL_Joystick *joystick, const Uint8 defs[][8], const Uint8 *data, int size, bool on, bool off) +{ + int i, j; + + for (i = 0; i < size; i++) { + for (j = 0; j < 8; j++) { + Uint8 button = defs[i][j]; + if (button != 0xFF) { + bool down = (data[i] >> j) & 1 ? on : off; + SDL_SendJoystickButton(timestamp, joystick, button, down); + } + } + } +} + +static const Uint8 GAMEPAD_BUTTON_DEFS[3][8] = { + { + 0xFF /* Unused */, + SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + SDL_GAMEPAD_BUTTON_START, + SDL_GAMEPAD_BUTTON_GUIDE, + SDL_GAMEPAD_BUTTON_BACK, + SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + SDL_GAMEPAD_BUTTON_DPAD_DOWN, + SDL_GAMEPAD_BUTTON_DPAD_RIGHT, + }, + { + SDL_GAMEPAD_BUTTON_DPAD_UP, + SDL_GAMEPAD_BUTTON_DPAD_LEFT, + 0xFF /* ZR */, + SDL_GAMEPAD_BUTTON_NORTH, + SDL_GAMEPAD_BUTTON_EAST, + SDL_GAMEPAD_BUTTON_WEST, + SDL_GAMEPAD_BUTTON_SOUTH, + 0xFF /*ZL*/, + }, + { + SDL_GAMEPAD_BUTTON_RIGHT_STICK, + SDL_GAMEPAD_BUTTON_LEFT_STICK, + 0xFF /* Charging */, + 0xFF /* Plugged In */, + 0xFF /* Unused */, + 0xFF /* Unused */, + 0xFF /* Unused */, + 0xFF /* Unused */, + } +}; + +static const Uint8 MP_GAMEPAD_BUTTON_DEFS[3][8] = { + { + 0xFF /* Unused */, + SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + SDL_GAMEPAD_BUTTON_START, + SDL_GAMEPAD_BUTTON_GUIDE, + SDL_GAMEPAD_BUTTON_BACK, + SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + SDL_GAMEPAD_BUTTON_DPAD_DOWN, + SDL_GAMEPAD_BUTTON_DPAD_RIGHT, + }, + { + 0xFF /* Motion Plus data */, + 0xFF /* Motion Plus data */, + 0xFF /* ZR */, + SDL_GAMEPAD_BUTTON_NORTH, + SDL_GAMEPAD_BUTTON_EAST, + SDL_GAMEPAD_BUTTON_WEST, + SDL_GAMEPAD_BUTTON_SOUTH, + 0xFF /*ZL*/, + }, + { + SDL_GAMEPAD_BUTTON_RIGHT_STICK, + SDL_GAMEPAD_BUTTON_LEFT_STICK, + 0xFF /* Charging */, + 0xFF /* Plugged In */, + 0xFF /* Unused */, + 0xFF /* Unused */, + 0xFF /* Unused */, + 0xFF /* Unused */, + } +}; + +static const Uint8 MP_FIXUP_DPAD_BUTTON_DEFS[2][8] = { + { + SDL_GAMEPAD_BUTTON_DPAD_UP, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + }, + { + SDL_GAMEPAD_BUTTON_DPAD_LEFT, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + } +}; + +static void HandleWiiUProButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) +{ + static const Uint8 axes[] = { SDL_GAMEPAD_AXIS_LEFTX, SDL_GAMEPAD_AXIS_RIGHTX, SDL_GAMEPAD_AXIS_LEFTY, SDL_GAMEPAD_AXIS_RIGHTY }; + const Uint8(*buttons)[8] = GAMEPAD_BUTTON_DEFS; + Uint8 zl, zr; + int i; + + if (data->ucNExtensionBytes < 11) { + return; + } + + // Buttons + PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucExtension + 8, 3, false, true); + + // Triggers + zl = data->rgucExtension[9] & 0x80; + zr = data->rgucExtension[9] & 0x04; + SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, zl ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, zr ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX); + + // Sticks + for (i = 0; i < 4; i++) { + Uint16 value = data->rgucExtension[i * 2] | (data->rgucExtension[i * 2 + 1] << 8); + PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[i], axes[i], value); + } + + // Power + UpdatePowerLevelWiiU(joystick, data->rgucExtension[10]); +} + +static void HandleGamepadControllerButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) +{ + const Uint8(*buttons)[8] = (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) ? MP_GAMEPAD_BUTTON_DEFS : GAMEPAD_BUTTON_DEFS; + Uint8 lx, ly, rx, ry, zl, zr; + + if (data->ucNExtensionBytes < 6) { + return; + } + + // Buttons + PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucExtension + 4, 2, false, true); + if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) { + PostPackedButtonData(ctx->timestamp, joystick, MP_FIXUP_DPAD_BUTTON_DEFS, data->rgucExtension, 2, false, true); + } + + // Triggers + zl = data->rgucExtension[5] & 0x80; + zr = data->rgucExtension[5] & 0x04; + SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, zl ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX); + SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, zr ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX); + + // Sticks + if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) { + lx = data->rgucExtension[0] & 0x3E; + ly = data->rgucExtension[1] & 0x3E; + } else { + lx = data->rgucExtension[0] & 0x3F; + ly = data->rgucExtension[1] & 0x3F; + } + rx = (data->rgucExtension[2] >> 7) | ((data->rgucExtension[1] >> 5) & 0x06) | ((data->rgucExtension[0] >> 3) & 0x18); + ry = data->rgucExtension[2] & 0x1F; + PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[0], SDL_GAMEPAD_AXIS_LEFTX, lx); + PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[1], SDL_GAMEPAD_AXIS_LEFTY, ly); + PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[2], SDL_GAMEPAD_AXIS_RIGHTX, rx); + PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[3], SDL_GAMEPAD_AXIS_RIGHTY, ry); +} + +static void HandleWiiRemoteButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) +{ + static const Uint8 buttons[2][8] = { + { + k_eWiiButtons_DPad_Left, + k_eWiiButtons_DPad_Right, + k_eWiiButtons_DPad_Down, + k_eWiiButtons_DPad_Up, + k_eWiiButtons_Plus, + 0xFF /* Unused */, + 0xFF /* Unused */, + 0xFF /* Unused */, + }, + { + k_eWiiButtons_Two, + k_eWiiButtons_One, + k_eWiiButtons_B, + k_eWiiButtons_A, + k_eWiiButtons_Minus, + 0xFF /* Unused */, + 0xFF /* Unused */, + k_eWiiButtons_Home, + } + }; + if (data->hasBaseButtons) { + PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucBaseButtons, 2, true, false); + } +} + +static void HandleWiiRemoteButtonDataAsMainController(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) +{ + /* Wii remote maps really badly to a normal controller + * Mapped 1 and 2 as X and Y + * Not going to attempt positional mapping + */ + static const Uint8 buttons[2][8] = { + { + SDL_GAMEPAD_BUTTON_DPAD_LEFT, + SDL_GAMEPAD_BUTTON_DPAD_RIGHT, + SDL_GAMEPAD_BUTTON_DPAD_DOWN, + SDL_GAMEPAD_BUTTON_DPAD_UP, + SDL_GAMEPAD_BUTTON_START, + 0xFF /* Unused */, + 0xFF /* Unused */, + 0xFF /* Unused */, + }, + { + SDL_GAMEPAD_BUTTON_NORTH, + SDL_GAMEPAD_BUTTON_WEST, + SDL_GAMEPAD_BUTTON_SOUTH, + SDL_GAMEPAD_BUTTON_EAST, + SDL_GAMEPAD_BUTTON_BACK, + 0xFF /* Unused */, + 0xFF /* Unused */, + SDL_GAMEPAD_BUTTON_GUIDE, + } + }; + if (data->hasBaseButtons) { + PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucBaseButtons, 2, true, false); + } +} + +static void HandleNunchuckButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) +{ + bool c_button, z_button; + + if (data->ucNExtensionBytes < 6) { + return; + } + + if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_NUNCHUK) { + c_button = (data->rgucExtension[5] & 0x08) ? false : true; + z_button = (data->rgucExtension[5] & 0x04) ? false : true; + } else { + c_button = (data->rgucExtension[5] & 0x02) ? false : true; + z_button = (data->rgucExtension[5] & 0x01) ? false : true; + } + SDL_SendJoystickButton(ctx->timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, c_button); + SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, z_button ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN); + PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[0], SDL_GAMEPAD_AXIS_LEFTX, data->rgucExtension[0]); + PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[1], SDL_GAMEPAD_AXIS_LEFTY, data->rgucExtension[1]); + + if (ctx->m_bReportSensors) { + const float ACCEL_RES_PER_G = 200.0f; + Sint16 x, y, z; + float values[3]; + + x = (data->rgucExtension[2] << 2); + y = (data->rgucExtension[3] << 2); + z = (data->rgucExtension[4] << 2); + + if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_NUNCHUK) { + x |= ((data->rgucExtension[5] >> 3) & 0x02); + y |= ((data->rgucExtension[5] >> 4) & 0x02); + z &= ~0x04; + z |= ((data->rgucExtension[5] >> 5) & 0x06); + } else { + x |= ((data->rgucExtension[5] >> 2) & 0x03); + y |= ((data->rgucExtension[5] >> 4) & 0x03); + z |= ((data->rgucExtension[5] >> 6) & 0x03); + } + + x -= 0x200; + y -= 0x200; + z -= 0x200; + + values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; + values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; + values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; + SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_ACCEL_L, ctx->timestamp, values, 3); + } +} + +static void HandleMotionPlusData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) +{ + if (ctx->m_bReportSensors) { + const float GYRO_RES_PER_DEGREE = 8192.0f; + int x, y, z; + float values[3]; + + x = (data->rgucExtension[0] | ((data->rgucExtension[3] << 6) & 0xFF00)) - 8192; + y = (data->rgucExtension[1] | ((data->rgucExtension[4] << 6) & 0xFF00)) - 8192; + z = (data->rgucExtension[2] | ((data->rgucExtension[5] << 6) & 0xFF00)) - 8192; + + if (data->rgucExtension[3] & 0x02) { + // Slow rotation rate: 8192/440 units per deg/s + x *= 440; + } else { + // Fast rotation rate: 8192/2000 units per deg/s + x *= 2000; + } + if (data->rgucExtension[4] & 0x02) { + // Slow rotation rate: 8192/440 units per deg/s + y *= 440; + } else { + // Fast rotation rate: 8192/2000 units per deg/s + y *= 2000; + } + if (data->rgucExtension[3] & 0x01) { + // Slow rotation rate: 8192/440 units per deg/s + z *= 440; + } else { + // Fast rotation rate: 8192/2000 units per deg/s + z *= 2000; + } + + values[0] = -((float)z / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f; + values[1] = ((float)x / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f; + values[2] = ((float)y / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f; + SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_GYRO, ctx->timestamp, values, 3); + } +} + +static void HandleWiiRemoteAccelData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) +{ + const float ACCEL_RES_PER_G = 100.0f; + Sint16 x, y, z; + float values[3]; + + if (!ctx->m_bReportSensors) { + return; + } + + x = ((data->rgucAccelerometer[0] << 2) | ((data->rgucBaseButtons[0] >> 5) & 0x03)) - 0x200; + y = ((data->rgucAccelerometer[1] << 2) | ((data->rgucBaseButtons[1] >> 4) & 0x02)) - 0x200; + z = ((data->rgucAccelerometer[2] << 2) | ((data->rgucBaseButtons[1] >> 5) & 0x02)) - 0x200; + + values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; + values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; + values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; + SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_ACCEL, ctx->timestamp, values, 3); +} + +static void HandleButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, WiiButtonData *data) +{ + if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) { + HandleWiiUProButtonData(ctx, joystick, data); + return; + } + + if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE && + data->ucNExtensionBytes > 5) { + if (data->rgucExtension[5] & 0x01) { + // The data is invalid, possibly during a hotplug + return; + } + + if (data->rgucExtension[4] & 0x01) { + if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_None) { + // Something was plugged into the extension port, reinitialize to get new state + ctx->m_bDisconnected = true; + } + } else { + if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_None) { + // Something was removed from the extension port, reinitialize to get new state + ctx->m_bDisconnected = true; + } + } + + if (data->rgucExtension[5] & 0x02) { + HandleMotionPlusData(ctx, joystick, data); + + // The extension data is consumed + data->ucNExtensionBytes = 0; + } + } + + HandleWiiRemoteButtonData(ctx, joystick, data); + switch (ctx->m_eExtensionControllerType) { + case k_eWiiExtensionControllerType_Nunchuk: + HandleNunchuckButtonData(ctx, joystick, data); + SDL_FALLTHROUGH; + case k_eWiiExtensionControllerType_None: + HandleWiiRemoteButtonDataAsMainController(ctx, joystick, data); + break; + case k_eWiiExtensionControllerType_Gamepad: + HandleGamepadControllerButtonData(ctx, joystick, data); + break; + default: + break; + } + HandleWiiRemoteAccelData(ctx, joystick, data); +} + +static void GetBaseButtons(WiiButtonData *dst, const Uint8 *src) +{ + SDL_memcpy(dst->rgucBaseButtons, src, 2); + dst->hasBaseButtons = true; +} + +static void GetAccelerometer(WiiButtonData *dst, const Uint8 *src) +{ + SDL_memcpy(dst->rgucAccelerometer, src, 3); + dst->hasAccelerometer = true; +} + +static void GetExtensionData(WiiButtonData *dst, const Uint8 *src, int size) +{ + bool valid_data = false; + int i; + + if (size > sizeof(dst->rgucExtension)) { + size = sizeof(dst->rgucExtension); + } + + for (i = 0; i < size; ++i) { + if (src[i] != 0xFF) { + valid_data = true; + break; + } + } + if (valid_data) { + SDL_memcpy(dst->rgucExtension, src, size); + dst->ucNExtensionBytes = (Uint8)size; + } +} + +static void HandleStatus(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick) +{ + bool hadExtension = ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_None; + bool hasExtension = (ctx->m_rgucReadBuffer[3] & 2) ? true : false; + WiiButtonData data; + SDL_zero(data); + GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); + HandleButtonData(ctx, joystick, &data); + + if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_WiiUPro) { + // Wii U has separate battery level tracking + UpdatePowerLevelWii(joystick, ctx->m_rgucReadBuffer[6]); + } + + // The report data format has been reset, need to update it + ResetButtonPacketType(ctx); + + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Status update, extension %s", hasExtension ? "CONNECTED" : "DISCONNECTED"); + + /* When Motion Plus is active, we get extension connect/disconnect status + * through the Motion Plus packets. Otherwise we can use the status here. + */ + if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE) { + /* Check to make sure the Motion Plus extension state hasn't changed, + * otherwise we'll get extension connect/disconnect status through + * Motion Plus packets. + */ + if (NeedsPeriodicMotionPlusCheck(ctx, true)) { + ctx->m_ulNextMotionPlusCheck = SDL_GetTicks(); + } + + } else if (hadExtension != hasExtension) { + // Reinitialize to get new state + ctx->m_bDisconnected = true; + } +} + +static void HandleResponse(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick) +{ + EWiiInputReportIDs type = (EWiiInputReportIDs)ctx->m_rgucReadBuffer[0]; + WiiButtonData data; + SDL_assert(type == k_eWiiInputReportIDs_Acknowledge || type == k_eWiiInputReportIDs_ReadMemory); + SDL_zero(data); + GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); + HandleButtonData(ctx, joystick, &data); + + switch (ctx->m_eCommState) { + case k_eWiiCommunicationState_None: + break; + + case k_eWiiCommunicationState_CheckMotionPlusStage1: + case k_eWiiCommunicationState_CheckMotionPlusStage2: + { + Uint16 extension = 0; + if (ParseExtensionIdentifyResponse(ctx, &extension)) { + if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) { + // Motion Plus is currently active + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Motion Plus CONNECTED (stage %d)", ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1 ? 1 : 2); + + if (!ctx->m_bMotionPlusPresent) { + // Reinitialize to get new sensor availability + ctx->m_bDisconnected = true; + } + ctx->m_eCommState = k_eWiiCommunicationState_None; + + } else if (ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1) { + // Check to see if Motion Plus is present + ReadRegister(ctx, 0xA600FE, 2, false); + + ctx->m_eCommState = k_eWiiCommunicationState_CheckMotionPlusStage2; + + } else { + // Motion Plus is not present + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Motion Plus DISCONNECTED (stage %d)", ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1 ? 1 : 2); + + if (ctx->m_bMotionPlusPresent) { + // Reinitialize to get new sensor availability + ctx->m_bDisconnected = true; + } + ctx->m_eCommState = k_eWiiCommunicationState_None; + } + } + } break; + default: + // Should never happen + break; + } +} + +static void HandleButtonPacket(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick) +{ + EWiiInputReportIDs eExpectedReport = GetButtonPacketType(ctx); + WiiButtonData data; + + // FIXME: This should see if the data format is compatible rather than equal + if (eExpectedReport != ctx->m_rgucReadBuffer[0]) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Resetting report mode to %d", eExpectedReport); + RequestButtonPacketType(ctx, eExpectedReport); + } + + // IR camera data is not supported + SDL_zero(data); + switch (ctx->m_rgucReadBuffer[0]) { + case k_eWiiInputReportIDs_ButtonData0: // 30 BB BB + GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); + break; + case k_eWiiInputReportIDs_ButtonData1: // 31 BB BB AA AA AA + case k_eWiiInputReportIDs_ButtonData3: // 33 BB BB AA AA AA II II II II II II II II II II II II + GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); + GetAccelerometer(&data, ctx->m_rgucReadBuffer + 3); + break; + case k_eWiiInputReportIDs_ButtonData2: // 32 BB BB EE EE EE EE EE EE EE EE + GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); + GetExtensionData(&data, ctx->m_rgucReadBuffer + 3, 8); + break; + case k_eWiiInputReportIDs_ButtonData4: // 34 BB BB EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE + GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); + GetExtensionData(&data, ctx->m_rgucReadBuffer + 3, 19); + break; + case k_eWiiInputReportIDs_ButtonData5: // 35 BB BB AA AA AA EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE + GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); + GetAccelerometer(&data, ctx->m_rgucReadBuffer + 3); + GetExtensionData(&data, ctx->m_rgucReadBuffer + 6, 16); + break; + case k_eWiiInputReportIDs_ButtonData6: // 36 BB BB II II II II II II II II II II EE EE EE EE EE EE EE EE EE + GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); + GetExtensionData(&data, ctx->m_rgucReadBuffer + 13, 9); + break; + case k_eWiiInputReportIDs_ButtonData7: // 37 BB BB AA AA AA II II II II II II II II II II EE EE EE EE EE EE + GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); + GetExtensionData(&data, ctx->m_rgucReadBuffer + 16, 6); + break; + case k_eWiiInputReportIDs_ButtonDataD: // 3d EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE + GetExtensionData(&data, ctx->m_rgucReadBuffer + 1, 21); + break; + case k_eWiiInputReportIDs_ButtonDataE: + case k_eWiiInputReportIDs_ButtonDataF: + default: + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Unsupported button data type %02x", ctx->m_rgucReadBuffer[0]); + return; + } + HandleButtonData(ctx, joystick, &data); +} + +static void HandleInput(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick) +{ + EWiiInputReportIDs type = (EWiiInputReportIDs)ctx->m_rgucReadBuffer[0]; + + // Set up for handling input + ctx->timestamp = SDL_GetTicksNS(); + + if (type == k_eWiiInputReportIDs_Status) { + HandleStatus(ctx, joystick); + } else if (type == k_eWiiInputReportIDs_Acknowledge || type == k_eWiiInputReportIDs_ReadMemory) { + HandleResponse(ctx, joystick); + } else if (type >= k_eWiiInputReportIDs_ButtonData0 && type <= k_eWiiInputReportIDs_ButtonDataF) { + HandleButtonPacket(ctx, joystick); + } else { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Unexpected input packet of type %x", type); + } +} + +static bool HIDAPI_DriverWii_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; + SDL_Joystick *joystick = NULL; + int size; + Uint64 now; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + now = SDL_GetTicks(); + + while ((size = ReadInput(ctx)) > 0) { + if (joystick) { + HandleInput(ctx, joystick); + } + ctx->m_ulLastInput = now; + } + + /* Check to see if we've lost connection to the controller. + * We have continuous reporting enabled, so this should be reliable now. + */ + { + SDL_COMPILE_TIME_ASSERT(ENABLE_CONTINUOUS_REPORTING, ENABLE_CONTINUOUS_REPORTING); + } + if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) { + // Bluetooth may have disconnected, try reopening the controller + size = -1; + } + + if (joystick) { + // These checks aren't needed on the Wii U Pro Controller + if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_WiiUPro) { + + // Check to see if the Motion Plus extension status has changed + if (ctx->m_ulNextMotionPlusCheck && now >= ctx->m_ulNextMotionPlusCheck) { + CheckMotionPlusConnection(ctx); + if (NeedsPeriodicMotionPlusCheck(ctx, false)) { + SchedulePeriodicMotionPlusCheck(ctx); + } else { + ctx->m_ulNextMotionPlusCheck = 0; + } + } + + // Request a status update periodically to make sure our battery value is up to date + if (!ctx->m_ulLastStatus || now >= (ctx->m_ulLastStatus + STATUS_UPDATE_TIME_MS)) { + Uint8 data[2]; + + data[0] = k_eWiiOutputReportIDs_StatusRequest; + data[1] = (Uint8)ctx->m_bRumbleActive; + WriteOutput(ctx, data, sizeof(data), false); + + ctx->m_ulLastStatus = now; + } + } + } + + if (size < 0 || ctx->m_bDisconnected) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverWii_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + ctx->joystick = NULL; +} + +static void HIDAPI_DriverWii_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii = { + SDL_HINT_JOYSTICK_HIDAPI_WII, + true, + HIDAPI_DriverWii_RegisterHints, + HIDAPI_DriverWii_UnregisterHints, + HIDAPI_DriverWii_IsEnabled, + HIDAPI_DriverWii_IsSupportedDevice, + HIDAPI_DriverWii_InitDevice, + HIDAPI_DriverWii_GetDevicePlayerIndex, + HIDAPI_DriverWii_SetDevicePlayerIndex, + HIDAPI_DriverWii_UpdateDevice, + HIDAPI_DriverWii_OpenJoystick, + HIDAPI_DriverWii_RumbleJoystick, + HIDAPI_DriverWii_RumbleJoystickTriggers, + HIDAPI_DriverWii_GetJoystickCapabilities, + HIDAPI_DriverWii_SetJoystickLED, + HIDAPI_DriverWii_SendJoystickEffect, + HIDAPI_DriverWii_SetJoystickSensorsEnabled, + HIDAPI_DriverWii_CloseJoystick, + HIDAPI_DriverWii_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_WII + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360.c new file mode 100644 index 0000000..49be08a --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360.c @@ -0,0 +1,379 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_XBOX360 + +// Define this if you want to log all packets from the controller +// #define DEBUG_XBOX_PROTOCOL + +typedef struct +{ + SDL_HIDAPI_Device *device; + SDL_Joystick *joystick; + int player_index; + bool player_lights; + Uint8 last_state[USB_PACKET_LENGTH]; +} SDL_DriverXbox360_Context; + +static void HIDAPI_DriverXbox360_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata); + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata); +} + +static void HIDAPI_DriverXbox360_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata); + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata); +} + +static bool HIDAPI_DriverXbox360_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT))); +} + +static bool HIDAPI_DriverXbox360_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + const int XB360W_IFACE_PROTOCOL = 129; // Wireless + + if (vendor_id == USB_VENDOR_ASTRO && product_id == USB_PRODUCT_ASTRO_C40_XBOX360) { + // This is the ASTRO C40 in Xbox 360 mode + return true; + } + if (vendor_id == USB_VENDOR_NVIDIA) { + // This is the NVIDIA Shield controller which doesn't talk Xbox controller protocol + return false; + } + if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER)) || + (type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) { + // This is the wireless dongle, which talks a different protocol + return false; + } + if (interface_number > 0) { + // This is the chatpad or other input interface, not the Xbox 360 interface + return false; + } +#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI) + if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) { + // GCController support doesn't work with the Steam Virtual Gamepad + return true; + } else { + // On macOS you can't write output reports to wired XBox controllers, + // so we'll just use the GCController support instead. + return false; + } +#else + return (type == SDL_GAMEPAD_TYPE_XBOX360); +#endif +} + +static bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, bool on) +{ + const bool blink = false; + Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0; + Uint8 led_packet[] = { 0x01, 0x03, 0x00 }; + + led_packet[2] = mode; + if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) { + return false; + } + return true; +} + +static void UpdateSlotLED(SDL_DriverXbox360_Context *ctx) +{ + if (ctx->player_lights && ctx->player_index >= 0) { + SetSlotLED(ctx->device->dev, (ctx->player_index % 4), true); + } else { + SetSlotLED(ctx->device->dev, 0, false); + } +} + +static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)userdata; + bool player_lights = SDL_GetStringBoolean(hint, true); + + if (player_lights != ctx->player_lights) { + ctx->player_lights = player_lights; + + UpdateSlotLED(ctx); + HIDAPI_UpdateDeviceProperties(ctx->device); + } +} + +static bool HIDAPI_DriverXbox360_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverXbox360_Context *ctx; + + ctx = (SDL_DriverXbox360_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + + device->context = ctx; + + device->type = SDL_GAMEPAD_TYPE_XBOX360; + + if (SDL_IsJoystickSteamVirtualGamepad(device->vendor_id, device->product_id, device->version) && + device->product_string && SDL_strncmp(device->product_string, "GamePad-", 8) == 0) { + int slot = 0; + SDL_sscanf(device->product_string, "GamePad-%d", &slot); + device->steam_virtual_gamepad_slot = (slot - 1); + } + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverXbox360_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverXbox360_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; + + if (!ctx->joystick) { + return; + } + + ctx->player_index = player_index; + + UpdateSlotLED(ctx); +} + +static bool HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + ctx->joystick = joystick; + SDL_zeroa(ctx->last_state); + + // Initialize player index (needed for setting LEDs) + ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); + ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, true); + UpdateSlotLED(ctx); + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + // Initialize the joystick capabilities + joystick->nbuttons = 11; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + return true; +} + +static bool HIDAPI_DriverXbox360_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + Uint8 rumble_packet[] = { 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + rumble_packet[3] = (low_frequency_rumble >> 8); + rumble_packet[4] = (high_frequency_rumble >> 8); + + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static bool HIDAPI_DriverXbox360_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverXbox360_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; + Uint32 result = SDL_JOYSTICK_CAP_RUMBLE; + + if (ctx->player_lights) { + result |= SDL_JOYSTICK_CAP_PLAYER_LED; + } + return result; +} + +static bool HIDAPI_DriverXbox360_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverXbox360_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverXbox360_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; +#ifdef SDL_PLATFORM_MACOS + const bool invert_y_axes = false; +#else + const bool invert_y_axes = true; +#endif + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[2] != data[2]) { + Uint8 hat = 0; + + if (data[2] & 0x01) { + hat |= SDL_HAT_UP; + } + if (data[2] & 0x02) { + hat |= SDL_HAT_DOWN; + } + if (data[2] & 0x04) { + hat |= SDL_HAT_LEFT; + } + if (data[2] & 0x08) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0)); + } + + if (ctx->last_state[3] != data[3]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[3] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x80) != 0)); + } + + axis = ((int)data[4] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = ((int)data[5] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + axis = SDL_Swap16LE(*(Sint16 *)(&data[6])); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = SDL_Swap16LE(*(Sint16 *)(&data[8])); + if (invert_y_axes) { + axis = ~axis; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = SDL_Swap16LE(*(Sint16 *)(&data[10])); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = SDL_Swap16LE(*(Sint16 *)(&data[12])); + if (invert_y_axes) { + axis = ~axis; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + + SDL_memcpy(ctx->last_state, data, SDL_min((size_t)size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverXbox360_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("Xbox 360 packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + if (data[0] == 0x00) { + HIDAPI_DriverXbox360_HandleStatePacket(joystick, ctx, data, size); + } + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverXbox360_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + ctx->joystick = NULL; +} + +static void HIDAPI_DriverXbox360_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 = { + SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, + true, + HIDAPI_DriverXbox360_RegisterHints, + HIDAPI_DriverXbox360_UnregisterHints, + HIDAPI_DriverXbox360_IsEnabled, + HIDAPI_DriverXbox360_IsSupportedDevice, + HIDAPI_DriverXbox360_InitDevice, + HIDAPI_DriverXbox360_GetDevicePlayerIndex, + HIDAPI_DriverXbox360_SetDevicePlayerIndex, + HIDAPI_DriverXbox360_UpdateDevice, + HIDAPI_DriverXbox360_OpenJoystick, + HIDAPI_DriverXbox360_RumbleJoystick, + HIDAPI_DriverXbox360_RumbleJoystickTriggers, + HIDAPI_DriverXbox360_GetJoystickCapabilities, + HIDAPI_DriverXbox360_SetJoystickLED, + HIDAPI_DriverXbox360_SendJoystickEffect, + HIDAPI_DriverXbox360_SetJoystickSensorsEnabled, + HIDAPI_DriverXbox360_CloseJoystick, + HIDAPI_DriverXbox360_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_XBOX360 + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360w.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360w.c new file mode 100644 index 0000000..bf63707 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360w.c @@ -0,0 +1,388 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_XBOX360 + +// Define this if you want to log all packets from the controller +// #define DEBUG_XBOX_PROTOCOL + +typedef struct +{ + SDL_HIDAPI_Device *device; + bool connected; + int player_index; + bool player_lights; + Uint8 last_state[USB_PACKET_LENGTH]; +} SDL_DriverXbox360W_Context; + +static void HIDAPI_DriverXbox360W_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata); + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata); + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, callback, userdata); +} + +static void HIDAPI_DriverXbox360W_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata); + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata); + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, callback, userdata); +} + +static bool HIDAPI_DriverXbox360W_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)))); +} + +static bool HIDAPI_DriverXbox360W_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + const int XB360W_IFACE_PROTOCOL = 129; // Wireless + + if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY1 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER) && interface_protocol == 0) || + (type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) { + return true; + } + return false; +} + +static bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, bool on) +{ + const bool blink = false; + Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0; + Uint8 led_packet[] = { 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + led_packet[3] = 0x40 + (mode % 0x0e); + if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) { + return false; + } + return true; +} + +static void UpdateSlotLED(SDL_DriverXbox360W_Context *ctx) +{ + if (ctx->player_lights && ctx->player_index >= 0) { + SetSlotLED(ctx->device->dev, (ctx->player_index % 4), true); + } else { + SetSlotLED(ctx->device->dev, 0, false); + } +} + +static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)userdata; + bool player_lights = SDL_GetStringBoolean(hint, true); + + if (player_lights != ctx->player_lights) { + ctx->player_lights = player_lights; + + UpdateSlotLED(ctx); + HIDAPI_UpdateDeviceProperties(ctx->device); + } +} + +static void UpdatePowerLevel(SDL_Joystick *joystick, Uint8 level) +{ + int percent = (int)SDL_roundf((level / 255.0f) * 100.0f); + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent); +} + +static bool HIDAPI_DriverXbox360W_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverXbox360W_Context *ctx; + + // Requests controller presence information from the wireless dongle + const Uint8 init_packet[] = { 0x08, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + HIDAPI_SetDeviceName(device, "Xbox 360 Wireless Controller"); + + ctx = (SDL_DriverXbox360W_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + + device->context = ctx; + + if (SDL_hid_write(device->dev, init_packet, sizeof(init_packet)) != sizeof(init_packet)) { + SDL_SetError("Couldn't write init packet"); + return false; + } + + device->type = SDL_GAMEPAD_TYPE_XBOX360; + + return true; +} + +static int HIDAPI_DriverXbox360W_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverXbox360W_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context; + + if (!ctx) { + return; + } + + ctx->player_index = player_index; + + UpdateSlotLED(ctx); +} + +static bool HIDAPI_DriverXbox360W_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + SDL_zeroa(ctx->last_state); + + // Initialize player index (needed for setting LEDs) + ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); + ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, true); + UpdateSlotLED(ctx); + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + // Initialize the joystick capabilities + joystick->nbuttons = 11; + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + + return true; +} + +static bool HIDAPI_DriverXbox360W_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + Uint8 rumble_packet[] = { 0x00, 0x01, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + rumble_packet[5] = (low_frequency_rumble >> 8); + rumble_packet[6] = (high_frequency_rumble >> 8); + + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static bool HIDAPI_DriverXbox360W_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverXbox360W_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context; + Uint32 result = SDL_JOYSTICK_CAP_RUMBLE; + + if (ctx->player_lights) { + result |= SDL_JOYSTICK_CAP_PLAYER_LED; + } + return result; +} + +static bool HIDAPI_DriverXbox360W_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverXbox360W_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void HIDAPI_DriverXbox360W_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverXbox360W_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + const bool invert_y_axes = true; + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[2] != data[2]) { + Uint8 hat = 0; + + if (data[2] & 0x01) { + hat |= SDL_HAT_UP; + } + if (data[2] & 0x02) { + hat |= SDL_HAT_DOWN; + } + if (data[2] & 0x04) { + hat |= SDL_HAT_LEFT; + } + if (data[2] & 0x08) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0)); + } + + if (ctx->last_state[3] != data[3]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[3] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x80) != 0)); + } + + axis = ((int)data[4] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = ((int)data[5] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + axis = SDL_Swap16LE(*(Sint16 *)(&data[6])); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = SDL_Swap16LE(*(Sint16 *)(&data[8])); + if (invert_y_axes) { + axis = ~axis; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = SDL_Swap16LE(*(Sint16 *)(&data[10])); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = SDL_Swap16LE(*(Sint16 *)(&data[12])); + if (invert_y_axes) { + axis = ~axis; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("Xbox 360 wireless packet: size = %d", data, size); +#endif + if (size == 2 && data[0] == 0x08) { + bool connected = (data[1] & 0x80) ? true : false; +#ifdef DEBUG_JOYSTICK + SDL_Log("Connected = %s", connected ? "TRUE" : "FALSE"); +#endif + if (connected != ctx->connected) { + ctx->connected = connected; + + if (connected) { + SDL_JoystickID joystickID; + + HIDAPI_JoystickConnected(device, &joystickID); + + } else if (device->num_joysticks > 0) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + } + } else if (size == 29 && data[0] == 0x00 && data[1] == 0x0f && data[2] == 0x00 && data[3] == 0xf0) { + // Serial number is data[7-13] +#ifdef DEBUG_JOYSTICK + SDL_Log("Battery status (initial): %d", data[17]); +#endif + if (joystick) { + UpdatePowerLevel(joystick, data[17]); + } + } else if (size == 29 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x13) { +#ifdef DEBUG_JOYSTICK + SDL_Log("Battery status: %d", data[4]); +#endif + if (joystick) { + UpdatePowerLevel(joystick, data[4]); + } + } else if (size == 29 && data[0] == 0x00 && (data[1] & 0x01) == 0x01) { + if (joystick) { + HIDAPI_DriverXbox360W_HandleStatePacket(joystick, device->dev, ctx, data + 4, size - 4); + } + } + } + + if (size < 0 && device->num_joysticks > 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverXbox360W_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context; + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); +} + +static void HIDAPI_DriverXbox360W_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W = { + SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, + true, + HIDAPI_DriverXbox360W_RegisterHints, + HIDAPI_DriverXbox360W_UnregisterHints, + HIDAPI_DriverXbox360W_IsEnabled, + HIDAPI_DriverXbox360W_IsSupportedDevice, + HIDAPI_DriverXbox360W_InitDevice, + HIDAPI_DriverXbox360W_GetDevicePlayerIndex, + HIDAPI_DriverXbox360W_SetDevicePlayerIndex, + HIDAPI_DriverXbox360W_UpdateDevice, + HIDAPI_DriverXbox360W_OpenJoystick, + HIDAPI_DriverXbox360W_RumbleJoystick, + HIDAPI_DriverXbox360W_RumbleJoystickTriggers, + HIDAPI_DriverXbox360W_GetJoystickCapabilities, + HIDAPI_DriverXbox360W_SetJoystickLED, + HIDAPI_DriverXbox360W_SendJoystickEffect, + HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled, + HIDAPI_DriverXbox360W_CloseJoystick, + HIDAPI_DriverXbox360W_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_XBOX360 + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xboxone.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xboxone.c new file mode 100644 index 0000000..342eabd --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xboxone.c @@ -0,0 +1,1675 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE + +// Define this if you want verbose logging of the init sequence +// #define DEBUG_JOYSTICK + +// Define this if you want to log all packets from the controller +// #define DEBUG_XBOX_PROTOCOL + +#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) +#define XBOX_ONE_DRIVER_ACTIVE 1 +#else +#define XBOX_ONE_DRIVER_ACTIVE 0 +#endif + +#define CONTROLLER_IDENTIFY_TIMEOUT_MS 100 +#define CONTROLLER_PREPARE_INPUT_TIMEOUT_MS 50 + +// Deadzone thresholds +#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849 +#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689 +#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD -25058 // Uint8 30 scaled to Sint16 full range + +enum +{ + SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON = 11 +}; + +// Power on +static const Uint8 xbox_init_power_on[] = { + 0x05, 0x20, 0x00, 0x01, 0x00 +}; +// Enable LED +static const Uint8 xbox_init_enable_led[] = { + 0x0A, 0x20, 0x00, 0x03, 0x00, 0x01, 0x14 +}; +// This controller passed security check +static const Uint8 xbox_init_security_passed[] = { + 0x06, 0x20, 0x00, 0x02, 0x01, 0x00 +}; +// Some PowerA controllers need to actually start the rumble motors +static const Uint8 xbox_init_powera_rumble[] = { + 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, + 0x1D, 0x1D, 0xFF, 0x00, 0x00 +}; +// Setup rumble (not needed for Microsoft controllers, but it doesn't hurt) +static const Uint8 xbox_init_rumble[] = { + 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xEB +}; + +/* + * This specifies the selection of init packets that a gamepad + * will be sent on init *and* the order in which they will be + * sent. The correct sequence number will be added when the + * packet is going to be sent. + */ +typedef struct +{ + Uint16 vendor_id; + Uint16 product_id; + const Uint8 *data; + int size; +} SDL_DriverXboxOne_InitPacket; + +static const SDL_DriverXboxOne_InitPacket xboxone_init_packets[] = { + { 0x0000, 0x0000, xbox_init_power_on, sizeof(xbox_init_power_on) }, + { 0x0000, 0x0000, xbox_init_enable_led, sizeof(xbox_init_enable_led) }, + { 0x0000, 0x0000, xbox_init_security_passed, sizeof(xbox_init_security_passed) }, + { 0x24c6, 0x541a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) }, + { 0x24c6, 0x542a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) }, + { 0x24c6, 0x543a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) }, + { 0x0000, 0x0000, xbox_init_rumble, sizeof(xbox_init_rumble) }, +}; + +typedef enum +{ + XBOX_ONE_INIT_STATE_ANNOUNCED, + XBOX_ONE_INIT_STATE_IDENTIFYING, + XBOX_ONE_INIT_STATE_STARTUP, + XBOX_ONE_INIT_STATE_PREPARE_INPUT, + XBOX_ONE_INIT_STATE_COMPLETE, +} SDL_XboxOneInitState; + +typedef enum +{ + XBOX_ONE_RUMBLE_STATE_IDLE, + XBOX_ONE_RUMBLE_STATE_QUEUED, + XBOX_ONE_RUMBLE_STATE_BUSY +} SDL_XboxOneRumbleState; + +typedef struct +{ + SDL_HIDAPI_Device *device; + Uint16 vendor_id; + Uint16 product_id; + SDL_XboxOneInitState init_state; + Uint64 start_time; + Uint8 sequence; + Uint64 send_time; + bool has_guide_packet; + bool has_color_led; + bool has_paddles; + bool has_unmapped_state; + bool has_trigger_rumble; + bool has_share_button; + Uint8 last_paddle_state; + Uint8 low_frequency_rumble; + Uint8 high_frequency_rumble; + Uint8 left_trigger_rumble; + Uint8 right_trigger_rumble; + SDL_XboxOneRumbleState rumble_state; + Uint64 rumble_time; + bool rumble_pending; + Uint8 last_state[USB_PACKET_LENGTH]; + Uint8 *chunk_buffer; + Uint32 chunk_length; +} SDL_DriverXboxOne_Context; + +static bool ControllerHasColorLED(Uint16 vendor_id, Uint16 product_id) +{ + return vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2; +} + +static bool ControllerHasPaddles(Uint16 vendor_id, Uint16 product_id) +{ + return SDL_IsJoystickXboxOneElite(vendor_id, product_id); +} + +static bool ControllerHasTriggerRumble(Uint16 vendor_id, Uint16 product_id) +{ + // All the Microsoft Xbox One controllers have trigger rumble + if (vendor_id == USB_VENDOR_MICROSOFT) { + return true; + } + + /* It turns out other controllers a mixed bag as to whether they support + trigger rumble or not, and when they do it's often a buzz rather than + the vibration of the Microsoft trigger rumble, so for now just pretend + that it is not available. + */ + return false; +} + +static bool ControllerHasShareButton(Uint16 vendor_id, Uint16 product_id) +{ + return SDL_IsJoystickXboxSeriesX(vendor_id, product_id); +} + +static int GetHomeLEDBrightness(const char *hint) +{ + const int MAX_VALUE = 50; + int value = 20; + + if (hint && *hint) { + if (SDL_strchr(hint, '.') != NULL) { + value = (int)(MAX_VALUE * SDL_atof(hint)); + } else if (!SDL_GetStringBoolean(hint, true)) { + value = 0; + } + } + return value; +} + +static void SetHomeLED(SDL_DriverXboxOne_Context *ctx, int value) +{ + Uint8 led_packet[] = { 0x0A, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00 }; + + if (value > 0) { + led_packet[5] = 0x01; + led_packet[6] = (Uint8)value; + } + SDL_HIDAPI_SendRumble(ctx->device, led_packet, sizeof(led_packet)); +} + +static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)userdata; + + if (hint && *hint) { + SetHomeLED(ctx, GetHomeLEDBrightness(hint)); + } +} + +static void SetInitState(SDL_DriverXboxOne_Context *ctx, SDL_XboxOneInitState state) +{ +#ifdef DEBUG_JOYSTICK + SDL_Log("Setting init state %d", state); +#endif + ctx->init_state = state; +} + +static Uint8 GetNextPacketSequence(SDL_DriverXboxOne_Context *ctx) +{ + ++ctx->sequence; + if (!ctx->sequence) { + ctx->sequence = 1; + } + return ctx->sequence; +} + +static bool SendProtocolPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) +{ +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("Xbox One sending packet: size = %d", data, size); +#endif + + ctx->send_time = SDL_GetTicks(); + + if (!SDL_HIDAPI_LockRumble()) { + return false; + } + if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size) != size) { + return false; + } + return true; +} + +#if 0 +static bool SendSerialRequest(SDL_DriverXboxOne_Context *ctx) +{ + Uint8 packet[] = { 0x1E, 0x20, 0x00, 0x01, 0x04 }; + + packet[2] = GetNextPacketSequence(ctx); + + /* Request the serial number + * Sending this should be done only after startup is complete. + * It will cancel the announce packet if sent before that, and will be + * ignored if sent during the startup sequence. + */ + if (!SendProtocolPacket(ctx, packet, sizeof(packet))) { + SDL_SetError("Couldn't send serial request packet"); + return false; + } + return true; +} +#endif + +static bool ControllerSendsAnnouncement(Uint16 vendor_id, Uint16 product_id) +{ + if (vendor_id == USB_VENDOR_PDP && product_id == 0x0246) { + // The PDP Rock Candy (PID 0x0246) doesn't send the announce packet on Linux for some reason + return false; + } + return true; +} + +static bool SendIdentificationRequest(SDL_DriverXboxOne_Context *ctx) +{ + // Request identification, sent in response to announce packet + Uint8 packet[] = { + 0x04, 0x20, 0x00, 0x00 + }; + + packet[2] = GetNextPacketSequence(ctx); + + if (!SendProtocolPacket(ctx, packet, sizeof(packet))) { + SDL_SetError("Couldn't send identification request packet"); + return false; + } + return true; +} + +static bool SendControllerStartup(SDL_DriverXboxOne_Context *ctx) +{ + Uint16 vendor_id = ctx->vendor_id; + Uint16 product_id = ctx->product_id; + Uint8 init_packet[USB_PACKET_LENGTH]; + size_t i; + + for (i = 0; i < SDL_arraysize(xboxone_init_packets); ++i) { + const SDL_DriverXboxOne_InitPacket *packet = &xboxone_init_packets[i]; + + if (packet->vendor_id && (vendor_id != packet->vendor_id)) { + continue; + } + + if (packet->product_id && (product_id != packet->product_id)) { + continue; + } + + SDL_memcpy(init_packet, packet->data, packet->size); + init_packet[2] = GetNextPacketSequence(ctx); + + if (init_packet[0] == 0x0A) { + // Get the initial brightness value + int brightness = GetHomeLEDBrightness(SDL_GetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED)); + init_packet[5] = (brightness > 0) ? 0x01 : 0x00; + init_packet[6] = (Uint8)brightness; + } + + if (!SendProtocolPacket(ctx, init_packet, packet->size)) { + SDL_SetError("Couldn't send initialization packet"); + return false; + } + + // Wait to process the rumble packet + if (packet->data == xbox_init_powera_rumble) { + SDL_Delay(10); + } + } + return true; +} + +static void HIDAPI_DriverXboxOne_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata); + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, callback, userdata); +} + +static void HIDAPI_DriverXboxOne_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata); + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, callback, userdata); +} + +static bool HIDAPI_DriverXboxOne_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT))); +} + +static bool HIDAPI_DriverXboxOne_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ +#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI) + if (!SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) { + // On macOS we get a shortened version of the real report and + // you can't write output reports for wired controllers, so + // we'll just use the GCController support instead. + return false; + } +#endif + return (type == SDL_GAMEPAD_TYPE_XBOXONE); +} + +static bool HIDAPI_DriverXboxOne_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverXboxOne_Context *ctx; + + ctx = (SDL_DriverXboxOne_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + + device->context = ctx; + + ctx->vendor_id = device->vendor_id; + ctx->product_id = device->product_id; + ctx->start_time = SDL_GetTicks(); + ctx->sequence = 0; + ctx->has_color_led = ControllerHasColorLED(ctx->vendor_id, ctx->product_id); + ctx->has_paddles = ControllerHasPaddles(ctx->vendor_id, ctx->product_id); + ctx->has_trigger_rumble = ControllerHasTriggerRumble(ctx->vendor_id, ctx->product_id); + ctx->has_share_button = ControllerHasShareButton(ctx->vendor_id, ctx->product_id); + + // Assume that the controller is correctly initialized when we start + if (!ControllerSendsAnnouncement(device->vendor_id, device->product_id)) { + // Jump into the startup sequence for this controller + ctx->init_state = XBOX_ONE_INIT_STATE_STARTUP; + } else { + ctx->init_state = XBOX_ONE_INIT_STATE_COMPLETE; + } + +#ifdef DEBUG_JOYSTICK + SDL_Log("Controller version: %d (0x%.4x)", device->version, device->version); +#endif + + device->type = SDL_GAMEPAD_TYPE_XBOXONE; + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverXboxOne_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverXboxOne_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static bool HIDAPI_DriverXboxOne_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + ctx->low_frequency_rumble = 0; + ctx->high_frequency_rumble = 0; + ctx->left_trigger_rumble = 0; + ctx->right_trigger_rumble = 0; + ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_IDLE; + ctx->rumble_time = 0; + ctx->rumble_pending = false; + SDL_zeroa(ctx->last_state); + + // Initialize the joystick capabilities + joystick->nbuttons = 11; + if (ctx->has_share_button) { + joystick->nbuttons += 1; + } + if (ctx->has_paddles) { + joystick->nbuttons += 4; + } + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED, + SDL_HomeLEDHintChanged, ctx); + return true; +} + +static void HIDAPI_DriverXboxOne_RumbleSent(void *userdata) +{ + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)userdata; + ctx->rumble_time = SDL_GetTicks(); +} + +static bool HIDAPI_DriverXboxOne_UpdateRumble(SDL_DriverXboxOne_Context *ctx) +{ + if (ctx->rumble_state == XBOX_ONE_RUMBLE_STATE_QUEUED) { + if (ctx->rumble_time) { + ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_BUSY; + } + } + + if (ctx->rumble_state == XBOX_ONE_RUMBLE_STATE_BUSY) { + const int RUMBLE_BUSY_TIME_MS = ctx->device->is_bluetooth ? 50 : 10; + if (SDL_GetTicks() >= (ctx->rumble_time + RUMBLE_BUSY_TIME_MS)) { + ctx->rumble_time = 0; + ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_IDLE; + } + } + + if (!ctx->rumble_pending) { + return true; + } + + if (ctx->rumble_state != XBOX_ONE_RUMBLE_STATE_IDLE) { + return true; + } + + // We're no longer pending, even if we fail to send the rumble below + ctx->rumble_pending = false; + + if (!SDL_HIDAPI_LockRumble()) { + return false; + } + + if (ctx->device->is_bluetooth) { + Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB }; + + rumble_packet[2] = ctx->left_trigger_rumble; + rumble_packet[3] = ctx->right_trigger_rumble; + rumble_packet[4] = ctx->low_frequency_rumble; + rumble_packet[5] = ctx->high_frequency_rumble; + + if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(ctx->device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + } else { + Uint8 rumble_packet[] = { 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB }; + + rumble_packet[6] = ctx->left_trigger_rumble; + rumble_packet[7] = ctx->right_trigger_rumble; + rumble_packet[8] = ctx->low_frequency_rumble; + rumble_packet[9] = ctx->high_frequency_rumble; + + if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(ctx->device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + } + + ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_QUEUED; + + return true; +} + +static bool HIDAPI_DriverXboxOne_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; + + // Magnitude is 1..100 so scale the 16-bit input here + ctx->low_frequency_rumble = (Uint8)(low_frequency_rumble / 655); + ctx->high_frequency_rumble = (Uint8)(high_frequency_rumble / 655); + ctx->rumble_pending = true; + + return HIDAPI_DriverXboxOne_UpdateRumble(ctx); +} + +static bool HIDAPI_DriverXboxOne_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; + + if (!ctx->has_trigger_rumble) { + return SDL_Unsupported(); + } + + // Magnitude is 1..100 so scale the 16-bit input here + ctx->left_trigger_rumble = (Uint8)(left_rumble / 655); + ctx->right_trigger_rumble = (Uint8)(right_rumble / 655); + ctx->rumble_pending = true; + + return HIDAPI_DriverXboxOne_UpdateRumble(ctx); +} + +static Uint32 HIDAPI_DriverXboxOne_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; + Uint32 result = 0; + + result |= SDL_JOYSTICK_CAP_RUMBLE; + if (ctx->has_trigger_rumble) { + result |= SDL_JOYSTICK_CAP_TRIGGER_RUMBLE; + } + + if (ctx->has_color_led) { + result |= SDL_JOYSTICK_CAP_RGB_LED; + } + + return result; +} + +static bool HIDAPI_DriverXboxOne_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; + + if (ctx->has_color_led) { + Uint8 led_packet[] = { 0x0E, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + led_packet[5] = 0x00; // Whiteness? Sets white intensity when RGB is 0, seems additive + led_packet[6] = red; + led_packet[7] = green; + led_packet[8] = blue; + + if (SDL_HIDAPI_SendRumble(device, led_packet, sizeof(led_packet)) != sizeof(led_packet)) { + return SDL_SetError("Couldn't send LED packet"); + } + return true; + } else { + return SDL_Unsupported(); + } +} + +static bool HIDAPI_DriverXboxOne_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +/* + * The Xbox One Elite controller with 5.13+ firmware sends the unmapped state in a separate packet. + * We can use this to send the paddle state when they aren't mapped + */ +static void HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +{ + Uint8 profile; + int paddle_index; + int button1_bit; + int button2_bit; + int button3_bit; + int button4_bit; + bool paddles_mapped; + Uint64 timestamp = SDL_GetTicksNS(); + + if (size == 17) { + // XBox One Elite Series 2 + paddle_index = 14; + button1_bit = 0x01; + button2_bit = 0x02; + button3_bit = 0x04; + button4_bit = 0x08; + profile = data[15]; + + if (profile == 0) { + paddles_mapped = false; + } else if (SDL_memcmp(&data[0], &ctx->last_state[0], 14) == 0) { + // We're using a profile, but paddles aren't mapped + paddles_mapped = false; + } else { + // Something is mapped, we can't use the paddles + paddles_mapped = true; + } + + } else { + // Unknown format + return; + } +#ifdef DEBUG_XBOX_PROTOCOL + SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s", + (data[paddle_index] & button1_bit) ? 1 : 0, + (data[paddle_index] & button2_bit) ? 1 : 0, + (data[paddle_index] & button3_bit) ? 1 : 0, + (data[paddle_index] & button4_bit) ? 1 : 0, + paddles_mapped ? "TRUE" : "FALSE"); +#endif + + if (paddles_mapped) { + // Respect that the paddles are being used for other controls and don't pass them on to the app + data[paddle_index] = 0; + } + + if (ctx->last_paddle_state != data[paddle_index]) { + Uint8 nButton = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); // Next available button + SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button1_bit) != 0)); + SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button2_bit) != 0)); + SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button3_bit) != 0)); + SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button4_bit) != 0)); + ctx->last_paddle_state = data[paddle_index]; + } + ctx->has_unmapped_state = true; +} + +static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + // Enable paddles on the Xbox Elite controller when connected over USB + if (ctx->has_paddles && !ctx->has_unmapped_state && size == 46) { + Uint8 packet[] = { 0x4d, 0x00, 0x00, 0x02, 0x07, 0x00 }; + +#ifdef DEBUG_JOYSTICK + SDL_Log("Enabling paddles on XBox Elite 2"); +#endif + SDL_HIDAPI_SendRumble(ctx->device, packet, sizeof(packet)); + } + + if (ctx->last_state[0] != data[0]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[0] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[0] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x80) != 0)); + } + + if (ctx->last_state[1] != data[1]) { + Uint8 hat = 0; + + if (data[1] & 0x01) { + hat |= SDL_HAT_UP; + } + if (data[1] & 0x02) { + hat |= SDL_HAT_DOWN; + } + if (data[1] & 0x04) { + hat |= SDL_HAT_LEFT; + } + if (data[1] & 0x08) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + if (ctx->vendor_id == USB_VENDOR_RAZER && ctx->product_id == USB_PRODUCT_RAZER_ATROX) { + // The Razer Atrox has the right and left shoulder bits reversed + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x10) != 0)); + } else { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0)); + } + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x80) != 0)); + } + + if (ctx->has_share_button) { + /* Xbox Series X firmware version 5.0, report is 32 bytes, share button is in byte 14 + * Xbox Series X firmware version 5.1, report is 40 bytes, share button is in byte 14 + * Xbox Series X firmware version 5.5, report is 44 bytes, share button is in byte 18 + * Victrix Gambit Tournament Controller, report is 46 bytes, share button is in byte 28 + * ThrustMaster eSwap PRO Controller Xbox, report is 60 bytes, share button is in byte 42 + */ + if (size < 44) { + if (ctx->last_state[14] != data[14]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[14] & 0x01) != 0)); + } + } else if (size == 44) { + if (ctx->last_state[18] != data[18]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[18] & 0x01) != 0)); + } + } else if (size == 46) { + if (ctx->last_state[28] != data[28]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[28] & 0x01) != 0)); + } + } else if (size == 60) { + if (ctx->last_state[42] != data[42]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[42] & 0x01) != 0)); + } + } + } + + /* Xbox One S report is 14 bytes + Xbox One Elite Series 1 report is 29 bytes, paddles in data[28], mode in data[28] & 0x10, both modes have mapped paddles by default + Paddle bits: + P3: 0x01 (A) P1: 0x02 (B) + P4: 0x04 (X) P2: 0x08 (Y) + Xbox One Elite Series 2 4.x firmware report is 34 bytes, paddles in data[14], mode in data[15], mode 0 has no mapped paddles by default + Paddle bits: + P3: 0x04 (A) P1: 0x01 (B) + P4: 0x08 (X) P2: 0x02 (Y) + Xbox One Elite Series 2 5.x firmware report is 46 bytes, paddles in data[18], mode in data[19], mode 0 has no mapped paddles by default + Paddle bits: + P3: 0x04 (A) P1: 0x01 (B) + P4: 0x08 (X) P2: 0x02 (Y) + Xbox One Elite Series 2 5.17+ firmware report is 47 bytes, paddles in data[14], mode in data[20], mode 0 has no mapped paddles by default + Paddle bits: + P3: 0x04 (A) P1: 0x01 (B) + P4: 0x08 (X) P2: 0x02 (Y) + */ + if (ctx->has_paddles && !ctx->has_unmapped_state && (size == 29 || size == 34 || size == 46 || size == 47)) { + int paddle_index; + int button1_bit; + int button2_bit; + int button3_bit; + int button4_bit; + bool paddles_mapped; + + if (size == 29) { + // XBox One Elite Series 1 + paddle_index = 28; + button1_bit = 0x02; + button2_bit = 0x08; + button3_bit = 0x01; + button4_bit = 0x04; + + // The mapped controller state is at offset 0, the raw state is at offset 14, compare them to see if the paddles are mapped + paddles_mapped = (SDL_memcmp(&data[0], &data[14], 2) != 0); + + } else if (size == 34) { + // XBox One Elite Series 2 + paddle_index = 14; + button1_bit = 0x01; + button2_bit = 0x02; + button3_bit = 0x04; + button4_bit = 0x08; + paddles_mapped = (data[15] != 0); + + } else if (size == 46) { + // XBox One Elite Series 2 + paddle_index = 18; + button1_bit = 0x01; + button2_bit = 0x02; + button3_bit = 0x04; + button4_bit = 0x08; + paddles_mapped = (data[19] != 0); + } else /* if (size == 47) */ { + // XBox One Elite Series 2 + paddle_index = 14; + button1_bit = 0x01; + button2_bit = 0x02; + button3_bit = 0x04; + button4_bit = 0x08; + paddles_mapped = (data[20] != 0); + } +#ifdef DEBUG_XBOX_PROTOCOL + SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s", + (data[paddle_index] & button1_bit) ? 1 : 0, + (data[paddle_index] & button2_bit) ? 1 : 0, + (data[paddle_index] & button3_bit) ? 1 : 0, + (data[paddle_index] & button4_bit) ? 1 : 0, + paddles_mapped ? "TRUE" : "FALSE"); +#endif + + if (paddles_mapped) { + // Respect that the paddles are being used for other controls and don't pass them on to the app + data[paddle_index] = 0; + } + + if (ctx->last_paddle_state != data[paddle_index]) { + Uint8 nButton = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); // Next available button + SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button1_bit) != 0)); + SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button2_bit) != 0)); + SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button3_bit) != 0)); + SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button4_bit) != 0)); + ctx->last_paddle_state = data[paddle_index]; + } + } + + axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[2])) * 64) - 32768; + if (axis == 32704) { + axis = 32767; + } + if (axis == -32768 && size == 26 && (data[18] & 0x80)) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[4])) * 64) - 32768; + if (axis == -32768 && size == 26 && (data[18] & 0x40)) { + axis = 32767; + } + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + axis = SDL_Swap16LE(*(Sint16 *)(&data[6])); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = SDL_Swap16LE(*(Sint16 *)(&data[8])); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); + axis = SDL_Swap16LE(*(Sint16 *)(&data[10])); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = SDL_Swap16LE(*(Sint16 *)(&data[12])); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); + + // We don't have the unmapped state for this packet + ctx->has_unmapped_state = false; +} + +static void HIDAPI_DriverXboxOne_HandleStatusPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) +{ + if (ctx->init_state < XBOX_ONE_INIT_STATE_COMPLETE) { + SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); + } +} + +static void HIDAPI_DriverXboxOne_HandleModePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) +{ + Uint64 timestamp = SDL_GetTicksNS(); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[0] & 0x01) != 0)); +} + +/* + * Xbox One S with firmware 3.1.1221 uses a 16 byte packet and the GUIDE button in a separate packet + */ +static void HIDAPI_DriverXboxOneBluetooth_HandleButtons16(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) +{ + if (ctx->last_state[14] != data[14]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[14] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[14] & 0x80) != 0)); + } + + if (ctx->last_state[15] != data[15]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x02) != 0)); + } +} + +/* + * Xbox One S with firmware 4.8.1923 uses a 17 byte packet with BACK button in byte 16 and the GUIDE button in a separate packet (on Windows), or in byte 15 (on Linux) + * Xbox One S with firmware 5.x uses a 17 byte packet with BACK and GUIDE buttons in byte 15 + * Xbox One Elite Series 2 with firmware 4.7.1872 uses a 55 byte packet with BACK button in byte 16, paddles starting at byte 33, and the GUIDE button in a separate packet + * Xbox One Elite Series 2 with firmware 4.8.1908 uses a 33 byte packet with BACK button in byte 16, paddles starting at byte 17, and the GUIDE button in a separate packet + * Xbox One Elite Series 2 with firmware 5.11.3112 uses a 19 byte packet with BACK and GUIDE buttons in byte 15 + * Xbox Series X with firmware 5.5.2641 uses a 17 byte packet with BACK and GUIDE buttons in byte 15, and SHARE button in byte 17 + */ +static void HIDAPI_DriverXboxOneBluetooth_HandleButtons(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +{ + if (ctx->last_state[14] != data[14]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x80) != 0)); + } + + if (ctx->last_state[15] != data[15]) { + if (!ctx->has_guide_packet) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[15] & 0x10) != 0)); + } + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[15] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x40) != 0)); + } + + if (ctx->has_share_button) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[15] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[16] & 0x01) != 0)); + } else { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[15] & 0x04) || ((data[16] & 0x01)) != 0)); + } + + /* + Paddle bits: + P3: 0x04 (A) P1: 0x01 (B) + P4: 0x08 (X) P2: 0x02 (Y) + */ + if (ctx->has_paddles && (size == 20 || size == 39 || size == 55)) { + int paddle_index; + int button1_bit; + int button2_bit; + int button3_bit; + int button4_bit; + bool paddles_mapped; + + if (size == 55) { + // Initial firmware for the Xbox Elite Series 2 controller + paddle_index = 33; + button1_bit = 0x01; + button2_bit = 0x02; + button3_bit = 0x04; + button4_bit = 0x08; + paddles_mapped = (data[35] != 0); + } else if (size == 39) { + // Updated firmware for the Xbox Elite Series 2 controller + paddle_index = 17; + button1_bit = 0x01; + button2_bit = 0x02; + button3_bit = 0x04; + button4_bit = 0x08; + paddles_mapped = (data[19] != 0); + } else /* if (size == 20) */ { + // Updated firmware for the Xbox Elite Series 2 controller (5.13+) + paddle_index = 19; + button1_bit = 0x01; + button2_bit = 0x02; + button3_bit = 0x04; + button4_bit = 0x08; + paddles_mapped = (data[17] != 0); + } + +#ifdef DEBUG_XBOX_PROTOCOL + SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s", + (data[paddle_index] & button1_bit) ? 1 : 0, + (data[paddle_index] & button2_bit) ? 1 : 0, + (data[paddle_index] & button3_bit) ? 1 : 0, + (data[paddle_index] & button4_bit) ? 1 : 0, + paddles_mapped ? "TRUE" : "FALSE"); +#endif + + if (paddles_mapped) { + // Respect that the paddles are being used for other controls and don't pass them on to the app + data[paddle_index] = 0; + } + + if (ctx->last_paddle_state != data[paddle_index]) { + Uint8 nButton = SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON; // Next available button + SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button1_bit) != 0)); + SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button2_bit) != 0)); + SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button3_bit) != 0)); + SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button4_bit) != 0)); + ctx->last_paddle_state = data[paddle_index]; + } + } +} + +static void HIDAPI_DriverXboxOneBluetooth_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (size == 16) { + // Original Xbox One S, with separate report for guide button + HIDAPI_DriverXboxOneBluetooth_HandleButtons16(timestamp, joystick, ctx, data, size); + } else if (size > 16) { + HIDAPI_DriverXboxOneBluetooth_HandleButtons(timestamp, joystick, ctx, data, size); + } else { +#ifdef DEBUG_XBOX_PROTOCOL + SDL_Log("Unknown Bluetooth state packet format"); +#endif + return; + } + + if (ctx->last_state[13] != data[13]) { + Uint8 hat; + + switch (data[13]) { + case 1: + hat = SDL_HAT_UP; + break; + case 2: + hat = SDL_HAT_RIGHTUP; + break; + case 3: + hat = SDL_HAT_RIGHT; + break; + case 4: + hat = SDL_HAT_RIGHTDOWN; + break; + case 5: + hat = SDL_HAT_DOWN; + break; + case 6: + hat = SDL_HAT_LEFTDOWN; + break; + case 7: + hat = SDL_HAT_LEFT; + break; + case 8: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[9])) * 64) - 32768; + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[11])) * 64) - 32768; + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[1])) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[3])) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[5])) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[7])) - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) +{ + Uint64 timestamp = SDL_GetTicksNS(); + + ctx->has_guide_packet = true; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x01) != 0)); +} + +static void HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) +{ + Uint8 flags = data[1]; + bool on_usb = (((flags & 0x0C) >> 2) == 0); + SDL_PowerState state; + int percent = 0; + + // Mapped percentage value from: + // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getbatterystate + switch (flags & 0x03) { + case 0: + percent = 10; + break; + case 1: + percent = 40; + break; + case 2: + percent = 70; + break; + case 3: + percent = 100; + break; + } + if (on_usb) { + state = SDL_POWERSTATE_CHARGING; + } else { + state = SDL_POWERSTATE_ON_BATTERY; + } + SDL_SendJoystickPowerInfo(joystick, state, percent); +} + +static void HIDAPI_DriverXboxOne_HandleSerialIDPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) +{ + char serial[29]; + int i; + + for (i = 0; i < 14; ++i) { + SDL_uitoa(data[2 + i], &serial[i * 2], 16); + } + serial[i * 2] = '\0'; + +#ifdef DEBUG_JOYSTICK + SDL_Log("Setting serial number to %s", serial); +#endif + HIDAPI_SetDeviceSerial(ctx->device, serial); +} + +static bool HIDAPI_DriverXboxOne_UpdateInitState(SDL_DriverXboxOne_Context *ctx) +{ + SDL_XboxOneInitState prev_state; + do { + prev_state = ctx->init_state; + + switch (ctx->init_state) { + case XBOX_ONE_INIT_STATE_ANNOUNCED: + if (XBOX_ONE_DRIVER_ACTIVE) { + // The driver is taking care of identification + SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); + } else { + SendIdentificationRequest(ctx); + SetInitState(ctx, XBOX_ONE_INIT_STATE_IDENTIFYING); + } + break; + case XBOX_ONE_INIT_STATE_IDENTIFYING: + if (SDL_GetTicks() >= (ctx->send_time + CONTROLLER_IDENTIFY_TIMEOUT_MS)) { + // We haven't heard anything, let's move on +#ifdef DEBUG_JOYSTICK + SDL_Log("Identification request timed out after %llu ms", (SDL_GetTicks() - ctx->send_time)); +#endif + SetInitState(ctx, XBOX_ONE_INIT_STATE_STARTUP); + } + break; + case XBOX_ONE_INIT_STATE_STARTUP: + if (XBOX_ONE_DRIVER_ACTIVE) { + // The driver is taking care of startup + SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); + } else { + SendControllerStartup(ctx); + SetInitState(ctx, XBOX_ONE_INIT_STATE_PREPARE_INPUT); + } + break; + case XBOX_ONE_INIT_STATE_PREPARE_INPUT: + if (SDL_GetTicks() >= (ctx->send_time + CONTROLLER_PREPARE_INPUT_TIMEOUT_MS)) { +#ifdef DEBUG_JOYSTICK + SDL_Log("Prepare input complete after %llu ms", (SDL_GetTicks() - ctx->send_time)); +#endif + SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); + } + break; + case XBOX_ONE_INIT_STATE_COMPLETE: + break; + } + + } while (ctx->init_state != prev_state); + + return true; +} + +/* GIP protocol handling adapted under the Zlib license with permission from @medusalix: + * https://github.com/medusalix/xone/blob/master/bus/protocol.h + * https://github.com/medusalix/xone/blob/master/bus/protocol.c + */ +#define GIP_HEADER_MIN_LENGTH 3 + +// Internal commands +#define GIP_CMD_ACKNOWLEDGE 0x01 +#define GIP_CMD_ANNOUNCE 0x02 +#define GIP_CMD_STATUS 0x03 +#define GIP_CMD_IDENTIFY 0x04 +#define GIP_CMD_POWER 0x05 +#define GIP_CMD_AUTHENTICATE 0x06 +#define GIP_CMD_VIRTUAL_KEY 0x07 +#define GIP_CMD_AUDIO_CONTROL 0x08 +#define GIP_CMD_LED 0x0A +#define GIP_CMD_HID_REPORT 0x0B +#define GIP_CMD_FIRMWARE 0x0C +#define GIP_CMD_SERIAL_NUMBER 0x1E +#define GIP_CMD_AUDIO_SAMPLES 0x60 + +// External commands +#define GIP_CMD_RUMBLE 0x09 +#define GIP_CMD_UNMAPPED_STATE 0x0C +#define GIP_CMD_INPUT 0x20 + +// Header option flags +#define GIP_OPT_ACKNOWLEDGE 0x10 +#define GIP_OPT_INTERNAL 0x20 +#define GIP_OPT_CHUNK_START 0x40 +#define GIP_OPT_CHUNK 0x80 + +#pragma pack(push, 1) + +struct gip_header { + Uint8 command; + Uint8 options; + Uint8 sequence; + Uint32 packet_length; + Uint32 chunk_offset; +}; + +struct gip_pkt_acknowledge { + Uint8 unknown; + Uint8 command; + Uint8 options; + Uint16 length; + Uint8 padding[2]; + Uint16 remaining; +}; + +#pragma pack(pop) + +static int EncodeVariableInt(Uint8 *buf, Uint32 val) +{ + int i; + + for (i = 0; i < sizeof(val); i++) { + buf[i] = (Uint8)val; + if (val > 0x7F) { + buf[i] |= 0x80; + } + + val >>= 7; + if (!val) { + break; + } + } + return i + 1; +} + +static int DecodeVariableInt(const Uint8 *data, int len, void *out) +{ + int i; + Uint32 val = 0; + + for (i = 0; i < sizeof(val) && i < len; i++) { + val |= (data[i] & 0x7F) << (i * 7); + + if (!(data[i] & 0x80)) { + break; + } + } + SDL_memcpy(out, &val, sizeof(val)); + return i + 1; +} + +static int HIDAPI_GIP_GetActualHeaderLength(struct gip_header *hdr) +{ + Uint32 pkt_len = hdr->packet_length; + Uint32 chunk_offset = hdr->chunk_offset; + int len = GIP_HEADER_MIN_LENGTH; + + do { + len++; + pkt_len >>= 7; + } while (pkt_len); + + if (hdr->options & GIP_OPT_CHUNK) { + while (chunk_offset) { + len++; + chunk_offset >>= 7; + } + } + + return len; +} + +static int HIDAPI_GIP_GetHeaderLength(struct gip_header *hdr) +{ + int len = HIDAPI_GIP_GetActualHeaderLength(hdr); + + // Header length must be even + return len + (len % 2); +} + +static void HIDAPI_GIP_EncodeHeader(struct gip_header *hdr, Uint8 *buf) +{ + int hdr_len = 0; + + buf[hdr_len++] = hdr->command; + buf[hdr_len++] = hdr->options; + buf[hdr_len++] = hdr->sequence; + + hdr_len += EncodeVariableInt(buf + hdr_len, hdr->packet_length); + + // Header length must be even + if (HIDAPI_GIP_GetActualHeaderLength(hdr) % 2) { + buf[hdr_len - 1] |= 0x80; + buf[hdr_len++] = 0; + } + + if (hdr->options & GIP_OPT_CHUNK) { + EncodeVariableInt(buf + hdr_len, hdr->chunk_offset); + } +} + +static int HIDAPI_GIP_DecodeHeader(struct gip_header *hdr, const Uint8 *data, int len) +{ + int hdr_len = 0; + + hdr->command = data[hdr_len++]; + hdr->options = data[hdr_len++]; + hdr->sequence = data[hdr_len++]; + hdr->packet_length = 0; + hdr->chunk_offset = 0; + + hdr_len += DecodeVariableInt(data + hdr_len, len - hdr_len, &hdr->packet_length); + + if (hdr->options & GIP_OPT_CHUNK) { + hdr_len += DecodeVariableInt(data + hdr_len, len - hdr_len, &hdr->chunk_offset); + } + return hdr_len; +} + +static bool HIDAPI_GIP_SendPacket(SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, const void *data) +{ + Uint8 packet[USB_PACKET_LENGTH]; + int hdr_len, size; + + hdr_len = HIDAPI_GIP_GetHeaderLength(hdr); + size = (hdr_len + hdr->packet_length); + if (size > sizeof(packet)) { + SDL_SetError("Couldn't send GIP packet, size (%d) too large", size); + return false; + } + + if (!hdr->sequence) { + hdr->sequence = GetNextPacketSequence(ctx); + } + + HIDAPI_GIP_EncodeHeader(hdr, packet); + if (data) { + SDL_memcpy(&packet[hdr_len], data, hdr->packet_length); + } + + if (!SendProtocolPacket(ctx, packet, size)) { + SDL_SetError("Couldn't send protocol packet"); + return false; + } + return true; +} + +static bool HIDAPI_GIP_AcknowledgePacket(SDL_DriverXboxOne_Context *ctx, struct gip_header *ack) +{ + if (XBOX_ONE_DRIVER_ACTIVE) { + // The driver is taking care of acks + return true; + } else { + struct gip_header hdr; + struct gip_pkt_acknowledge pkt; + + SDL_zero(hdr); + hdr.command = GIP_CMD_ACKNOWLEDGE; + hdr.options = GIP_OPT_INTERNAL; + hdr.sequence = ack->sequence; + hdr.packet_length = sizeof(pkt); + + SDL_zero(pkt); + pkt.command = ack->command; + pkt.options = GIP_OPT_INTERNAL; + pkt.length = SDL_Swap16LE((Uint16)(ack->chunk_offset + ack->packet_length)); + + if ((ack->options & GIP_OPT_CHUNK) && ctx->chunk_buffer) { + pkt.remaining = SDL_Swap16LE((Uint16)(ctx->chunk_length - pkt.length)); + } + + return HIDAPI_GIP_SendPacket(ctx, &hdr, &pkt); + } +} + +static bool HIDAPI_GIP_DispatchPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data, Uint32 size) +{ + if ((hdr->options & 0x0F) != 0) { + // This is a packet for a device plugged into the controller, skip it + return true; + } + + if (hdr->options & GIP_OPT_INTERNAL) { + switch (hdr->command) { + case GIP_CMD_ACKNOWLEDGE: + // Ignore this packet + break; + case GIP_CMD_ANNOUNCE: + // Controller is connected and waiting for initialization + /* The data bytes are: + 0x02 0x20 NN 0x1c, where NN is the packet sequence + then 6 bytes of wireless MAC address + then 2 bytes padding + then 16-bit VID + then 16-bit PID + then 16-bit firmware version quartet AA.BB.CC.DD + e.g. 0x05 0x00 0x05 0x00 0x51 0x0a 0x00 0x00 + is firmware version 5.5.2641.0, and product version 0x0505 = 1285 + then 8 bytes of unknown data + */ +#ifdef DEBUG_JOYSTICK + SDL_Log("Controller announce after %llu ms", (SDL_GetTicks() - ctx->start_time)); +#endif + SetInitState(ctx, XBOX_ONE_INIT_STATE_ANNOUNCED); + break; + case GIP_CMD_STATUS: + // Controller status update + HIDAPI_DriverXboxOne_HandleStatusPacket(ctx, data, size); + break; + case GIP_CMD_IDENTIFY: +#ifdef DEBUG_JOYSTICK + SDL_Log("Identification request completed after %llu ms", (SDL_GetTicks() - ctx->send_time)); +#endif +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("Xbox One identification data: size = %d", data, size); +#endif + SetInitState(ctx, XBOX_ONE_INIT_STATE_STARTUP); + break; + case GIP_CMD_POWER: + // Ignore this packet + break; + case GIP_CMD_AUTHENTICATE: + // Ignore this packet + break; + case GIP_CMD_VIRTUAL_KEY: + if (!joystick) { + break; + } + HIDAPI_DriverXboxOne_HandleModePacket(joystick, ctx, data, size); + break; + case GIP_CMD_SERIAL_NUMBER: + /* If the packet starts with this: + 0x1E 0x30 0x00 0x10 0x04 0x00 + then the next 14 bytes are the controller serial number + e.g. 0x30 0x39 0x37 0x31 0x32 0x33 0x33 0x32 0x33 0x35 0x34 0x30 0x33 0x36 + is serial number "3039373132333332333534303336" + + The controller sends that in response to this request: + 0x1E 0x20 0x00 0x01 0x04 + */ + HIDAPI_DriverXboxOne_HandleSerialIDPacket(ctx, data, size); + break; + default: +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown Xbox One packet: 0x%.2x", hdr->command); +#endif + break; + } + } else { + switch (hdr->command) { + case GIP_CMD_INPUT: + if (ctx->init_state < XBOX_ONE_INIT_STATE_COMPLETE) { + SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); + + // Ignore the first input, it may be spurious +#ifdef DEBUG_JOYSTICK + SDL_Log("Controller ignoring spurious input"); +#endif + break; + } + if (!joystick) { + break; + } + HIDAPI_DriverXboxOne_HandleStatePacket(joystick, ctx, data, size); + break; + case GIP_CMD_UNMAPPED_STATE: + if (!joystick) { + break; + } + HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(joystick, ctx, data, size); + break; + default: +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown Xbox One packet: 0x%.2x", hdr->command); +#endif + break; + } + } + return true; +} + +static void HIDAPI_GIP_DestroyChunkBuffer(SDL_DriverXboxOne_Context *ctx) +{ + if (ctx->chunk_buffer) { + SDL_free(ctx->chunk_buffer); + ctx->chunk_buffer = NULL; + ctx->chunk_length = 0; + } +} + +static bool HIDAPI_GIP_CreateChunkBuffer(SDL_DriverXboxOne_Context *ctx, Uint32 size) +{ + HIDAPI_GIP_DestroyChunkBuffer(ctx); + + ctx->chunk_buffer = (Uint8 *)SDL_malloc(size); + if (ctx->chunk_buffer) { + ctx->chunk_length = size; + return true; + } else { + return false; + } +} + +static bool HIDAPI_GIP_ProcessPacketChunked(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data) +{ + bool result; + + if (!ctx->chunk_buffer) { + return false; + } + + if ((hdr->chunk_offset + hdr->packet_length) > ctx->chunk_length) { + return false; + } + + if (hdr->packet_length) { + SDL_memcpy(ctx->chunk_buffer + hdr->chunk_offset, data, hdr->packet_length); + return true; + } + + result = HIDAPI_GIP_DispatchPacket(joystick, ctx, hdr, ctx->chunk_buffer, ctx->chunk_length); + + HIDAPI_GIP_DestroyChunkBuffer(ctx); + + return result; +} + +static bool HIDAPI_GIP_ProcessPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data) +{ + if (hdr->options & GIP_OPT_CHUNK_START) { + if (!HIDAPI_GIP_CreateChunkBuffer(ctx, hdr->chunk_offset)) { + return false; + } + ctx->chunk_length = hdr->chunk_offset; + + hdr->chunk_offset = 0; + } + + if (hdr->options & GIP_OPT_ACKNOWLEDGE) { + if (!HIDAPI_GIP_AcknowledgePacket(ctx, hdr)) { + return false; + } + } + + if (hdr->options & GIP_OPT_CHUNK) { + return HIDAPI_GIP_ProcessPacketChunked(joystick, ctx, hdr, data); + } else { + return HIDAPI_GIP_DispatchPacket(joystick, ctx, hdr, data, hdr->packet_length); + } +} + +static bool HIDAPI_GIP_ProcessData(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +{ + struct gip_header hdr; + int hdr_len; + + while (size > GIP_HEADER_MIN_LENGTH) { + hdr_len = HIDAPI_GIP_DecodeHeader(&hdr, data, size); + if ((hdr_len + hdr.packet_length) > (Uint32)size) { + // On macOS we get a shortened version of the real report + hdr.packet_length = (Uint32)(size - hdr_len); + } + + if (!HIDAPI_GIP_ProcessPacket(joystick, ctx, &hdr, data + hdr_len)) { + return false; + } + + data += hdr_len + hdr.packet_length; + size -= hdr_len + hdr.packet_length; + } + return true; +} + +static bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("Xbox One packet: size = %d", data, size); +#endif + if (device->is_bluetooth) { + switch (data[0]) { + case 0x01: + if (!joystick) { + break; + } + if (size >= 16) { + HIDAPI_DriverXboxOneBluetooth_HandleStatePacket(joystick, ctx, data, size); + } else { +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown Xbox One Bluetooth packet size: %d", size); +#endif + } + break; + case 0x02: + if (!joystick) { + break; + } + HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(joystick, ctx, data, size); + break; + case 0x04: + if (!joystick) { + break; + } + HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(joystick, ctx, data, size); + break; + default: +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown Xbox One packet: 0x%.2x", data[0]); +#endif + break; + } + } else { + HIDAPI_GIP_ProcessData(joystick, ctx, data, size); + } + } + + HIDAPI_DriverXboxOne_UpdateInitState(ctx); + HIDAPI_DriverXboxOne_UpdateRumble(ctx); + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverXboxOne_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED, + SDL_HomeLEDHintChanged, ctx); +} + +static void HIDAPI_DriverXboxOne_FreeDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; + + HIDAPI_GIP_DestroyChunkBuffer(ctx); +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne = { + SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, + true, + HIDAPI_DriverXboxOne_RegisterHints, + HIDAPI_DriverXboxOne_UnregisterHints, + HIDAPI_DriverXboxOne_IsEnabled, + HIDAPI_DriverXboxOne_IsSupportedDevice, + HIDAPI_DriverXboxOne_InitDevice, + HIDAPI_DriverXboxOne_GetDevicePlayerIndex, + HIDAPI_DriverXboxOne_SetDevicePlayerIndex, + HIDAPI_DriverXboxOne_UpdateDevice, + HIDAPI_DriverXboxOne_OpenJoystick, + HIDAPI_DriverXboxOne_RumbleJoystick, + HIDAPI_DriverXboxOne_RumbleJoystickTriggers, + HIDAPI_DriverXboxOne_GetJoystickCapabilities, + HIDAPI_DriverXboxOne_SetJoystickLED, + HIDAPI_DriverXboxOne_SendJoystickEffect, + HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled, + HIDAPI_DriverXboxOne_CloseJoystick, + HIDAPI_DriverXboxOne_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_XBOXONE + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick.c new file mode 100644 index 0000000..aec6463 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick.c @@ -0,0 +1,1730 @@ +/* + 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_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" +#include "../../SDL_hints_c.h" + +#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) +#include "../windows/SDL_rawinputjoystick_c.h" +#endif + + +struct joystick_hwdata +{ + SDL_HIDAPI_Device *device; +}; + +static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { +#ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE + &SDL_HIDAPI_DriverGameCube, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_LUNA + &SDL_HIDAPI_DriverLuna, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_SHIELD + &SDL_HIDAPI_DriverShield, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_PS3 + &SDL_HIDAPI_DriverPS3, + &SDL_HIDAPI_DriverPS3ThirdParty, + &SDL_HIDAPI_DriverPS3SonySixaxis, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_PS4 + &SDL_HIDAPI_DriverPS4, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_PS5 + &SDL_HIDAPI_DriverPS5, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_STADIA + &SDL_HIDAPI_DriverStadia, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_STEAM + &SDL_HIDAPI_DriverSteam, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_STEAM_HORI + &SDL_HIDAPI_DriverSteamHori, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK + &SDL_HIDAPI_DriverSteamDeck, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_SWITCH + &SDL_HIDAPI_DriverNintendoClassic, + &SDL_HIDAPI_DriverJoyCons, + &SDL_HIDAPI_DriverSwitch, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_WII + &SDL_HIDAPI_DriverWii, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_XBOX360 + &SDL_HIDAPI_DriverXbox360, + &SDL_HIDAPI_DriverXbox360W, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE + &SDL_HIDAPI_DriverXboxOne, +#endif +}; +static int SDL_HIDAPI_numdrivers = 0; +static SDL_AtomicInt SDL_HIDAPI_updating_devices; +static bool SDL_HIDAPI_hints_changed = false; +static Uint32 SDL_HIDAPI_change_count = 0; +static SDL_HIDAPI_Device *SDL_HIDAPI_devices SDL_GUARDED_BY(SDL_joystick_lock); +static int SDL_HIDAPI_numjoysticks = 0; +static bool SDL_HIDAPI_combine_joycons = true; +static bool initialized = false; +static bool shutting_down = false; + +static char *HIDAPI_ConvertString(const wchar_t *wide_string) +{ + char *string = NULL; + + if (wide_string) { + string = SDL_iconv_string("UTF-8", "WCHAR_T", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t)); + if (!string) { + switch (sizeof(wchar_t)) { + case 2: + string = SDL_iconv_string("UTF-8", "UCS-2-INTERNAL", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t)); + break; + case 4: + string = SDL_iconv_string("UTF-8", "UCS-4-INTERNAL", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t)); + break; + } + } + } + return string; +} + +void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size) +{ + int i; + char *buffer; + size_t length = SDL_strlen(prefix) + 11 * (size / 8) + (5 * size * 2) + 1 + 1; + int start = 0, amount = size; + size_t current_len; + + buffer = (char *)SDL_malloc(length); + current_len = SDL_snprintf(buffer, length, prefix, size); + for (i = start; i < start + amount; ++i) { + if ((i % 8) == 0) { + current_len += SDL_snprintf(&buffer[current_len], length - current_len, "\n%.2d: ", i); + } + current_len += SDL_snprintf(&buffer[current_len], length - current_len, " 0x%.2x", data[i]); + } + SDL_strlcat(buffer, "\n", length); + SDL_Log("%s", buffer); + SDL_free(buffer); +} + +bool HIDAPI_SupportsPlaystationDetection(Uint16 vendor, Uint16 product) +{ + /* If we already know the controller is a different type, don't try to detect it. + * This fixes a hang with the HORIPAD for Nintendo Switch (0x0f0d/0x00c1) + */ + if (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, false) != SDL_GAMEPAD_TYPE_STANDARD) { + return false; + } + + switch (vendor) { + case USB_VENDOR_DRAGONRISE: + return true; + case USB_VENDOR_HORI: + return true; + case USB_VENDOR_LOGITECH: + /* Most Logitech devices are not PlayStation controllers, and some of them + * lock up or reset when we send them the Sony third-party query feature + * report, so don't include that vendor here. Instead add devices as + * appropriate to controller_list.h + */ + return false; + case USB_VENDOR_MADCATZ: + if (product == USB_PRODUCT_MADCATZ_SAITEK_SIDE_PANEL_CONTROL_DECK) { + // This is not a Playstation compatible device + return false; + } + return true; + case USB_VENDOR_MAYFLASH: + return true; + case USB_VENDOR_NACON: + case USB_VENDOR_NACON_ALT: + return true; + case USB_VENDOR_PDP: + return true; + case USB_VENDOR_POWERA: + return true; + case USB_VENDOR_POWERA_ALT: + return true; + case USB_VENDOR_QANBA: + return true; + case USB_VENDOR_RAZER: + /* Most Razer devices are not PlayStation controllers, and some of them + * lock up or reset when we send them the Sony third-party query feature + * report, so don't include that vendor here. Instead add devices as + * appropriate to controller_list.h + * + * Reference: https://github.com/libsdl-org/SDL/issues/6733 + * https://github.com/libsdl-org/SDL/issues/6799 + */ + return false; + case USB_VENDOR_SHANWAN: + return true; + case USB_VENDOR_SHANWAN_ALT: + return true; + case USB_VENDOR_THRUSTMASTER: + /* Most of these are wheels, don't have the full set of effects, and + * at least in the case of the T248 and T300 RS, the hid-tmff2 driver + * puts them in a non-standard report mode and they can't be read. + * + * If these should use the HIDAPI driver, add them to controller_list.h + */ + return false; + case USB_VENDOR_ZEROPLUS: + return true; + case 0x7545 /* SZ-MYPOWER */: + return true; + default: + return false; + } +} + +float HIDAPI_RemapVal(float val, float val_min, float val_max, float output_min, float output_max) +{ + return output_min + (output_max - output_min) * (val - val_min) / (val_max - val_min); +} + +static void HIDAPI_UpdateDeviceList(void); +static void HIDAPI_JoystickClose(SDL_Joystick *joystick); + +static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, Uint16 vendor, Uint16 product, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + static const int LIBUSB_CLASS_VENDOR_SPEC = 0xFF; + static const int XB360_IFACE_SUBCLASS = 93; + static const int XB360_IFACE_PROTOCOL = 1; // Wired + static const int XB360W_IFACE_PROTOCOL = 129; // Wireless + static const int XBONE_IFACE_SUBCLASS = 71; + static const int XBONE_IFACE_PROTOCOL = 208; + + SDL_GamepadType type = SDL_GAMEPAD_TYPE_STANDARD; + + // This code should match the checks in libusb/hid.c and HIDDeviceManager.java + if (interface_class == LIBUSB_CLASS_VENDOR_SPEC && + interface_subclass == XB360_IFACE_SUBCLASS && + (interface_protocol == XB360_IFACE_PROTOCOL || + interface_protocol == XB360W_IFACE_PROTOCOL)) { + + static const int SUPPORTED_VENDORS[] = { + 0x0079, // GPD Win 2 + 0x044f, // Thrustmaster + 0x045e, // Microsoft + 0x046d, // Logitech + 0x056e, // Elecom + 0x06a3, // Saitek + 0x0738, // Mad Catz + 0x07ff, // Mad Catz + 0x0e6f, // PDP + 0x0f0d, // Hori + 0x1038, // SteelSeries + 0x11c9, // Nacon + 0x12ab, // Unknown + 0x1430, // RedOctane + 0x146b, // BigBen + 0x1532, // Razer + 0x15e4, // Numark + 0x162e, // Joytech + 0x1689, // Razer Onza + 0x1949, // Lab126, Inc. + 0x1bad, // Harmonix + 0x20d6, // PowerA + 0x24c6, // PowerA + 0x2c22, // Qanba + 0x2dc8, // 8BitDo + 0x9886, // ASTRO Gaming + }; + + int i; + for (i = 0; i < SDL_arraysize(SUPPORTED_VENDORS); ++i) { + if (vendor == SUPPORTED_VENDORS[i]) { + type = SDL_GAMEPAD_TYPE_XBOX360; + break; + } + } + } + + if (interface_number == 0 && + interface_class == LIBUSB_CLASS_VENDOR_SPEC && + interface_subclass == XBONE_IFACE_SUBCLASS && + interface_protocol == XBONE_IFACE_PROTOCOL) { + + static const int SUPPORTED_VENDORS[] = { + 0x03f0, // HP + 0x044f, // Thrustmaster + 0x045e, // Microsoft + 0x0738, // Mad Catz + 0x0b05, // ASUS + 0x0e6f, // PDP + 0x0f0d, // Hori + 0x10f5, // Turtle Beach + 0x1532, // Razer + 0x20d6, // PowerA + 0x24c6, // PowerA + 0x2dc8, // 8BitDo + 0x2e24, // Hyperkin + 0x3537, // GameSir + }; + + int i; + for (i = 0; i < SDL_arraysize(SUPPORTED_VENDORS); ++i) { + if (vendor == SUPPORTED_VENDORS[i]) { + type = SDL_GAMEPAD_TYPE_XBOXONE; + break; + } + } + } + + if (type == SDL_GAMEPAD_TYPE_STANDARD) { + type = SDL_GetGamepadTypeFromVIDPID(vendor, product, name, false); + } + return type; +} + +static bool HIDAPI_IsDeviceSupported(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + int i; + SDL_GamepadType type = SDL_GetJoystickGameControllerProtocol(name, vendor_id, product_id, -1, 0, 0, 0); + + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; + if (driver->enabled && driver->IsSupportedDevice(NULL, name, type, vendor_id, product_id, version, -1, 0, 0, 0)) { + return true; + } + } + return false; +} + +static SDL_HIDAPI_DeviceDriver *HIDAPI_GetDeviceDriver(SDL_HIDAPI_Device *device) +{ + const Uint16 USAGE_PAGE_GENERIC_DESKTOP = 0x0001; + const Uint16 USAGE_JOYSTICK = 0x0004; + const Uint16 USAGE_GAMEPAD = 0x0005; + const Uint16 USAGE_MULTIAXISCONTROLLER = 0x0008; + int i; + + if (device->num_children > 0) { + return &SDL_HIDAPI_DriverCombined; + } + + if (SDL_ShouldIgnoreJoystick(device->vendor_id, device->product_id, device->version, device->name)) { + return NULL; + } + + if (device->vendor_id != USB_VENDOR_VALVE) { + if (device->usage_page && device->usage_page != USAGE_PAGE_GENERIC_DESKTOP) { + return NULL; + } + if (device->usage && device->usage != USAGE_JOYSTICK && device->usage != USAGE_GAMEPAD && device->usage != USAGE_MULTIAXISCONTROLLER) { + return NULL; + } + } + + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; + if (driver->enabled && driver->IsSupportedDevice(device, device->name, device->type, device->vendor_id, device->product_id, device->version, device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol)) { + return driver; + } + } + return NULL; +} + +static SDL_HIDAPI_Device *HIDAPI_GetDeviceByIndex(int device_index, SDL_JoystickID *pJoystickID) +{ + SDL_HIDAPI_Device *device; + + SDL_AssertJoysticksLocked(); + + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (device->parent || device->broken) { + continue; + } + if (device->driver) { + if (device_index < device->num_joysticks) { + if (pJoystickID) { + *pJoystickID = device->joysticks[device_index]; + } + return device; + } + device_index -= device->num_joysticks; + } + } + return NULL; +} + +static SDL_HIDAPI_Device *HIDAPI_GetJoystickByInfo(const char *path, Uint16 vendor_id, Uint16 product_id) +{ + SDL_HIDAPI_Device *device; + + SDL_AssertJoysticksLocked(); + + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (device->vendor_id == vendor_id && device->product_id == product_id && + SDL_strcmp(device->path, path) == 0) { + break; + } + } + return device; +} + +static void HIDAPI_CleanupDeviceDriver(SDL_HIDAPI_Device *device) +{ + if (!device->driver) { + return; // Already cleaned up + } + + // Disconnect any joysticks + while (device->num_joysticks && device->joysticks) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + + device->driver->FreeDevice(device); + device->driver = NULL; + + SDL_LockMutex(device->dev_lock); + { + if (device->dev) { + SDL_hid_close(device->dev); + device->dev = NULL; + } + + if (device->context) { + SDL_free(device->context); + device->context = NULL; + } + } + SDL_UnlockMutex(device->dev_lock); +} + +static void HIDAPI_SetupDeviceDriver(SDL_HIDAPI_Device *device, bool *removed) SDL_NO_THREAD_SAFETY_ANALYSIS // We unlock the joystick lock to be able to open the HID device on Android +{ + *removed = false; + + if (device->driver) { + bool enabled; + + if (device->vendor_id == USB_VENDOR_NINTENDO && device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) { + enabled = SDL_HIDAPI_combine_joycons; + } else { + enabled = device->driver->enabled; + } + if (device->children) { + int i; + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + if (!child->driver || !child->driver->enabled) { + enabled = false; + break; + } + } + } + if (!enabled) { + HIDAPI_CleanupDeviceDriver(device); + } + return; // Already setup + } + + if (HIDAPI_GetDeviceDriver(device)) { + // We might have a device driver for this device, try opening it and see + if (device->num_children == 0) { + SDL_hid_device *dev; + + // Wait a little bit for the device to initialize + SDL_Delay(10); + + dev = SDL_hid_open_path(device->path); + + if (dev == NULL) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "HIDAPI_SetupDeviceDriver() couldn't open %s: %s", + device->path, SDL_GetError()); + return; + } + SDL_hid_set_nonblocking(dev, 1); + + device->dev = dev; + } + + device->driver = HIDAPI_GetDeviceDriver(device); + + // Initialize the device, which may cause a connected event + if (device->driver && !device->driver->InitDevice(device)) { + HIDAPI_CleanupDeviceDriver(device); + } + + if (!device->driver && device->dev) { + // No driver claimed this device, go ahead and close it + SDL_hid_close(device->dev); + device->dev = NULL; + } + } +} + +static void SDL_HIDAPI_UpdateDrivers(void) +{ + int i; + SDL_HIDAPI_Device *device; + bool removed; + + SDL_AssertJoysticksLocked(); + + SDL_HIDAPI_numdrivers = 0; + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; + driver->enabled = driver->IsEnabled(); + if (driver->enabled && driver != &SDL_HIDAPI_DriverCombined) { + ++SDL_HIDAPI_numdrivers; + } + } + + removed = false; + do { + for (device = SDL_HIDAPI_devices; device; device = device->next) { + HIDAPI_SetupDeviceDriver(device, &removed); + if (removed) { + break; + } + } + } while (removed); +} + +static void SDLCALL SDL_HIDAPIDriverHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + if (SDL_strcmp(name, SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS) == 0) { + SDL_HIDAPI_combine_joycons = SDL_GetStringBoolean(hint, true); + } + SDL_HIDAPI_hints_changed = true; + SDL_HIDAPI_change_count = 0; +} + +static bool HIDAPI_JoystickInit(void) +{ + int i; + + if (initialized) { + return true; + } + + if (SDL_hid_init() < 0) { + return SDL_SetError("Couldn't initialize hidapi"); + } + + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; + driver->RegisterHints(SDL_HIDAPIDriverHintChanged, driver); + } + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS, + SDL_HIDAPIDriverHintChanged, NULL); + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI, + SDL_HIDAPIDriverHintChanged, NULL); + + SDL_HIDAPI_change_count = SDL_hid_device_change_count(); + HIDAPI_UpdateDeviceList(); + HIDAPI_UpdateDevices(); + + initialized = true; + + return true; +} + +static bool HIDAPI_AddJoystickInstanceToDevice(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) +{ + SDL_JoystickID *joysticks = (SDL_JoystickID *)SDL_realloc(device->joysticks, (device->num_joysticks + 1) * sizeof(*device->joysticks)); + if (!joysticks) { + return false; + } + + device->joysticks = joysticks; + device->joysticks[device->num_joysticks++] = joystickID; + return true; +} + +static bool HIDAPI_DelJoystickInstanceFromDevice(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) +{ + int i, size; + + for (i = 0; i < device->num_joysticks; ++i) { + if (device->joysticks[i] == joystickID) { + size = (device->num_joysticks - i - 1) * sizeof(SDL_JoystickID); + SDL_memmove(&device->joysticks[i], &device->joysticks[i + 1], size); + --device->num_joysticks; + if (device->num_joysticks == 0) { + SDL_free(device->joysticks); + device->joysticks = NULL; + } + return true; + } + } + return false; +} + +static bool HIDAPI_JoystickInstanceIsUnique(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) +{ + if (device->parent && device->num_joysticks == 1 && device->parent->num_joysticks == 1 && + device->joysticks[0] == device->parent->joysticks[0]) { + return false; + } + return true; +} + +void HIDAPI_SetDeviceName(SDL_HIDAPI_Device *device, const char *name) +{ + if (name && *name && SDL_strcmp(name, device->name) != 0) { + SDL_free(device->name); + device->name = SDL_strdup(name); + SDL_SetJoystickGUIDCRC(&device->guid, SDL_crc16(0, name, SDL_strlen(name))); + } +} + +void HIDAPI_SetDeviceProduct(SDL_HIDAPI_Device *device, Uint16 vendor_id, Uint16 product_id) +{ + // Don't set the device product ID directly, or we'll constantly re-enumerate this device + device->guid = SDL_CreateJoystickGUID(device->guid.data[0], vendor_id, product_id, device->version, device->manufacturer_string, device->product_string, 'h', 0); +} + +static void HIDAPI_UpdateJoystickSerial(SDL_HIDAPI_Device *device) +{ + int i; + + SDL_AssertJoysticksLocked(); + + for (i = 0; i < device->num_joysticks; ++i) { + SDL_Joystick *joystick = SDL_GetJoystickFromID(device->joysticks[i]); + if (joystick && device->serial) { + SDL_free(joystick->serial); + joystick->serial = SDL_strdup(device->serial); + } + } +} + +static bool HIDAPI_SerialIsEmpty(SDL_HIDAPI_Device *device) +{ + bool all_zeroes = true; + + if (device->serial) { + const char *serial = device->serial; + for (serial = device->serial; *serial; ++serial) { + if (*serial != '0') { + all_zeroes = false; + break; + } + } + } + return all_zeroes; +} + +void HIDAPI_SetDeviceSerial(SDL_HIDAPI_Device *device, const char *serial) +{ + if (serial && *serial && (!device->serial || SDL_strcmp(serial, device->serial) != 0)) { + SDL_free(device->serial); + device->serial = SDL_strdup(serial); + HIDAPI_UpdateJoystickSerial(device); + } +} + +static int wcstrcmp(const wchar_t *str1, const char *str2) +{ + int result; + + while (1) { + result = (*str1 - *str2); + if (result != 0 || *str1 == 0) { + break; + } + ++str1; + ++str2; + } + return result; +} + +static void HIDAPI_SetDeviceSerialW(SDL_HIDAPI_Device *device, const wchar_t *serial) +{ + if (serial && *serial && (!device->serial || wcstrcmp(serial, device->serial) != 0)) { + SDL_free(device->serial); + device->serial = HIDAPI_ConvertString(serial); + HIDAPI_UpdateJoystickSerial(device); + } +} + +bool HIDAPI_HasConnectedUSBDevice(const char *serial) +{ + SDL_HIDAPI_Device *device; + + SDL_AssertJoysticksLocked(); + + if (!serial) { + return false; + } + + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (!device->driver || device->broken) { + continue; + } + + if (device->is_bluetooth) { + continue; + } + + if (device->serial && SDL_strcmp(serial, device->serial) == 0) { + return true; + } + } + return false; +} + +void HIDAPI_DisconnectBluetoothDevice(const char *serial) +{ + SDL_HIDAPI_Device *device; + + SDL_AssertJoysticksLocked(); + + if (!serial) { + return; + } + + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (!device->driver || device->broken) { + continue; + } + + if (!device->is_bluetooth) { + continue; + } + + if (device->serial && SDL_strcmp(serial, device->serial) == 0) { + while (device->num_joysticks && device->joysticks) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + } + } +} + +bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID) +{ + int i, j; + SDL_JoystickID joystickID; + + SDL_AssertJoysticksLocked(); + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + for (j = child->num_joysticks; j--;) { + HIDAPI_JoystickDisconnected(child, child->joysticks[j]); + } + } + + joystickID = SDL_GetNextObjectID(); + HIDAPI_AddJoystickInstanceToDevice(device, joystickID); + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + HIDAPI_AddJoystickInstanceToDevice(child, joystickID); + } + + ++SDL_HIDAPI_numjoysticks; + + SDL_PrivateJoystickAdded(joystickID); + + if (pJoystickID) { + *pJoystickID = joystickID; + } + return true; +} + +void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) +{ + int i, j; + + SDL_LockJoysticks(); + + if (!HIDAPI_JoystickInstanceIsUnique(device, joystickID)) { + // Disconnecting a child always disconnects the parent + device = device->parent; + } + + for (i = 0; i < device->num_joysticks; ++i) { + if (device->joysticks[i] == joystickID) { + SDL_Joystick *joystick = SDL_GetJoystickFromID(joystickID); + if (joystick) { + HIDAPI_JoystickClose(joystick); + } + + HIDAPI_DelJoystickInstanceFromDevice(device, joystickID); + + for (j = 0; j < device->num_children; ++j) { + SDL_HIDAPI_Device *child = device->children[j]; + HIDAPI_DelJoystickInstanceFromDevice(child, joystickID); + } + + --SDL_HIDAPI_numjoysticks; + + if (!shutting_down) { + SDL_PrivateJoystickRemoved(joystickID); + } + } + } + + // Rescan the device list in case device state has changed + SDL_HIDAPI_change_count = 0; + + SDL_UnlockJoysticks(); +} + +static void HIDAPI_UpdateJoystickProperties(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_PropertiesID props = SDL_GetJoystickProperties(joystick); + Uint32 caps = device->driver->GetJoystickCapabilities(device, joystick); + + if (caps & SDL_JOYSTICK_CAP_MONO_LED) { + SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_MONO_LED_BOOLEAN, true); + } else { + SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_MONO_LED_BOOLEAN, false); + } + if (caps & SDL_JOYSTICK_CAP_RGB_LED) { + SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true); + } else { + SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, false); + } + if (caps & SDL_JOYSTICK_CAP_PLAYER_LED) { + SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_PLAYER_LED_BOOLEAN, true); + } else { + SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_PLAYER_LED_BOOLEAN, false); + } + if (caps & SDL_JOYSTICK_CAP_RUMBLE) { + SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } else { + SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, false); + } + if (caps & SDL_JOYSTICK_CAP_TRIGGER_RUMBLE) { + SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); + } else { + SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, false); + } +} + +void HIDAPI_UpdateDeviceProperties(SDL_HIDAPI_Device *device) +{ + int i; + + SDL_LockJoysticks(); + + for (i = 0; i < device->num_joysticks; ++i) { + SDL_Joystick *joystick = SDL_GetJoystickFromID(device->joysticks[i]); + if (joystick) { + HIDAPI_UpdateJoystickProperties(device, joystick); + } + } + + SDL_UnlockJoysticks(); +} + +static int HIDAPI_JoystickGetCount(void) +{ + return SDL_HIDAPI_numjoysticks; +} + +static SDL_HIDAPI_Device *HIDAPI_AddDevice(const struct SDL_hid_device_info *info, int num_children, SDL_HIDAPI_Device **children) +{ + SDL_HIDAPI_Device *device; + SDL_HIDAPI_Device *curr, *last = NULL; + bool removed; + Uint16 bus; + + SDL_AssertJoysticksLocked(); + + for (curr = SDL_HIDAPI_devices, last = NULL; curr; last = curr, curr = curr->next) { + } + + device = (SDL_HIDAPI_Device *)SDL_calloc(1, sizeof(*device)); + if (!device) { + return NULL; + } + SDL_SetObjectValid(device, SDL_OBJECT_TYPE_HIDAPI_JOYSTICK, true); + device->path = SDL_strdup(info->path); + if (!device->path) { + SDL_free(device); + return NULL; + } + device->seen = true; + device->vendor_id = info->vendor_id; + device->product_id = info->product_id; + device->version = info->release_number; + device->interface_number = info->interface_number; + device->interface_class = info->interface_class; + device->interface_subclass = info->interface_subclass; + device->interface_protocol = info->interface_protocol; + device->usage_page = info->usage_page; + device->usage = info->usage; + device->is_bluetooth = (info->bus_type == SDL_HID_API_BUS_BLUETOOTH); + device->dev_lock = SDL_CreateMutex(); + + // Need the device name before getting the driver to know whether to ignore this device + { + char *serial_number = HIDAPI_ConvertString(info->serial_number); + + device->manufacturer_string = HIDAPI_ConvertString(info->manufacturer_string); + device->product_string = HIDAPI_ConvertString(info->product_string); + device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, device->manufacturer_string, device->product_string); + + if (serial_number && *serial_number) { + device->serial = serial_number; + } else { + SDL_free(serial_number); + } + + if (!device->name) { + SDL_free(device->manufacturer_string); + SDL_free(device->product_string); + SDL_free(device->serial); + SDL_free(device->path); + SDL_free(device); + return NULL; + } + } + + if (info->bus_type == SDL_HID_API_BUS_BLUETOOTH) { + bus = SDL_HARDWARE_BUS_BLUETOOTH; + } else { + bus = SDL_HARDWARE_BUS_USB; + } + device->guid = SDL_CreateJoystickGUID(bus, device->vendor_id, device->product_id, device->version, device->manufacturer_string, device->product_string, 'h', 0); + device->joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; + device->type = SDL_GetJoystickGameControllerProtocol(device->name, device->vendor_id, device->product_id, device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol); + device->steam_virtual_gamepad_slot = -1; + + if (num_children > 0) { + int i; + + device->num_children = num_children; + device->children = children; + for (i = 0; i < num_children; ++i) { + children[i]->parent = device; + } + } + + // Add it to the list + if (last) { + last->next = device; + } else { + SDL_HIDAPI_devices = device; + } + + removed = false; + HIDAPI_SetupDeviceDriver(device, &removed); + if (removed) { + return NULL; + } + + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Added HIDAPI device '%s' VID 0x%.4x, PID 0x%.4x, bluetooth %d, version %d, serial %s, interface %d, interface_class %d, interface_subclass %d, interface_protocol %d, usage page 0x%.4x, usage 0x%.4x, path = %s, driver = %s (%s)", device->name, device->vendor_id, device->product_id, device->is_bluetooth, device->version, + device->serial ? device->serial : "NONE", device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol, device->usage_page, device->usage, + device->path, device->driver ? device->driver->name : "NONE", device->driver && device->driver->enabled ? "ENABLED" : "DISABLED"); + + return device; +} + +static void HIDAPI_DelDevice(SDL_HIDAPI_Device *device) +{ + SDL_HIDAPI_Device *curr, *last; + int i; + + SDL_AssertJoysticksLocked(); + + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Removing HIDAPI device '%s' VID 0x%.4x, PID 0x%.4x, bluetooth %d, version %d, serial %s, interface %d, interface_class %d, interface_subclass %d, interface_protocol %d, usage page 0x%.4x, usage 0x%.4x, path = %s, driver = %s (%s)", device->name, device->vendor_id, device->product_id, device->is_bluetooth, device->version, + device->serial ? device->serial : "NONE", device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol, device->usage_page, device->usage, + device->path, device->driver ? device->driver->name : "NONE", device->driver && device->driver->enabled ? "ENABLED" : "DISABLED"); + + for (curr = SDL_HIDAPI_devices, last = NULL; curr; last = curr, curr = curr->next) { + if (curr == device) { + if (last) { + last->next = curr->next; + } else { + SDL_HIDAPI_devices = curr->next; + } + + HIDAPI_CleanupDeviceDriver(device); + + // Make sure the rumble thread is done with this device + while (SDL_GetAtomicInt(&device->rumble_pending) > 0) { + SDL_Delay(10); + } + + for (i = 0; i < device->num_children; ++i) { + device->children[i]->parent = NULL; + } + + SDL_SetObjectValid(device, SDL_OBJECT_TYPE_HIDAPI_JOYSTICK, false); + SDL_DestroyMutex(device->dev_lock); + SDL_free(device->manufacturer_string); + SDL_free(device->product_string); + SDL_free(device->serial); + SDL_free(device->name); + SDL_free(device->path); + SDL_free(device->children); + SDL_free(device); + return; + } + } +} + +static bool HIDAPI_CreateCombinedJoyCons(void) +{ + SDL_HIDAPI_Device *device, *combined; + SDL_HIDAPI_Device *joycons[2] = { NULL, NULL }; + + SDL_AssertJoysticksLocked(); + + if (!SDL_HIDAPI_combine_joycons) { + return false; + } + + for (device = SDL_HIDAPI_devices; device; device = device->next) { + Uint16 vendor, product; + + if (!device->driver) { + // Unsupported device + continue; + } + if (device->parent) { + // This device is already part of a combined device + continue; + } + if (device->broken) { + // This device can't be used + continue; + } + + SDL_GetJoystickGUIDInfo(device->guid, &vendor, &product, NULL, NULL); + + if (!joycons[0] && + (SDL_IsJoystickNintendoSwitchJoyConLeft(vendor, product) || + (SDL_IsJoystickNintendoSwitchJoyConGrip(vendor, product) && + SDL_strstr(device->name, "(L)") != NULL))) { + joycons[0] = device; + } + if (!joycons[1] && + (SDL_IsJoystickNintendoSwitchJoyConRight(vendor, product) || + (SDL_IsJoystickNintendoSwitchJoyConGrip(vendor, product) && + SDL_strstr(device->name, "(R)") != NULL))) { + joycons[1] = device; + } + if (joycons[0] && joycons[1]) { + SDL_hid_device_info info; + SDL_HIDAPI_Device **children = (SDL_HIDAPI_Device **)SDL_malloc(2 * sizeof(SDL_HIDAPI_Device *)); + if (!children) { + return false; + } + children[0] = joycons[0]; + children[1] = joycons[1]; + + SDL_zero(info); + info.path = "nintendo_joycons_combined"; + info.vendor_id = USB_VENDOR_NINTENDO; + info.product_id = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; + info.interface_number = -1; + info.usage_page = USB_USAGEPAGE_GENERIC_DESKTOP; + info.usage = USB_USAGE_GENERIC_GAMEPAD; + info.manufacturer_string = L"Nintendo"; + info.product_string = L"Switch Joy-Con (L/R)"; + + combined = HIDAPI_AddDevice(&info, 2, children); + if (combined && combined->driver) { + return true; + } else { + if (combined) { + HIDAPI_DelDevice(combined); + } else { + SDL_free(children); + } + return false; + } + } + } + return false; +} + +static void HIDAPI_UpdateDeviceList(void) +{ + SDL_HIDAPI_Device *device; + struct SDL_hid_device_info *devs, *info; + + SDL_LockJoysticks(); + + if (SDL_HIDAPI_hints_changed) { + SDL_HIDAPI_UpdateDrivers(); + SDL_HIDAPI_hints_changed = false; + } + + // Prepare the existing device list + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (device->children) { + continue; + } + device->seen = false; + } + + // Enumerate the devices + if (SDL_HIDAPI_numdrivers > 0) { + devs = SDL_hid_enumerate(0, 0); + if (devs) { + for (info = devs; info; info = info->next) { + device = HIDAPI_GetJoystickByInfo(info->path, info->vendor_id, info->product_id); + if (device) { + device->seen = true; + + // Check to see if the serial number is available now + if(HIDAPI_SerialIsEmpty(device)) { + HIDAPI_SetDeviceSerialW(device, info->serial_number); + } + } else { + HIDAPI_AddDevice(info, 0, NULL); + } + } + SDL_hid_free_enumeration(devs); + } + } + + // Remove any devices that weren't seen or have been disconnected due to read errors +check_removed: + device = SDL_HIDAPI_devices; + while (device) { + SDL_HIDAPI_Device *next = device->next; + + if (!device->seen || + ((device->driver || device->children) && device->num_joysticks == 0 && !device->dev)) { + if (device->parent) { + // When a child device goes away, so does the parent + int i; + device = device->parent; + for (i = 0; i < device->num_children; ++i) { + HIDAPI_DelDevice(device->children[i]); + } + HIDAPI_DelDevice(device); + + // Update the device list again to pick up any children left + SDL_HIDAPI_change_count = 0; + + // We deleted more than one device here, restart the loop + goto check_removed; + } else { + HIDAPI_DelDevice(device); + device = NULL; + + // Update the device list again in case this device comes back + SDL_HIDAPI_change_count = 0; + } + } + if (device && device->broken && device->parent) { + HIDAPI_DelDevice(device->parent); + + // We deleted a different device here, restart the loop + goto check_removed; + } + device = next; + } + + // See if we can create any combined Joy-Con controllers + while (HIDAPI_CreateCombinedJoyCons()) { + } + + SDL_UnlockJoysticks(); +} + +static bool HIDAPI_IsEquivalentToDevice(Uint16 vendor_id, Uint16 product_id, SDL_HIDAPI_Device *device) +{ + if (vendor_id == device->vendor_id && product_id == device->product_id) { + return true; + } + + if (vendor_id == USB_VENDOR_MICROSOFT) { + // If we're looking for the wireless XBox 360 controller, also look for the dongle + if (product_id == USB_PRODUCT_XBOX360_XUSB_CONTROLLER && device->product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER) { + return true; + } + + // If we're looking for the raw input Xbox One controller, match it against any other Xbox One controller + if (product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER && + device->type == SDL_GAMEPAD_TYPE_XBOXONE) { + return true; + } + + // If we're looking for an XInput controller, match it against any other Xbox controller + if (product_id == USB_PRODUCT_XBOX360_XUSB_CONTROLLER) { + if (device->type == SDL_GAMEPAD_TYPE_XBOX360 || device->type == SDL_GAMEPAD_TYPE_XBOXONE) { + return true; + } + } + } + + if (vendor_id == USB_VENDOR_NVIDIA) { + // If we're looking for the NVIDIA SHIELD controller Xbox interface, match it against any NVIDIA SHIELD controller + if (product_id == 0xb400 && + SDL_IsJoystickNVIDIASHIELDController(vendor_id, product_id)) { + return true; + } + } + return false; +} + +static bool HIDAPI_StartUpdatingDevices(void) +{ + return SDL_CompareAndSwapAtomicInt(&SDL_HIDAPI_updating_devices, false, true); +} + +static void HIDAPI_FinishUpdatingDevices(void) +{ + SDL_SetAtomicInt(&SDL_HIDAPI_updating_devices, false); +} + +bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type) +{ + SDL_HIDAPI_Device *device; + bool result = false; + + // Make sure we're initialized, as this could be called from other drivers during startup + if (!HIDAPI_JoystickInit()) { + return false; + } + + if (HIDAPI_StartUpdatingDevices()) { + HIDAPI_UpdateDeviceList(); + HIDAPI_FinishUpdatingDevices(); + } + + SDL_LockJoysticks(); + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (device->driver && device->type == type) { + result = true; + break; + } + } + SDL_UnlockJoysticks(); + +#ifdef DEBUG_HIDAPI + SDL_Log("HIDAPI_IsDeviceTypePresent() returning %s for %d", result ? "true" : "false", type); +#endif + return result; +} + +bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + SDL_HIDAPI_Device *device; + bool supported = false; + bool result = false; + + // Make sure we're initialized, as this could be called from other drivers during startup + if (!HIDAPI_JoystickInit()) { + return false; + } + + /* Only update the device list for devices we know might be supported. + If we did this for every device, it would hit the USB driver too hard and potentially + lock up the system. This won't catch devices that we support but can only detect using + USB interface details, like Xbox controllers, but hopefully the device list update is + responsive enough to catch those. + */ + supported = HIDAPI_IsDeviceSupported(vendor_id, product_id, version, name); +#if defined(SDL_JOYSTICK_HIDAPI_XBOX360) || defined(SDL_JOYSTICK_HIDAPI_XBOXONE) + if (!supported && + (SDL_strstr(name, "Xbox") || SDL_strstr(name, "X-Box") || SDL_strstr(name, "XBOX"))) { + supported = true; + } +#endif // SDL_JOYSTICK_HIDAPI_XBOX360 || SDL_JOYSTICK_HIDAPI_XBOXONE + if (supported) { + if (HIDAPI_StartUpdatingDevices()) { + HIDAPI_UpdateDeviceList(); + HIDAPI_FinishUpdatingDevices(); + } + } + + /* Note that this isn't a perfect check - there may be multiple devices with 0 VID/PID, + or a different name than we have it listed here, etc, but if we support the device + and we have something similar in our device list, mark it as present. + */ + SDL_LockJoysticks(); + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (device->driver && + HIDAPI_IsEquivalentToDevice(vendor_id, product_id, device)) { + result = true; + break; + } + } + SDL_UnlockJoysticks(); + +#ifdef DEBUG_HIDAPI + SDL_Log("HIDAPI_IsDevicePresent() returning %s for 0x%.4x / 0x%.4x", result ? "true" : "false", vendor_id, product_id); +#endif + return result; +} + +char *HIDAPI_GetDeviceProductName(Uint16 vendor_id, Uint16 product_id) +{ + SDL_HIDAPI_Device *device; + char *name = NULL; + + SDL_LockJoysticks(); + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (vendor_id == device->vendor_id && product_id == device->product_id) { + if (device->product_string) { + name = SDL_strdup(device->product_string); + } + break; + } + } + SDL_UnlockJoysticks(); + + return name; +} + +char *HIDAPI_GetDeviceManufacturerName(Uint16 vendor_id, Uint16 product_id) +{ + SDL_HIDAPI_Device *device; + char *name = NULL; + + SDL_LockJoysticks(); + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (vendor_id == device->vendor_id && product_id == device->product_id) { + if (device->manufacturer_string) { + name = SDL_strdup(device->manufacturer_string); + } + break; + } + } + SDL_UnlockJoysticks(); + + return name; +} + +SDL_JoystickType HIDAPI_GetJoystickTypeFromGUID(SDL_GUID guid) +{ + SDL_HIDAPI_Device *device; + SDL_JoystickType type = SDL_JOYSTICK_TYPE_UNKNOWN; + + SDL_LockJoysticks(); + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (SDL_memcmp(&guid, &device->guid, sizeof(guid)) == 0) { + type = device->joystick_type; + break; + } + } + SDL_UnlockJoysticks(); + + return type; +} + +SDL_GamepadType HIDAPI_GetGamepadTypeFromGUID(SDL_GUID guid) +{ + SDL_HIDAPI_Device *device; + SDL_GamepadType type = SDL_GAMEPAD_TYPE_STANDARD; + + SDL_LockJoysticks(); + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (SDL_memcmp(&guid, &device->guid, sizeof(guid)) == 0) { + type = device->type; + break; + } + } + SDL_UnlockJoysticks(); + + return type; +} + +static void HIDAPI_JoystickDetect(void) +{ + if (HIDAPI_StartUpdatingDevices()) { + Uint32 count = SDL_hid_device_change_count(); + if (SDL_HIDAPI_change_count != count) { + SDL_HIDAPI_change_count = count; + HIDAPI_UpdateDeviceList(); + } + HIDAPI_FinishUpdatingDevices(); + } +} + +void HIDAPI_UpdateDevices(void) +{ + SDL_HIDAPI_Device *device; + + SDL_AssertJoysticksLocked(); + + // Update the devices, which may change connected joysticks and send events + + // Prepare the existing device list + if (HIDAPI_StartUpdatingDevices()) { + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (device->parent) { + continue; + } + if (device->driver) { + if (SDL_TryLockMutex(device->dev_lock)) { + device->updating = true; + device->driver->UpdateDevice(device); + device->updating = false; + SDL_UnlockMutex(device->dev_lock); + } + } + } + HIDAPI_FinishUpdatingDevices(); + } +} + +static const char *HIDAPI_JoystickGetDeviceName(int device_index) +{ + SDL_HIDAPI_Device *device; + const char *name = NULL; + + device = HIDAPI_GetDeviceByIndex(device_index, NULL); + if (device) { + // FIXME: The device could be freed after this name is returned... + name = device->name; + } + + return name; +} + +static const char *HIDAPI_JoystickGetDevicePath(int device_index) +{ + SDL_HIDAPI_Device *device; + const char *path = NULL; + + device = HIDAPI_GetDeviceByIndex(device_index, NULL); + if (device) { + // FIXME: The device could be freed after this path is returned... + path = device->path; + } + + return path; +} + +static int HIDAPI_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + SDL_HIDAPI_Device *device; + + device = HIDAPI_GetDeviceByIndex(device_index, NULL); + if (device) { + return device->steam_virtual_gamepad_slot; + } + return -1; +} + +static int HIDAPI_JoystickGetDevicePlayerIndex(int device_index) +{ + SDL_HIDAPI_Device *device; + SDL_JoystickID instance_id; + int player_index = -1; + + device = HIDAPI_GetDeviceByIndex(device_index, &instance_id); + if (device) { + player_index = device->driver->GetDevicePlayerIndex(device, instance_id); + } + + return player_index; +} + +static void HIDAPI_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ + SDL_HIDAPI_Device *device; + SDL_JoystickID instance_id; + + device = HIDAPI_GetDeviceByIndex(device_index, &instance_id); + if (device) { + device->driver->SetDevicePlayerIndex(device, instance_id, player_index); + } +} + +static SDL_GUID HIDAPI_JoystickGetDeviceGUID(int device_index) +{ + SDL_HIDAPI_Device *device; + SDL_GUID guid; + + device = HIDAPI_GetDeviceByIndex(device_index, NULL); + if (device) { + SDL_memcpy(&guid, &device->guid, sizeof(guid)); + } else { + SDL_zero(guid); + } + + return guid; +} + +static SDL_JoystickID HIDAPI_JoystickGetDeviceInstanceID(int device_index) +{ + SDL_JoystickID joystickID = 0; + HIDAPI_GetDeviceByIndex(device_index, &joystickID); + return joystickID; +} + +static bool HIDAPI_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + SDL_JoystickID joystickID = 0; + SDL_HIDAPI_Device *device = HIDAPI_GetDeviceByIndex(device_index, &joystickID); + struct joystick_hwdata *hwdata; + + SDL_AssertJoysticksLocked(); + + if (!device || !device->driver || device->broken) { + // This should never happen - validated before being called + return SDL_SetError("Couldn't find HIDAPI device at index %d", device_index); + } + + hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata)); + if (!hwdata) { + return false; + } + hwdata->device = device; + + // Process any pending reports before opening the device + SDL_LockMutex(device->dev_lock); + device->updating = true; + device->driver->UpdateDevice(device); + device->updating = false; + SDL_UnlockMutex(device->dev_lock); + + // UpdateDevice() may have called HIDAPI_JoystickDisconnected() if the device went away + if (device->num_joysticks == 0) { + SDL_free(hwdata); + return SDL_SetError("HIDAPI device disconnected while opening"); + } + + // Set the default connection state, can be overridden below + if (device->is_bluetooth) { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + } else { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; + } + + if (!device->driver->OpenJoystick(device, joystick)) { + // The open failed, mark this device as disconnected and update devices + HIDAPI_JoystickDisconnected(device, joystickID); + SDL_free(hwdata); + return false; + } + + HIDAPI_UpdateJoystickProperties(device, joystick); + + if (device->serial) { + joystick->serial = SDL_strdup(device->serial); + } + + joystick->hwdata = hwdata; + return true; +} + +static bool HIDAPI_GetJoystickDevice(SDL_Joystick *joystick, SDL_HIDAPI_Device **device) +{ + SDL_AssertJoysticksLocked(); + + if (joystick && joystick->hwdata) { + *device = joystick->hwdata->device; + if (SDL_ObjectValid(*device, SDL_OBJECT_TYPE_HIDAPI_JOYSTICK) && (*device)->driver != NULL) { + return true; + } + } + return false; +} + +static bool HIDAPI_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + bool result; + SDL_HIDAPI_Device *device = NULL; + + if (HIDAPI_GetJoystickDevice(joystick, &device)) { + result = device->driver->RumbleJoystick(device, joystick, low_frequency_rumble, high_frequency_rumble); + } else { + result = SDL_SetError("Rumble failed, device disconnected"); + } + + return result; +} + +static bool HIDAPI_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + bool result; + SDL_HIDAPI_Device *device = NULL; + + if (HIDAPI_GetJoystickDevice(joystick, &device)) { + result = device->driver->RumbleJoystickTriggers(device, joystick, left_rumble, right_rumble); + } else { + result = SDL_SetError("Rumble failed, device disconnected"); + } + + return result; +} + +static bool HIDAPI_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + bool result; + SDL_HIDAPI_Device *device = NULL; + + if (HIDAPI_GetJoystickDevice(joystick, &device)) { + result = device->driver->SetJoystickLED(device, joystick, red, green, blue); + } else { + result = SDL_SetError("SetLED failed, device disconnected"); + } + + return result; +} + +static bool HIDAPI_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + bool result; + SDL_HIDAPI_Device *device = NULL; + + if (HIDAPI_GetJoystickDevice(joystick, &device)) { + result = device->driver->SendJoystickEffect(device, joystick, data, size); + } else { + result = SDL_SetError("SendEffect failed, device disconnected"); + } + + return result; +} + +static bool HIDAPI_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + bool result; + SDL_HIDAPI_Device *device = NULL; + + if (HIDAPI_GetJoystickDevice(joystick, &device)) { + result = device->driver->SetJoystickSensorsEnabled(device, joystick, enabled); + } else { + result = SDL_SetError("SetSensorsEnabled failed, device disconnected"); + } + + return result; +} + +static void HIDAPI_JoystickUpdate(SDL_Joystick *joystick) +{ + // This is handled in SDL_HIDAPI_UpdateDevices() +} + +static void HIDAPI_JoystickClose(SDL_Joystick *joystick) SDL_NO_THREAD_SAFETY_ANALYSIS // We unlock the device lock so rumble can complete +{ + SDL_AssertJoysticksLocked(); + + if (joystick->hwdata) { + SDL_HIDAPI_Device *device = joystick->hwdata->device; + int i; + + // Wait up to 30 ms for pending rumble to complete + if (device->updating) { + // Unlock the device so rumble can complete + SDL_UnlockMutex(device->dev_lock); + } + for (i = 0; i < 3; ++i) { + if (SDL_GetAtomicInt(&device->rumble_pending) > 0) { + SDL_Delay(10); + } + } + if (device->updating) { + // Relock the device + SDL_LockMutex(device->dev_lock); + } + + device->driver->CloseJoystick(device, joystick); + + SDL_free(joystick->hwdata); + joystick->hwdata = NULL; + } +} + +static void HIDAPI_JoystickQuit(void) +{ + int i; + + SDL_AssertJoysticksLocked(); + + shutting_down = true; + + SDL_HIDAPI_QuitRumble(); + + while (SDL_HIDAPI_devices) { + SDL_HIDAPI_Device *device = SDL_HIDAPI_devices; + if (device->parent) { + // When a child device goes away, so does the parent + device = device->parent; + for (i = 0; i < device->num_children; ++i) { + HIDAPI_DelDevice(device->children[i]); + } + HIDAPI_DelDevice(device); + } else { + HIDAPI_DelDevice(device); + } + } + + // Make sure the drivers cleaned up properly + SDL_assert(SDL_HIDAPI_numjoysticks == 0); + + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; + driver->UnregisterHints(SDL_HIDAPIDriverHintChanged, driver); + } + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS, + SDL_HIDAPIDriverHintChanged, NULL); + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI, + SDL_HIDAPIDriverHintChanged, NULL); + + SDL_hid_exit(); + + SDL_HIDAPI_change_count = 0; + shutting_down = false; + initialized = false; +} + +static bool HIDAPI_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +SDL_JoystickDriver SDL_HIDAPI_JoystickDriver = { + HIDAPI_JoystickInit, + HIDAPI_JoystickGetCount, + HIDAPI_JoystickDetect, + HIDAPI_IsDevicePresent, + HIDAPI_JoystickGetDeviceName, + HIDAPI_JoystickGetDevicePath, + HIDAPI_JoystickGetDeviceSteamVirtualGamepadSlot, + HIDAPI_JoystickGetDevicePlayerIndex, + HIDAPI_JoystickSetDevicePlayerIndex, + HIDAPI_JoystickGetDeviceGUID, + HIDAPI_JoystickGetDeviceInstanceID, + HIDAPI_JoystickOpen, + HIDAPI_JoystickRumble, + HIDAPI_JoystickRumbleTriggers, + HIDAPI_JoystickSetLED, + HIDAPI_JoystickSendEffect, + HIDAPI_JoystickSetSensorsEnabled, + HIDAPI_JoystickUpdate, + HIDAPI_JoystickClose, + HIDAPI_JoystickQuit, + HIDAPI_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick_c.h b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick_c.h new file mode 100644 index 0000000..9cd9f40 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -0,0 +1,195 @@ +/* + 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" + +#ifndef SDL_JOYSTICK_HIDAPI_H +#define SDL_JOYSTICK_HIDAPI_H + +#include "../usb_ids.h" + +// This is the full set of HIDAPI drivers available +#define SDL_JOYSTICK_HIDAPI_GAMECUBE +#define SDL_JOYSTICK_HIDAPI_LUNA +#define SDL_JOYSTICK_HIDAPI_PS3 +#define SDL_JOYSTICK_HIDAPI_PS4 +#define SDL_JOYSTICK_HIDAPI_PS5 +#define SDL_JOYSTICK_HIDAPI_STADIA +#define SDL_JOYSTICK_HIDAPI_STEAM +#define SDL_JOYSTICK_HIDAPI_STEAMDECK +#define SDL_JOYSTICK_HIDAPI_SWITCH +#define SDL_JOYSTICK_HIDAPI_WII +#define SDL_JOYSTICK_HIDAPI_XBOX360 +#define SDL_JOYSTICK_HIDAPI_XBOXONE +#define SDL_JOYSTICK_HIDAPI_SHIELD +#define SDL_JOYSTICK_HIDAPI_STEAM_HORI + +// Joystick capability definitions +#define SDL_JOYSTICK_CAP_MONO_LED 0x00000001 +#define SDL_JOYSTICK_CAP_RGB_LED 0x00000002 +#define SDL_JOYSTICK_CAP_PLAYER_LED 0x00000004 +#define SDL_JOYSTICK_CAP_RUMBLE 0x00000010 +#define SDL_JOYSTICK_CAP_TRIGGER_RUMBLE 0x00000020 + +// Whether HIDAPI is enabled by default +#if defined(SDL_PLATFORM_ANDROID) || \ + defined(SDL_PLATFORM_IOS) || \ + defined(SDL_PLATFORM_TVOS) || \ + defined(SDL_PLATFORM_VISIONOS) +// On Android, HIDAPI prompts for permissions and acquires exclusive access to the device, and on Apple mobile platforms it doesn't do anything except for handling Bluetooth Steam Controllers, so we'll leave it off by default. +#define SDL_HIDAPI_DEFAULT false +#else +#define SDL_HIDAPI_DEFAULT true +#endif + +// The maximum size of a USB packet for HID devices +#define USB_PACKET_LENGTH 64 + +// Forward declaration +struct SDL_HIDAPI_DeviceDriver; + +typedef struct SDL_HIDAPI_Device +{ + char *name; + char *manufacturer_string; + char *product_string; + char *path; + Uint16 vendor_id; + Uint16 product_id; + Uint16 version; + char *serial; + SDL_GUID guid; + int interface_number; // Available on Windows and Linux + int interface_class; + int interface_subclass; + int interface_protocol; + Uint16 usage_page; // Available on Windows and macOS + Uint16 usage; // Available on Windows and macOS + bool is_bluetooth; + SDL_JoystickType joystick_type; + SDL_GamepadType type; + int steam_virtual_gamepad_slot; + + struct SDL_HIDAPI_DeviceDriver *driver; + void *context; + SDL_Mutex *dev_lock; + SDL_hid_device *dev; + SDL_AtomicInt rumble_pending; + int num_joysticks; + SDL_JoystickID *joysticks; + + // Used during scanning for device changes + bool seen; + + // Used to flag that the device is being updated + bool updating; + + // Used to flag devices that failed open + // This can happen on Windows with Bluetooth devices that have turned off + bool broken; + + struct SDL_HIDAPI_Device *parent; + int num_children; + struct SDL_HIDAPI_Device **children; + + struct SDL_HIDAPI_Device *next; +} SDL_HIDAPI_Device; + +typedef struct SDL_HIDAPI_DeviceDriver +{ + const char *name; + bool enabled; + void (*RegisterHints)(SDL_HintCallback callback, void *userdata); + void (*UnregisterHints)(SDL_HintCallback callback, void *userdata); + bool (*IsEnabled)(void); + bool (*IsSupportedDevice)(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol); + bool (*InitDevice)(SDL_HIDAPI_Device *device); + int (*GetDevicePlayerIndex)(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id); + void (*SetDevicePlayerIndex)(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index); + bool (*UpdateDevice)(SDL_HIDAPI_Device *device); + bool (*OpenJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick); + bool (*RumbleJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); + bool (*RumbleJoystickTriggers)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble); + Uint32 (*GetJoystickCapabilities)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick); + bool (*SetJoystickLED)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue); + bool (*SendJoystickEffect)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size); + bool (*SetJoystickSensorsEnabled)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled); + void (*CloseJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick); + void (*FreeDevice)(SDL_HIDAPI_Device *device); + +} SDL_HIDAPI_DeviceDriver; + +// HIDAPI device support +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3ThirdParty; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3SonySixaxis; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori; + +// Return true if a HID device is present and supported as a joystick of the given type +extern bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type); + +// Return true if a HID device is present and supported as a joystick +extern bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name); + +// Return the name of a connected device, which should be freed with SDL_free(), or NULL if it's not available +extern char *HIDAPI_GetDeviceProductName(Uint16 vendor_id, Uint16 product_id); + +// Return the manufacturer of a connected device, which should be freed with SDL_free(), or NULL if it's not available +extern char *HIDAPI_GetDeviceManufacturerName(Uint16 vendor_id, Uint16 product_id); + +// Return the type of a joystick if it's present and supported +extern SDL_JoystickType HIDAPI_GetJoystickTypeFromGUID(SDL_GUID guid); + +// Return the type of a game controller if it's present and supported +extern SDL_GamepadType HIDAPI_GetGamepadTypeFromGUID(SDL_GUID guid); + +extern void HIDAPI_UpdateDevices(void); +extern void HIDAPI_SetDeviceName(SDL_HIDAPI_Device *device, const char *name); +extern void HIDAPI_SetDeviceProduct(SDL_HIDAPI_Device *device, Uint16 vendor_id, Uint16 product_id); +extern void HIDAPI_SetDeviceSerial(SDL_HIDAPI_Device *device, const char *serial); +extern bool HIDAPI_HasConnectedUSBDevice(const char *serial); +extern void HIDAPI_DisconnectBluetoothDevice(const char *serial); +extern bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID); +extern void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID); +extern void HIDAPI_UpdateDeviceProperties(SDL_HIDAPI_Device *device); + +extern void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size); + +extern bool HIDAPI_SupportsPlaystationDetection(Uint16 vendor, Uint16 product); + +extern float HIDAPI_RemapVal(float val, float val_min, float val_max, float output_min, float output_max); + +#endif // SDL_JOYSTICK_HIDAPI_H diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_constants.h b/contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_constants.h new file mode 100644 index 0000000..78af016 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_constants.h @@ -0,0 +1,582 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2021 Valve Corporation + + 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. +*/ + +#ifndef _CONTROLLER_CONSTANTS_ +#define _CONTROLLER_CONSTANTS_ + +#include "controller_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FEATURE_REPORT_SIZE 64 + +#define VALVE_USB_VID 0x28DE + +// Frame update rate (in ms). +#define FAST_SCAN_INTERVAL 6 +#define SLOW_SCAN_INTERVAL 9 + +// Contains each of the USB PIDs for Valve controllers (only add to this enum and never change the order) +enum ValveControllerPID +{ + BASTILLE_PID = 0x2202, + CHELL_PID = 0x1101, + D0G_PID = 0x1102, + ELI_PID = 0x1103, + FREEMAN_PID = 0x1104, + D0G_BLE_PID = 0x1105, + D0G_BLE2_PID = 0x1106, + D0GGLE_PID = 0x1142, + + JUPITER_PID = 0x1205, +}; + +// This enum contains all of the messages exchanged between the host and the target (only add to this enum and never change the order) +enum FeatureReportMessageIDs +{ + ID_SET_DIGITAL_MAPPINGS = 0x80, + ID_CLEAR_DIGITAL_MAPPINGS = 0x81, + ID_GET_DIGITAL_MAPPINGS = 0x82, + ID_GET_ATTRIBUTES_VALUES = 0x83, + ID_GET_ATTRIBUTE_LABEL = 0x84, + ID_SET_DEFAULT_DIGITAL_MAPPINGS = 0x85, + ID_FACTORY_RESET = 0x86, + ID_SET_SETTINGS_VALUES = 0x87, + ID_CLEAR_SETTINGS_VALUES = 0x88, + ID_GET_SETTINGS_VALUES = 0x89, + ID_GET_SETTING_LABEL = 0x8A, + ID_GET_SETTINGS_MAXS = 0x8B, + ID_GET_SETTINGS_DEFAULTS = 0x8C, + ID_SET_CONTROLLER_MODE = 0x8D, + ID_LOAD_DEFAULT_SETTINGS = 0x8E, + ID_TRIGGER_HAPTIC_PULSE = 0x8F, + + ID_TURN_OFF_CONTROLLER = 0x9F, + + ID_GET_DEVICE_INFO = 0xA1, + + ID_CALIBRATE_TRACKPADS = 0xA7, + ID_RESERVED_0 = 0xA8, + ID_SET_SERIAL_NUMBER = 0xA9, + ID_GET_TRACKPAD_CALIBRATION = 0xAA, + ID_GET_TRACKPAD_FACTORY_CALIBRATION = 0xAB, + ID_GET_TRACKPAD_RAW_DATA = 0xAC, + ID_ENABLE_PAIRING = 0xAD, + ID_GET_STRING_ATTRIBUTE = 0xAE, + ID_RADIO_ERASE_RECORDS = 0xAF, + ID_RADIO_WRITE_RECORD = 0xB0, + ID_SET_DONGLE_SETTING = 0xB1, + ID_DONGLE_DISCONNECT_DEVICE = 0xB2, + ID_DONGLE_COMMIT_DEVICE = 0xB3, + ID_DONGLE_GET_WIRELESS_STATE = 0xB4, + ID_CALIBRATE_GYRO = 0xB5, + ID_PLAY_AUDIO = 0xB6, + ID_AUDIO_UPDATE_START = 0xB7, + ID_AUDIO_UPDATE_DATA = 0xB8, + ID_AUDIO_UPDATE_COMPLETE = 0xB9, + ID_GET_CHIPID = 0xBA, + + ID_CALIBRATE_JOYSTICK = 0xBF, + ID_CALIBRATE_ANALOG_TRIGGERS = 0xC0, + ID_SET_AUDIO_MAPPING = 0xC1, + ID_CHECK_GYRO_FW_LOAD = 0xC2, + ID_CALIBRATE_ANALOG = 0xC3, + ID_DONGLE_GET_CONNECTED_SLOTS = 0xC4, + + ID_RESET_IMU = 0xCE, + + // Deck only + ID_TRIGGER_HAPTIC_CMD = 0xEA, + ID_TRIGGER_RUMBLE_CMD = 0xEB, +}; + + +// Enumeration of all wireless dongle events +typedef enum WirelessEventTypes +{ + WIRELESS_EVENT_DISCONNECT = 1, + WIRELESS_EVENT_CONNECT = 2, + WIRELESS_EVENT_PAIR = 3, +} EWirelessEventType; + + +// Enumeration of generic digital inputs - not all of these will be supported on all controllers (only add to this enum and never change the order) +typedef enum +{ + IO_DIGITAL_BUTTON_NONE = -1, + IO_DIGITAL_BUTTON_RIGHT_TRIGGER, + IO_DIGITAL_BUTTON_LEFT_TRIGGER, + IO_DIGITAL_BUTTON_1, + IO_DIGITAL_BUTTON_Y=IO_DIGITAL_BUTTON_1, + IO_DIGITAL_BUTTON_2, + IO_DIGITAL_BUTTON_B=IO_DIGITAL_BUTTON_2, + IO_DIGITAL_BUTTON_3, + IO_DIGITAL_BUTTON_X=IO_DIGITAL_BUTTON_3, + IO_DIGITAL_BUTTON_4, + IO_DIGITAL_BUTTON_A=IO_DIGITAL_BUTTON_4, + IO_DIGITAL_BUTTON_RIGHT_BUMPER, + IO_DIGITAL_BUTTON_LEFT_BUMPER, + IO_DIGITAL_BUTTON_LEFT_JOYSTICK_CLICK, + IO_DIGITAL_BUTTON_ESCAPE, + IO_DIGITAL_BUTTON_STEAM, + IO_DIGITAL_BUTTON_MENU, + IO_DIGITAL_STICK_UP, + IO_DIGITAL_STICK_DOWN, + IO_DIGITAL_STICK_LEFT, + IO_DIGITAL_STICK_RIGHT, + IO_DIGITAL_TOUCH_1, + IO_DIGITAL_BUTTON_UP=IO_DIGITAL_TOUCH_1, + IO_DIGITAL_TOUCH_2, + IO_DIGITAL_BUTTON_RIGHT=IO_DIGITAL_TOUCH_2, + IO_DIGITAL_TOUCH_3, + IO_DIGITAL_BUTTON_LEFT=IO_DIGITAL_TOUCH_3, + IO_DIGITAL_TOUCH_4, + IO_DIGITAL_BUTTON_DOWN=IO_DIGITAL_TOUCH_4, + IO_DIGITAL_BUTTON_BACK_LEFT, + IO_DIGITAL_BUTTON_BACK_RIGHT, + IO_DIGITAL_LEFT_TRACKPAD_N, + IO_DIGITAL_LEFT_TRACKPAD_NE, + IO_DIGITAL_LEFT_TRACKPAD_E, + IO_DIGITAL_LEFT_TRACKPAD_SE, + IO_DIGITAL_LEFT_TRACKPAD_S, + IO_DIGITAL_LEFT_TRACKPAD_SW, + IO_DIGITAL_LEFT_TRACKPAD_W, + IO_DIGITAL_LEFT_TRACKPAD_NW, + IO_DIGITAL_RIGHT_TRACKPAD_N, + IO_DIGITAL_RIGHT_TRACKPAD_NE, + IO_DIGITAL_RIGHT_TRACKPAD_E, + IO_DIGITAL_RIGHT_TRACKPAD_SE, + IO_DIGITAL_RIGHT_TRACKPAD_S, + IO_DIGITAL_RIGHT_TRACKPAD_SW, + IO_DIGITAL_RIGHT_TRACKPAD_W, + IO_DIGITAL_RIGHT_TRACKPAD_NW, + IO_DIGITAL_LEFT_TRACKPAD_DOUBLE_TAP, + IO_DIGITAL_RIGHT_TRACKPAD_DOUBLE_TAP, + IO_DIGITAL_LEFT_TRACKPAD_OUTER_RADIUS, + IO_DIGITAL_RIGHT_TRACKPAD_OUTER_RADIUS, + IO_DIGITAL_LEFT_TRACKPAD_CLICK, + IO_DIGITAL_RIGHT_TRACKPAD_CLICK, + IO_DIGITAL_BATTERY_LOW, + IO_DIGITAL_LEFT_TRIGGER_THRESHOLD, + IO_DIGITAL_RIGHT_TRIGGER_THRESHOLD, + IO_DIGITAL_BUTTON_BACK_LEFT2, + IO_DIGITAL_BUTTON_BACK_RIGHT2, + IO_DIGITAL_BUTTON_ALWAYS_ON, + IO_DIGITAL_BUTTON_ANCILLARY_1, + IO_DIGITAL_BUTTON_MACRO_0, + IO_DIGITAL_BUTTON_MACRO_1, + IO_DIGITAL_BUTTON_MACRO_2, + IO_DIGITAL_BUTTON_MACRO_3, + IO_DIGITAL_BUTTON_MACRO_4, + IO_DIGITAL_BUTTON_MACRO_5, + IO_DIGITAL_BUTTON_MACRO_6, + IO_DIGITAL_BUTTON_MACRO_7, + IO_DIGITAL_BUTTON_MACRO_1FINGER, + IO_DIGITAL_BUTTON_MACRO_2FINGER, + IO_DIGITAL_COUNT +} DigitalIO ; + +// Enumeration of generic analog inputs - not all of these will be supported on all controllers (only add to this enum and never change the order) +typedef enum +{ + IO_ANALOG_LEFT_STICK_X, + IO_ANALOG_LEFT_STICK_Y, + IO_ANALOG_RIGHT_STICK_X, + IO_ANALOG_RIGHT_STICK_Y, + IO_ANALOG_LEFT_TRIGGER, + IO_ANALOG_RIGHT_TRIGGER, + IO_MOUSE1_X, + IO_MOUSE1_Y, + IO_MOUSE1_Z, + IO_ACCEL_X, + IO_ACCEL_Y, + IO_ACCEL_Z, + IO_GYRO_X, + IO_GYRO_Y, + IO_GYRO_Z, + IO_GYRO_QUAT_W, + IO_GYRO_QUAT_X, + IO_GYRO_QUAT_Y, + IO_GYRO_QUAT_Z, + IO_GYRO_STEERING_VEC, + IO_RAW_TRIGGER_LEFT, + IO_RAW_TRIGGER_RIGHT, + IO_RAW_JOYSTICK_X, + IO_RAW_JOYSTICK_Y, + IO_GYRO_TILT_VEC, + IO_PRESSURE_LEFT_PAD, + IO_PRESSURE_RIGHT_PAD, + IO_PRESSURE_LEFT_BUMPER, + IO_PRESSURE_RIGHT_BUMPER, + IO_PRESSURE_LEFT_GRIP, + IO_PRESSURE_RIGHT_GRIP, + IO_ANALOG_LEFT_TRIGGER_THRESHOLD, + IO_ANALOG_RIGHT_TRIGGER_THRESHOLD, + IO_PRESSURE_RIGHT_PAD_THRESHOLD, + IO_PRESSURE_LEFT_PAD_THRESHOLD, + IO_PRESSURE_RIGHT_BUMPER_THRESHOLD, + IO_PRESSURE_LEFT_BUMPER_THRESHOLD, + IO_PRESSURE_RIGHT_GRIP_THRESHOLD, + IO_PRESSURE_LEFT_GRIP_THRESHOLD, + IO_PRESSURE_RIGHT_PAD_RAW, + IO_PRESSURE_LEFT_PAD_RAW, + IO_PRESSURE_RIGHT_BUMPER_RAW, + IO_PRESSURE_LEFT_BUMPER_RAW, + IO_PRESSURE_RIGHT_GRIP_RAW, + IO_PRESSURE_LEFT_GRIP_RAW, + IO_PRESSURE_RIGHT_GRIP2_THRESHOLD, + IO_PRESSURE_LEFT_GRIP2_THRESHOLD, + IO_PRESSURE_LEFT_GRIP2, + IO_PRESSURE_RIGHT_GRIP2, + IO_PRESSURE_RIGHT_GRIP2_RAW, + IO_PRESSURE_LEFT_GRIP2_RAW, + IO_ANALOG_COUNT +} AnalogIO; + + +// Contains list of all types of devices that the controller emulates (only add to this enum and never change the order) +enum DeviceTypes +{ + DEVICE_KEYBOARD, + DEVICE_MOUSE, + DEVICE_GAMEPAD, + DEVICE_MODE_ADJUST, + DEVICE_COUNT +}; + +// Scan codes for HID keyboards +enum HIDKeyboardKeys +{ + KEY_INVALID, + KEY_FIRST = 0x04, + KEY_A = KEY_FIRST, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, + KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_1, KEY_2, + KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_RETURN, KEY_ESCAPE, KEY_BACKSPACE, KEY_TAB, KEY_SPACE, KEY_DASH, KEY_EQUALS, KEY_LEFT_BRACKET, + KEY_RIGHT_BRACKET, KEY_BACKSLASH, KEY_UNUSED1, KEY_SEMICOLON, KEY_SINGLE_QUOTE, KEY_BACK_TICK, KEY_COMMA, KEY_PERIOD, KEY_FORWARD_SLASH, KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, + KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_PRINT_SCREEN, KEY_SCROLL_LOCK, KEY_BREAK, KEY_INSERT, KEY_HOME, KEY_PAGE_UP, KEY_DELETE, KEY_END, KEY_PAGE_DOWN, KEY_RIGHT_ARROW, + KEY_LEFT_ARROW, KEY_DOWN_ARROW, KEY_UP_ARROW, KEY_NUM_LOCK, KEY_KEYPAD_FORWARD_SLASH, KEY_KEYPAD_ASTERISK, KEY_KEYPAD_DASH, KEY_KEYPAD_PLUS, KEY_KEYPAD_ENTER, KEY_KEYPAD_1, KEY_KEYPAD_2, KEY_KEYPAD_3, KEY_KEYPAD_4, KEY_KEYPAD_5, KEY_KEYPAD_6, KEY_KEYPAD_7, + KEY_KEYPAD_8, KEY_KEYPAD_9, KEY_KEYPAD_0, KEY_KEYPAD_PERIOD, + KEY_LALT, + KEY_LSHIFT, + KEY_LWIN, + KEY_LCONTROL, + KEY_RALT, + KEY_RSHIFT, + KEY_RWIN, + KEY_RCONTROL, + KEY_VOLUP, + KEY_VOLDOWN, + KEY_MUTE, + KEY_PLAY, + KEY_STOP, + KEY_NEXT, + KEY_PREV, + KEY_LAST = KEY_PREV +}; + +enum ModifierMasks +{ + KEY_LCONTROL_MASK = (1<<0), + KEY_LSHIFT_MASK = (1<<1), + KEY_LALT_MASK = (1<<2), + KEY_LWIN_MASK = (1<<3), + KEY_RCONTROL_MASK = (1<<4), + KEY_RSHIFT_MASK = (1<<5), + KEY_RALT_MASK = (1<<6), + KEY_RWIN_MASK = (1<<7) +}; + +// Standard mouse buttons as specified in the HID mouse spec +enum MouseButtons +{ + MOUSE_BTN_LEFT, + MOUSE_BTN_RIGHT, + MOUSE_BTN_MIDDLE, + MOUSE_BTN_BACK, + MOUSE_BTN_FORWARD, + MOUSE_SCROLL_UP, + MOUSE_SCROLL_DOWN, + MOUSE_BTN_COUNT +}; + +// Gamepad buttons +enum GamepadButtons +{ + GAMEPAD_BTN_TRIGGER_LEFT=1, + GAMEPAD_BTN_TRIGGER_RIGHT, + GAMEPAD_BTN_A, + GAMEPAD_BTN_B, + GAMEPAD_BTN_Y, + GAMEPAD_BTN_X, + GAMEPAD_BTN_SHOULDER_LEFT, + GAMEPAD_BTN_SHOULDER_RIGHT, + GAMEPAD_BTN_LEFT_JOYSTICK, + GAMEPAD_BTN_RIGHT_JOYSTICK, + GAMEPAD_BTN_START, + GAMEPAD_BTN_SELECT, + GAMEPAD_BTN_STEAM, + GAMEPAD_BTN_DPAD_UP, + GAMEPAD_BTN_DPAD_DOWN, + GAMEPAD_BTN_DPAD_LEFT, + GAMEPAD_BTN_DPAD_RIGHT, + GAMEPAD_BTN_LSTICK_UP, + GAMEPAD_BTN_LSTICK_DOWN, + GAMEPAD_BTN_LSTICK_LEFT, + GAMEPAD_BTN_LSTICK_RIGHT, + GAMEPAD_BTN_RSTICK_UP, + GAMEPAD_BTN_RSTICK_DOWN, + GAMEPAD_BTN_RSTICK_LEFT, + GAMEPAD_BTN_RSTICK_RIGHT, + GAMEPAD_BTN_COUNT +}; + +// Mode adjust +enum ModeAdjustModes +{ + MODE_ADJUST_SENSITITY=1, + MODE_ADJUST_LEFT_PAD_SECONDARY_MODE, + MODE_ADJUST_RIGHT_PAD_SECONDARY_MODE, + MODE_ADJUST_COUNT +}; + +// Read-only attributes of controllers (only add to this enum and never change the order) +typedef enum +{ + ATTRIB_UNIQUE_ID, + ATTRIB_PRODUCT_ID, + ATTRIB_PRODUCT_REVISON, // deprecated + ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISON, // intentional aliasing + ATTRIB_FIRMWARE_VERSION, // deprecated + ATTRIB_FIRMWARE_BUILD_TIME, + ATTRIB_RADIO_FIRMWARE_BUILD_TIME, + ATTRIB_RADIO_DEVICE_ID0, + ATTRIB_RADIO_DEVICE_ID1, + ATTRIB_DONGLE_FIRMWARE_BUILD_TIME, + ATTRIB_BOARD_REVISION, + ATTRIB_BOOTLOADER_BUILD_TIME, + ATTRIB_CONNECTION_INTERVAL_IN_US, + ATTRIB_COUNT +} ControllerAttributes; + +// Read-only string attributes of controllers (only add to this enum and never change the order) +typedef enum +{ + ATTRIB_STR_BOARD_SERIAL, + ATTRIB_STR_UNIT_SERIAL, + ATTRIB_STR_COUNT +} ControllerStringAttributes; + +typedef enum +{ + STATUS_CODE_NORMAL, + STATUS_CODE_CRITICAL_BATTERY, + STATUS_CODE_GYRO_INIT_ERROR, +} ControllerStatusEventCodes; + +typedef enum +{ + STATUS_STATE_LOW_BATTERY=0, +} ControllerStatusStateFlags; + +typedef enum { + TRACKPAD_ABSOLUTE_MOUSE, + TRACKPAD_RELATIVE_MOUSE, + TRACKPAD_DPAD_FOUR_WAY_DISCRETE, + TRACKPAD_DPAD_FOUR_WAY_OVERLAP, + TRACKPAD_DPAD_EIGHT_WAY, + TRACKPAD_RADIAL_MODE, + TRACKPAD_ABSOLUTE_DPAD, + TRACKPAD_NONE, + TRACKPAD_GESTURE_KEYBOARD, + TRACKPAD_NUM_MODES +} TrackpadDPadMode; + +// Read-write controller settings (only add to this enum and never change the order) +typedef enum +{ + SETTING_MOUSE_SENSITIVITY, + SETTING_MOUSE_ACCELERATION, + SETTING_TRACKBALL_ROTATION_ANGLE, + SETTING_HAPTIC_INTENSITY_UNUSED, + SETTING_LEFT_GAMEPAD_STICK_ENABLED, + SETTING_RIGHT_GAMEPAD_STICK_ENABLED, + SETTING_USB_DEBUG_MODE, + SETTING_LEFT_TRACKPAD_MODE, + SETTING_RIGHT_TRACKPAD_MODE, + SETTING_MOUSE_POINTER_ENABLED, + + // 10 + SETTING_DPAD_DEADZONE, + SETTING_MINIMUM_MOMENTUM_VEL, + SETTING_MOMENTUM_DECAY_AMOUNT, + SETTING_TRACKPAD_RELATIVE_MODE_TICKS_PER_PIXEL, + SETTING_HAPTIC_INCREMENT, + SETTING_DPAD_ANGLE_SIN, + SETTING_DPAD_ANGLE_COS, + SETTING_MOMENTUM_VERTICAL_DIVISOR, + SETTING_MOMENTUM_MAXIMUM_VELOCITY, + SETTING_TRACKPAD_Z_ON, + + // 20 + SETTING_TRACKPAD_Z_OFF, + SETTING_SENSITIVITY_SCALE_AMOUNT, + SETTING_LEFT_TRACKPAD_SECONDARY_MODE, + SETTING_RIGHT_TRACKPAD_SECONDARY_MODE, + SETTING_SMOOTH_ABSOLUTE_MOUSE, + SETTING_STEAMBUTTON_POWEROFF_TIME, + SETTING_UNUSED_1, + SETTING_TRACKPAD_OUTER_RADIUS, + SETTING_TRACKPAD_Z_ON_LEFT, + SETTING_TRACKPAD_Z_OFF_LEFT, + + // 30 + SETTING_TRACKPAD_OUTER_SPIN_VEL, + SETTING_TRACKPAD_OUTER_SPIN_RADIUS, + SETTING_TRACKPAD_OUTER_SPIN_HORIZONTAL_ONLY, + SETTING_TRACKPAD_RELATIVE_MODE_DEADZONE, + SETTING_TRACKPAD_RELATIVE_MODE_MAX_VEL, + SETTING_TRACKPAD_RELATIVE_MODE_INVERT_Y, + SETTING_TRACKPAD_DOUBLE_TAP_BEEP_ENABLED, + SETTING_TRACKPAD_DOUBLE_TAP_BEEP_PERIOD, + SETTING_TRACKPAD_DOUBLE_TAP_BEEP_COUNT, + SETTING_TRACKPAD_OUTER_RADIUS_RELEASE_ON_TRANSITION, + + // 40 + SETTING_RADIAL_MODE_ANGLE, + SETTING_HAPTIC_INTENSITY_MOUSE_MODE, + SETTING_LEFT_DPAD_REQUIRES_CLICK, + SETTING_RIGHT_DPAD_REQUIRES_CLICK, + SETTING_LED_BASELINE_BRIGHTNESS, + SETTING_LED_USER_BRIGHTNESS, + SETTING_ENABLE_RAW_JOYSTICK, + SETTING_ENABLE_FAST_SCAN, + SETTING_IMU_MODE, + SETTING_WIRELESS_PACKET_VERSION, + + // 50 + SETTING_SLEEP_INACTIVITY_TIMEOUT, + SETTING_TRACKPAD_NOISE_THRESHOLD, + SETTING_LEFT_TRACKPAD_CLICK_PRESSURE, + SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE, + SETTING_LEFT_BUMPER_CLICK_PRESSURE, + SETTING_RIGHT_BUMPER_CLICK_PRESSURE, + SETTING_LEFT_GRIP_CLICK_PRESSURE, + SETTING_RIGHT_GRIP_CLICK_PRESSURE, + SETTING_LEFT_GRIP2_CLICK_PRESSURE, + SETTING_RIGHT_GRIP2_CLICK_PRESSURE, + + // 60 + SETTING_PRESSURE_MODE, + SETTING_CONTROLLER_TEST_MODE, + SETTING_TRIGGER_MODE, + SETTING_TRACKPAD_Z_THRESHOLD, + SETTING_FRAME_RATE, + SETTING_TRACKPAD_FILT_CTRL, + SETTING_TRACKPAD_CLIP, + SETTING_DEBUG_OUTPUT_SELECT, + SETTING_TRIGGER_THRESHOLD_PERCENT, + SETTING_TRACKPAD_FREQUENCY_HOPPING, + + // 70 + SETTING_HAPTICS_ENABLED, + SETTING_STEAM_WATCHDOG_ENABLE, + SETTING_TIMP_TOUCH_THRESHOLD_ON, + SETTING_TIMP_TOUCH_THRESHOLD_OFF, + SETTING_FREQ_HOPPING, + SETTING_TEST_CONTROL, + SETTING_HAPTIC_MASTER_GAIN_DB, + SETTING_THUMB_TOUCH_THRESH, + SETTING_DEVICE_POWER_STATUS, + SETTING_HAPTIC_INTENSITY, + + // 80 + SETTING_STABILIZER_ENABLED, + SETTING_TIMP_MODE_MTE, + SETTING_COUNT, + + // This is a special setting value use for callbacks and should not be set/get explicitly. + SETTING_ALL=0xFF +} ControllerSettings; + +typedef enum +{ + SETTING_DEFAULT, + SETTING_MIN, + SETTING_MAX, + SETTING_DEFAULTMINMAXCOUNT +} SettingDefaultMinMax; + +// Bitmask that define which IMU features to enable. +typedef enum +{ + SETTING_GYRO_MODE_OFF = 0x0000, + SETTING_GYRO_MODE_STEERING = 0x0001, + SETTING_GYRO_MODE_TILT = 0x0002, + SETTING_GYRO_MODE_SEND_ORIENTATION = 0x0004, + SETTING_GYRO_MODE_SEND_RAW_ACCEL = 0x0008, + SETTING_GYRO_MODE_SEND_RAW_GYRO = 0x0010, +} SettingGyroMode; + +// Bitmask for haptic pulse flags +typedef enum +{ + HAPTIC_PULSE_NORMAL = 0x0000, + HAPTIC_PULSE_HIGH_PRIORITY = 0x0001, + HAPTIC_PULSE_VERY_HIGH_PRIORITY = 0x0002, + HAPTIC_PULSE_IGNORE_USER_PREFS = 0x0003, +} SettingHapticPulseFlags; + +typedef struct +{ + // default,min,max in this array in that order + short defaultminmax[SETTING_DEFAULTMINMAXCOUNT]; +} SettingValueRange_t; + +// below is from controller_constants.c which should be compiled into any code that uses this +extern const SettingValueRange_t g_DefaultSettingValues[SETTING_COUNT]; + +// Read-write settings for dongle (only add to this enum and never change the order) +typedef enum +{ + DONGLE_SETTING_MOUSE_KEYBOARD_ENABLED, + DONGLE_SETTING_COUNT, +} DongleSettings; + +typedef enum +{ + AUDIO_STARTUP = 0, + AUDIO_SHUTDOWN = 1, + AUDIO_PAIR = 2, + AUDIO_PAIR_SUCCESS = 3, + AUDIO_IDENTIFY = 4, + AUDIO_LIZARDMODE = 5, + AUDIO_NORMALMODE = 6, + + AUDIO_MAX_SLOT = 15 +} ControllerAudio; + +#ifdef __cplusplus +} +#endif + +#endif // _CONTROLLER_CONSTANTS_H diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_structs.h b/contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_structs.h new file mode 100644 index 0000000..ea2a352 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_structs.h @@ -0,0 +1,463 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2020 Valve Corporation + + 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. +*/ +#ifndef _CONTROLLER_STRUCTS_ +#define _CONTROLLER_STRUCTS_ + +#pragma pack(1) + +#define HID_FEATURE_REPORT_BYTES 64 + +// Header for all host <==> target messages +typedef struct +{ + unsigned char type; + unsigned char length; +} FeatureReportHeader; + +// Generic controller settings structure +typedef struct +{ + unsigned char settingNum; + unsigned short settingValue; +} ControllerSetting; + +// Generic controller attribute structure +typedef struct +{ + unsigned char attributeTag; + uint32_t attributeValue; +} ControllerAttribute; + +// Generic controller settings structure +typedef struct +{ + ControllerSetting settings[ ( HID_FEATURE_REPORT_BYTES - sizeof( FeatureReportHeader ) ) / sizeof( ControllerSetting ) ]; +} MsgSetSettingsValues, MsgGetSettingsValues, MsgGetSettingsDefaults, MsgGetSettingsMaxs; + +// Generic controller settings structure +typedef struct +{ + ControllerAttribute attributes[ ( HID_FEATURE_REPORT_BYTES - sizeof( FeatureReportHeader ) ) / sizeof( ControllerAttribute ) ]; +} MsgGetAttributes; + +typedef struct +{ + unsigned char attributeTag; + char attributeValue[20]; +} MsgGetStringAttribute; + +typedef struct +{ + unsigned char mode; +} MsgSetControllerMode; + +// Trigger a haptic pulse +typedef struct { + unsigned char which_pad; + unsigned short pulse_duration; + unsigned short pulse_interval; + unsigned short pulse_count; + short dBgain; + unsigned char priority; +} MsgFireHapticPulse; + +typedef struct { + uint8_t mode; +} MsgHapticSetMode; + +typedef enum { + HAPTIC_TYPE_OFF, + HAPTIC_TYPE_TICK, + HAPTIC_TYPE_CLICK, + HAPTIC_TYPE_TONE, + HAPTIC_TYPE_RUMBLE, + HAPTIC_TYPE_NOISE, + HAPTIC_TYPE_SCRIPT, + HAPTIC_TYPE_LOG_SWEEP, +} haptic_type_t; + +typedef enum { + HAPTIC_INTENSITY_SYSTEM, + HAPTIC_INTENSITY_SHORT, + HAPTIC_INTENSITY_MEDIUM, + HAPTIC_INTENSITY_LONG, + HAPTIC_INTENSITY_INSANE, +} haptic_intensity_t; + +typedef struct { + uint8_t side; // 0x01 = L, 0x02 = R, 0x03 = Both + uint8_t cmd; // 0 = Off, 1 = tick, 2 = click, 3 = tone, 4 = rumble, 5 = + // rumble_noise, 6 = script, 7 = sweep, + uint8_t ui_intensity; // 0-4 (0 = default) + int8_t dBgain; // dB Can be positive (reasonable clipping / limiting will apply) + uint16_t freq; // Frequency of tone (if applicable) + int16_t dur_ms; // Duration of tone / rumble (if applicable) (neg = infinite) + + uint16_t noise_intensity; + uint16_t lfo_freq; // Drives both tone and rumble geneators + uint8_t lfo_depth; // percentage, typically 100 + uint8_t rand_tone_gain; // Randomize each LFO cycle's gain + uint8_t script_id; // Used w/ dBgain for scripted haptics + + uint16_t lss_start_freq; // Used w/ Log Sine Sweep + uint16_t lss_end_freq; // Ditto +} MsgTriggerHaptic; + +typedef struct { + uint8_t unRumbleType; + uint16_t unIntensity; + uint16_t unLeftMotorSpeed; + uint16_t unRightMotorSpeed; + int8_t nLeftGain; + int8_t nRightGain; +} MsgSimpleRumbleCmd; + +// This is the only message struct that application code should use to interact with feature request messages. Any new +// messages should be added to the union. The structures defined here should correspond to the ones defined in +// ValveDeviceCore.cpp. +// +typedef struct +{ + FeatureReportHeader header; + union + { + MsgSetSettingsValues setSettingsValues; + MsgGetSettingsValues getSettingsValues; + MsgGetSettingsMaxs getSettingsMaxs; + MsgGetSettingsDefaults getSettingsDefaults; + MsgGetAttributes getAttributes; + MsgSetControllerMode controllerMode; + MsgFireHapticPulse fireHapticPulse; + MsgGetStringAttribute getStringAttribute; + MsgHapticSetMode hapticMode; + MsgTriggerHaptic triggerHaptic; + MsgSimpleRumbleCmd simpleRumble; + } payload; + +} FeatureReportMsg; + +// Roll this version forward anytime that you are breaking compatibility of existing +// message types within ValveInReport_t or the header itself. Hopefully this should +// be super rare and instead you should just add new message payloads to the union, +// or just add fields to the end of existing payload structs which is expected to be +// safe in all code consuming these as they should just consume/copy up to the prior size +// they were aware of when processing. +#define k_ValveInReportMsgVersion 0x01 + +typedef enum +{ + ID_CONTROLLER_STATE = 1, + ID_CONTROLLER_DEBUG = 2, + ID_CONTROLLER_WIRELESS = 3, + ID_CONTROLLER_STATUS = 4, + ID_CONTROLLER_DEBUG2 = 5, + ID_CONTROLLER_SECONDARY_STATE = 6, + ID_CONTROLLER_BLE_STATE = 7, + ID_CONTROLLER_DECK_STATE = 9, + ID_CONTROLLER_MSG_COUNT +} ValveInReportMessageIDs; + +typedef struct +{ + unsigned short unReportVersion; + + unsigned char ucType; + unsigned char ucLength; + +} ValveInReportHeader_t; + +// State payload +typedef struct +{ + // If packet num matches that on your prior call, then the controller state hasn't been changed since + // your last call and there is no need to process it + Uint32 unPacketNum; + + // Button bitmask and trigger data. + union + { + Uint64 ulButtons; + struct + { + unsigned char _pad0[3]; + unsigned char nLeft; + unsigned char nRight; + unsigned char _pad1[3]; + } Triggers; + } ButtonTriggerData; + + // Left pad coordinates + short sLeftPadX; + short sLeftPadY; + + // Right pad coordinates + short sRightPadX; + short sRightPadY; + + // This is redundant, packed above, but still sent over wired + unsigned short sTriggerL; + unsigned short sTriggerR; + + // FIXME figure out a way to grab this stuff over wireless + short sAccelX; + short sAccelY; + short sAccelZ; + + short sGyroX; + short sGyroY; + short sGyroZ; + + short sGyroQuatW; + short sGyroQuatX; + short sGyroQuatY; + short sGyroQuatZ; + +} ValveControllerStatePacket_t; + +// BLE State payload this has to be re-formatted from the normal state because BLE controller shows up as +//a HID device and we don't want to send all the optional parts of the message. Keep in sync with struct above. +typedef struct +{ + // If packet num matches that on your prior call, then the controller state hasn't been changed since + // your last call and there is no need to process it + Uint32 unPacketNum; + + // Button bitmask and trigger data. + union + { + Uint64 ulButtons; + struct + { + unsigned char _pad0[3]; + unsigned char nLeft; + unsigned char nRight; + unsigned char _pad1[3]; + } Triggers; + } ButtonTriggerData; + + // Left pad coordinates + short sLeftPadX; + short sLeftPadY; + + // Right pad coordinates + short sRightPadX; + short sRightPadY; + + //This mimcs how the dongle reconstitutes HID packets, there will be 0-4 shorts depending on gyro mode + unsigned char ucGyroDataType; //TODO could maybe find some unused bits in the button field for this info (is only 2bits) + short sGyro[4]; + +} ValveControllerBLEStatePacket_t; + +// Define a payload for reporting debug information +typedef struct +{ + // Left pad coordinates + short sLeftPadX; + short sLeftPadY; + + // Right pad coordinates + short sRightPadX; + short sRightPadY; + + // Left mouse deltas + short sLeftPadMouseDX; + short sLeftPadMouseDY; + + // Right mouse deltas + short sRightPadMouseDX; + short sRightPadMouseDY; + + // Left mouse filtered deltas + short sLeftPadMouseFilteredDX; + short sLeftPadMouseFilteredDY; + + // Right mouse filtered deltas + short sRightPadMouseFilteredDX; + short sRightPadMouseFilteredDY; + + // Pad Z values + unsigned char ucLeftZ; + unsigned char ucRightZ; + + // FingerPresent + unsigned char ucLeftFingerPresent; + unsigned char ucRightFingerPresent; + + // Timestamps + unsigned char ucLeftTimestamp; + unsigned char ucRightTimestamp; + + // Double tap state + unsigned char ucLeftTapState; + unsigned char ucRightTapState; + + unsigned int unDigitalIOStates0; + unsigned int unDigitalIOStates1; + +} ValveControllerDebugPacket_t; + +typedef struct +{ + unsigned char ucPadNum; + unsigned char ucPad[3]; // need Data to be word aligned + short Data[20]; + unsigned short unNoise; +} ValveControllerTrackpadImage_t; + +typedef struct +{ + unsigned char ucPadNum; + unsigned char ucOffset; + unsigned char ucPad[2]; // need Data to be word aligned + short rgData[28]; +} ValveControllerRawTrackpadImage_t; + +// Payload for wireless metadata +typedef struct +{ + unsigned char ucEventType; +} SteamControllerWirelessEvent_t; + +typedef struct +{ + // Current packet number. + unsigned int unPacketNum; + + // Event codes and state information. + unsigned short sEventCode; + unsigned short unStateFlags; + + // Current battery voltage (mV). + unsigned short sBatteryVoltage; + + // Current battery level (0-100). + unsigned char ucBatteryLevel; +} SteamControllerStatusEvent_t; + +// Deck State payload +typedef struct +{ + // If packet num matches that on your prior call, then the controller + // state hasn't been changed since your last call and there is no need to + // process it + Uint32 unPacketNum; + + // Button bitmask and trigger data. + union + { + Uint64 ulButtons; + struct + { + Uint32 ulButtonsL; + Uint32 ulButtonsH; + }; + }; + + // Left pad coordinates + short sLeftPadX; + short sLeftPadY; + + // Right pad coordinates + short sRightPadX; + short sRightPadY; + + // Accelerometer values + short sAccelX; + short sAccelY; + short sAccelZ; + + // Gyroscope values + short sGyroX; + short sGyroY; + short sGyroZ; + + // Gyro quaternions + short sGyroQuatW; + short sGyroQuatX; + short sGyroQuatY; + short sGyroQuatZ; + + // Uncalibrated trigger values + unsigned short sTriggerRawL; + unsigned short sTriggerRawR; + + // Left stick values + short sLeftStickX; + short sLeftStickY; + + // Right stick values + short sRightStickX; + short sRightStickY; + + // Touchpad pressures + unsigned short sPressurePadLeft; + unsigned short sPressurePadRight; +} SteamDeckStatePacket_t; + +typedef struct +{ + ValveInReportHeader_t header; + + union + { + ValveControllerStatePacket_t controllerState; + ValveControllerBLEStatePacket_t controllerBLEState; + ValveControllerDebugPacket_t debugState; + ValveControllerTrackpadImage_t padImage; + ValveControllerRawTrackpadImage_t rawPadImage; + SteamControllerWirelessEvent_t wirelessEvent; + SteamControllerStatusEvent_t statusEvent; + SteamDeckStatePacket_t deckState; + } payload; + +} ValveInReport_t; + + +// Enumeration for BLE packet protocol +enum EBLEPacketReportNums +{ + // Skipping past 2-3 because they are escape characters in Uart protocol + k_EBLEReportState = 4, + k_EBLEReportStatus = 5, +}; + + +// Enumeration of data chunks in BLE state packets +enum EBLEOptionDataChunksBitmask +{ + // First byte upper nibble + k_EBLEButtonChunk1 = 0x10, + k_EBLEButtonChunk2 = 0x20, + k_EBLEButtonChunk3 = 0x40, + k_EBLELeftJoystickChunk = 0x80, + + // Second full byte + k_EBLELeftTrackpadChunk = 0x100, + k_EBLERightTrackpadChunk = 0x200, + k_EBLEIMUAccelChunk = 0x400, + k_EBLEIMUGyroChunk = 0x800, + k_EBLEIMUQuatChunk = 0x1000, +}; + +#pragma pack() + +#endif // _CONTROLLER_STRUCTS diff --git a/contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick.c new file mode 100644 index 0000000..ea73821 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick.c @@ -0,0 +1,2730 @@ +/* + 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_JOYSTICK_LINUX + +#ifndef SDL_INPUT_LINUXEV +#error SDL now requires a Linux 2.4+ kernel with /dev/input/event support. +#endif + +// This is the Linux implementation of the SDL joystick API + +#include +#include // errno, strerror +#include +#include // For the definition of PATH_MAX +#ifdef HAVE_INOTIFY +#include +#include // strerror +#endif +#include +#include +#include +#include + +#include "../../events/SDL_events_c.h" +#include "../../core/linux/SDL_evdev.h" +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" +#include "../usb_ids.h" +#include "SDL_sysjoystick_c.h" +#include "../hidapi/SDL_hidapijoystick_c.h" + +// This isn't defined in older Linux kernel headers +#ifndef MSC_TIMESTAMP +#define MSC_TIMESTAMP 0x05 +#endif + +#ifndef SYN_DROPPED +#define SYN_DROPPED 3 +#endif +#ifndef BTN_NORTH +#define BTN_NORTH 0x133 +#endif +#ifndef BTN_WEST +#define BTN_WEST 0x134 +#endif +#ifndef BTN_DPAD_UP +#define BTN_DPAD_UP 0x220 +#endif +#ifndef BTN_DPAD_DOWN +#define BTN_DPAD_DOWN 0x221 +#endif +#ifndef BTN_DPAD_LEFT +#define BTN_DPAD_LEFT 0x222 +#endif +#ifndef BTN_DPAD_RIGHT +#define BTN_DPAD_RIGHT 0x223 +#endif + +#ifndef BTN_TRIGGER_HAPPY +#define BTN_TRIGGER_HAPPY 0x2c0 +#define BTN_TRIGGER_HAPPY1 0x2c0 +#define BTN_TRIGGER_HAPPY2 0x2c1 +#define BTN_TRIGGER_HAPPY3 0x2c2 +#define BTN_TRIGGER_HAPPY4 0x2c3 +#define BTN_TRIGGER_HAPPY5 0x2c4 +#define BTN_TRIGGER_HAPPY6 0x2c5 +#define BTN_TRIGGER_HAPPY7 0x2c6 +#define BTN_TRIGGER_HAPPY8 0x2c7 +#define BTN_TRIGGER_HAPPY9 0x2c8 +#define BTN_TRIGGER_HAPPY10 0x2c9 +#define BTN_TRIGGER_HAPPY11 0x2ca +#define BTN_TRIGGER_HAPPY12 0x2cb +#define BTN_TRIGGER_HAPPY13 0x2cc +#define BTN_TRIGGER_HAPPY14 0x2cd +#define BTN_TRIGGER_HAPPY15 0x2ce +#define BTN_TRIGGER_HAPPY16 0x2cf +#define BTN_TRIGGER_HAPPY17 0x2d0 +#define BTN_TRIGGER_HAPPY18 0x2d1 +#define BTN_TRIGGER_HAPPY19 0x2d2 +#define BTN_TRIGGER_HAPPY20 0x2d3 +#define BTN_TRIGGER_HAPPY21 0x2d4 +#define BTN_TRIGGER_HAPPY22 0x2d5 +#define BTN_TRIGGER_HAPPY23 0x2d6 +#define BTN_TRIGGER_HAPPY24 0x2d7 +#define BTN_TRIGGER_HAPPY25 0x2d8 +#define BTN_TRIGGER_HAPPY26 0x2d9 +#define BTN_TRIGGER_HAPPY27 0x2da +#define BTN_TRIGGER_HAPPY28 0x2db +#define BTN_TRIGGER_HAPPY29 0x2dc +#define BTN_TRIGGER_HAPPY30 0x2dd +#define BTN_TRIGGER_HAPPY31 0x2de +#define BTN_TRIGGER_HAPPY32 0x2df +#define BTN_TRIGGER_HAPPY33 0x2e0 +#define BTN_TRIGGER_HAPPY34 0x2e1 +#define BTN_TRIGGER_HAPPY35 0x2e2 +#define BTN_TRIGGER_HAPPY36 0x2e3 +#define BTN_TRIGGER_HAPPY37 0x2e4 +#define BTN_TRIGGER_HAPPY38 0x2e5 +#define BTN_TRIGGER_HAPPY39 0x2e6 +#define BTN_TRIGGER_HAPPY40 0x2e7 +#endif + + +#include "../../core/linux/SDL_evdev_capabilities.h" +#include "../../core/linux/SDL_udev.h" + +#if 0 +#define DEBUG_INPUT_EVENTS 1 +#endif + +#if 0 +#define DEBUG_GAMEPAD_MAPPING 1 +#endif + +typedef enum +{ + ENUMERATION_UNSET, + ENUMERATION_LIBUDEV, + ENUMERATION_FALLBACK +} EnumerationMethod; + +static EnumerationMethod enumeration_method = ENUMERATION_UNSET; + +static bool IsJoystickJSNode(const char *node); +static void MaybeAddDevice(const char *path); +static void MaybeRemoveDevice(const char *path); + +// A linked list of available joysticks +typedef struct SDL_joylist_item +{ + SDL_JoystickID device_instance; + char *path; // "/dev/input/event2" or whatever + char *name; // "SideWinder 3D Pro" or whatever + SDL_GUID guid; + dev_t devnum; + int steam_virtual_gamepad_slot; + struct joystick_hwdata *hwdata; + struct SDL_joylist_item *next; + + bool checked_mapping; + SDL_GamepadMapping *mapping; +} SDL_joylist_item; + +// A linked list of available gamepad sensors +typedef struct SDL_sensorlist_item +{ + char *path; // "/dev/input/event2" or whatever + dev_t devnum; + struct joystick_hwdata *hwdata; + struct SDL_sensorlist_item *next; +} SDL_sensorlist_item; + +static bool SDL_classic_joysticks = false; +static SDL_joylist_item *SDL_joylist SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static SDL_joylist_item *SDL_joylist_tail SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static int numjoysticks SDL_GUARDED_BY(SDL_joystick_lock) = 0; +static SDL_sensorlist_item *SDL_sensorlist SDL_GUARDED_BY(SDL_joystick_lock) = NULL; +static int inotify_fd = -1; + +static Uint64 last_joy_detect_time; +static time_t last_input_dir_mtime; + +static void FixupDeviceInfoForMapping(int fd, struct input_id *inpid) +{ + if (inpid->vendor == 0x045e && inpid->product == 0x0b05 && inpid->version == 0x0903) { + // This is a Microsoft Xbox One Elite Series 2 controller + unsigned long keybit[NBITS(KEY_MAX)] = { 0 }; + + // The first version of the firmware duplicated all the inputs + if ((ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) >= 0) && + test_bit(0x2c0, keybit)) { + // Change the version to 0x0902, so we can map it differently + inpid->version = 0x0902; + } + } + + /* For Atari vcs modern and classic controllers have the version reflecting + * firmware version, but the mapping stays stable so ignore + * version information */ + if (inpid->vendor == 0x3250 && (inpid->product == 0x1001 || inpid->product == 0x1002)) { + inpid->version = 0; + } +} + +#ifdef SDL_JOYSTICK_HIDAPI +static bool IsVirtualJoystick(Uint16 vendor, Uint16 product, Uint16 version, const char *name) +{ + if (vendor == USB_VENDOR_MICROSOFT && product == USB_PRODUCT_XBOX_ONE_S && version == 0 && + SDL_strcmp(name, "Xbox One S Controller") == 0) { + // This is the virtual device created by the xow driver + return true; + } + return false; +} +#else +static bool IsVirtualJoystick(Uint16 vendor, Uint16 product, Uint16 version, const char *name) +{ + return false; +} +#endif // SDL_JOYSTICK_HIDAPI + +static bool GetSteamVirtualGamepadSlot(int fd, int *slot) +{ + char name[128]; + + if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) > 0) { + const char *digits = SDL_strstr(name, "pad "); + if (digits) { + digits += 4; + if (SDL_isdigit(*digits)) { + *slot = SDL_atoi(digits); + return true; + } + } + } + return false; +} + +static int GuessDeviceClass(int fd) +{ + unsigned long propbit[NBITS(INPUT_PROP_MAX)] = { 0 }; + unsigned long evbit[NBITS(EV_MAX)] = { 0 }; + unsigned long keybit[NBITS(KEY_MAX)] = { 0 }; + unsigned long absbit[NBITS(ABS_MAX)] = { 0 }; + unsigned long relbit[NBITS(REL_MAX)] = { 0 }; + + if ((ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit) < 0) || + (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) < 0) || + (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit) < 0) || + (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) < 0)) { + return 0; + } + + /* This is a newer feature, so it's allowed to fail - if so, then the + * device just doesn't have any properties. */ + (void) ioctl(fd, EVIOCGPROP(sizeof(propbit)), propbit); + + return SDL_EVDEV_GuessDeviceClass(propbit, evbit, absbit, keybit, relbit); +} + +static bool GuessIsJoystick(int fd) +{ + if (GuessDeviceClass(fd) & SDL_UDEV_DEVICE_JOYSTICK) { + return true; + } + return false; +} + +static bool GuessIsSensor(int fd) +{ + if (GuessDeviceClass(fd) & SDL_UDEV_DEVICE_ACCELEROMETER) { + return true; + } + return false; +} + +static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid) +{ + struct input_id inpid; + char *name; + char product_string[128]; + int class = 0; + + SDL_zero(inpid); +#ifdef SDL_USE_LIBUDEV + // Opening input devices can generate synchronous device I/O, so avoid it if we can + if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) && + !(class & SDL_UDEV_DEVICE_JOYSTICK)) { + return false; + } +#endif + + if (fd && *fd < 0) { + *fd = open(path, O_RDONLY | O_CLOEXEC, 0); + } + if (!fd || *fd < 0) { + return false; + } + + if (ioctl(*fd, JSIOCGNAME(sizeof(product_string)), product_string) <= 0) { + // When udev enumeration or classification, we only got joysticks here, so no need to test + if (enumeration_method != ENUMERATION_LIBUDEV && !class && !GuessIsJoystick(*fd)) { + return false; + } + + // Could have vendor and product already from udev, but should agree with evdev + if (ioctl(*fd, EVIOCGID, &inpid) < 0) { + return false; + } + + if (ioctl(*fd, EVIOCGNAME(sizeof(product_string)), product_string) < 0) { + return false; + } + } + + name = SDL_CreateJoystickName(inpid.vendor, inpid.product, NULL, product_string); + if (!name) { + return false; + } + + if (!IsVirtualJoystick(inpid.vendor, inpid.product, inpid.version, name) && + SDL_JoystickHandledByAnotherDriver(&SDL_LINUX_JoystickDriver, inpid.vendor, inpid.product, inpid.version, name)) { + SDL_free(name); + return false; + } + + FixupDeviceInfoForMapping(*fd, &inpid); + +#ifdef DEBUG_JOYSTICK + SDL_Log("Joystick: %s, bustype = %d, vendor = 0x%.4x, product = 0x%.4x, version = %d", name, inpid.bustype, inpid.vendor, inpid.product, inpid.version); +#endif + + if (SDL_ShouldIgnoreJoystick(inpid.vendor, inpid.product, inpid.version, name)) { + SDL_free(name); + return false; + } + *name_return = name; + *vendor_return = inpid.vendor; + *product_return = inpid.product; + *guid = SDL_CreateJoystickGUID(inpid.bustype, inpid.vendor, inpid.product, inpid.version, NULL, product_string, 0, 0); + return true; +} + +static bool IsSensor(const char *path, int *fd) +{ + struct input_id inpid; + int class = 0; + + SDL_zero(inpid); +#ifdef SDL_USE_LIBUDEV + // Opening input devices can generate synchronous device I/O, so avoid it if we can + if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) && + !(class & SDL_UDEV_DEVICE_ACCELEROMETER)) { + return false; + } +#endif + + if (fd && *fd < 0) { + *fd = open(path, O_RDONLY | O_CLOEXEC, 0); + } + if (!fd || *fd < 0) { + return false; + } + + if (!class && !GuessIsSensor(*fd)) { + return false; + } + + if (ioctl(*fd, EVIOCGID, &inpid) < 0) { + return false; + } + + if (inpid.vendor == USB_VENDOR_NINTENDO && inpid.product == USB_PRODUCT_NINTENDO_WII_REMOTE) { + // Wii extension controls + // These may create 3 sensor devices but we only support reading from 1: ignore them + return false; + } + + return true; +} + +#ifdef SDL_USE_LIBUDEV +static void joystick_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath) +{ + if (!devpath) { + return; + } + + switch (udev_type) { + case SDL_UDEV_DEVICEADDED: + if (!(udev_class & (SDL_UDEV_DEVICE_JOYSTICK | SDL_UDEV_DEVICE_ACCELEROMETER))) { + return; + } + if (SDL_classic_joysticks) { + if (!IsJoystickJSNode(devpath)) { + return; + } + } else { + if (IsJoystickJSNode(devpath)) { + return; + } + } + + // Wait a bit for the hidraw udev node to initialize + SDL_Delay(10); + + MaybeAddDevice(devpath); + break; + + case SDL_UDEV_DEVICEREMOVED: + MaybeRemoveDevice(devpath); + break; + + default: + break; + } +} +#endif // SDL_USE_LIBUDEV + +static void FreeJoylistItem(SDL_joylist_item *item) +{ + SDL_free(item->mapping); + SDL_free(item->path); + SDL_free(item->name); + SDL_free(item); +} + +static void FreeSensorlistItem(SDL_sensorlist_item *item) +{ + SDL_free(item->path); + SDL_free(item); +} + +static void MaybeAddDevice(const char *path) +{ + struct stat sb; + int fd = -1; + char *name = NULL; + Uint16 vendor, product; + SDL_GUID guid; + SDL_joylist_item *item; + SDL_sensorlist_item *item_sensor; + + if (!path) { + return; + } + + fd = open(path, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) { + return; + } + + if (fstat(fd, &sb) == -1) { + close(fd); + return; + } + + SDL_LockJoysticks(); + + // Check to make sure it's not already in list. + for (item = SDL_joylist; item; item = item->next) { + if (sb.st_rdev == item->devnum) { + goto done; // already have this one + } + } + for (item_sensor = SDL_sensorlist; item_sensor; item_sensor = item_sensor->next) { + if (sb.st_rdev == item_sensor->devnum) { + goto done; // already have this one + } + } + +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Checking %s", path); +#endif + + if (IsJoystick(path, &fd, &name, &vendor, &product, &guid)) { +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("found joystick: %s", path); +#endif + item = (SDL_joylist_item *)SDL_calloc(1, sizeof(SDL_joylist_item)); + if (!item) { + SDL_free(name); + goto done; + } + + item->devnum = sb.st_rdev; + item->steam_virtual_gamepad_slot = -1; + item->path = SDL_strdup(path); + item->name = name; + item->guid = guid; + + if (vendor == USB_VENDOR_VALVE && + product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + GetSteamVirtualGamepadSlot(fd, &item->steam_virtual_gamepad_slot); + } + + if ((!item->path) || (!item->name)) { + FreeJoylistItem(item); + goto done; + } + + item->device_instance = SDL_GetNextObjectID(); + if (!SDL_joylist_tail) { + SDL_joylist = SDL_joylist_tail = item; + } else { + SDL_joylist_tail->next = item; + SDL_joylist_tail = item; + } + + // Need to increment the joystick count before we post the event + ++numjoysticks; + + SDL_PrivateJoystickAdded(item->device_instance); + goto done; + } + + if (IsSensor(path, &fd)) { +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("found sensor: %s", path); +#endif + item_sensor = (SDL_sensorlist_item *)SDL_calloc(1, sizeof(SDL_sensorlist_item)); + if (!item_sensor) { + goto done; + } + item_sensor->devnum = sb.st_rdev; + item_sensor->path = SDL_strdup(path); + + if (!item_sensor->path) { + FreeSensorlistItem(item_sensor); + goto done; + } + + item_sensor->next = SDL_sensorlist; + SDL_sensorlist = item_sensor; + goto done; + } + +done: + close(fd); + SDL_UnlockJoysticks(); +} + +static void RemoveJoylistItem(SDL_joylist_item *item, SDL_joylist_item *prev) +{ + SDL_AssertJoysticksLocked(); + + if (item->hwdata) { + item->hwdata->item = NULL; + } + + if (prev) { + prev->next = item->next; + } else { + SDL_assert(SDL_joylist == item); + SDL_joylist = item->next; + } + + if (item == SDL_joylist_tail) { + SDL_joylist_tail = prev; + } + + // Need to decrement the joystick count before we post the event + --numjoysticks; + + SDL_PrivateJoystickRemoved(item->device_instance); + FreeJoylistItem(item); +} + +static void RemoveSensorlistItem(SDL_sensorlist_item *item, SDL_sensorlist_item *prev) +{ + SDL_AssertJoysticksLocked(); + + if (item->hwdata) { + item->hwdata->item_sensor = NULL; + } + + if (prev) { + prev->next = item->next; + } else { + SDL_assert(SDL_sensorlist == item); + SDL_sensorlist = item->next; + } + + /* Do not call SDL_PrivateJoystickRemoved here as RemoveJoylistItem will do it, + * assuming both sensor and joy item are removed at the same time */ + FreeSensorlistItem(item); +} + +static void MaybeRemoveDevice(const char *path) +{ + SDL_joylist_item *item; + SDL_joylist_item *prev = NULL; + SDL_sensorlist_item *item_sensor; + SDL_sensorlist_item *prev_sensor = NULL; + + if (!path) { + return; + } + + SDL_LockJoysticks(); + for (item = SDL_joylist; item; item = item->next) { + // found it, remove it. + if (SDL_strcmp(path, item->path) == 0) { + RemoveJoylistItem(item, prev); + goto done; + } + prev = item; + } + for (item_sensor = SDL_sensorlist; item_sensor; item_sensor = item_sensor->next) { + // found it, remove it. + if (SDL_strcmp(path, item_sensor->path) == 0) { + RemoveSensorlistItem(item_sensor, prev_sensor); + goto done; + } + prev_sensor = item_sensor; + } +done: + SDL_UnlockJoysticks(); +} + +static void HandlePendingRemovals(void) +{ + SDL_joylist_item *prev = NULL; + SDL_joylist_item *item = NULL; + SDL_sensorlist_item *prev_sensor = NULL; + SDL_sensorlist_item *item_sensor = NULL; + + SDL_AssertJoysticksLocked(); + + item = SDL_joylist; + while (item) { + if (item->hwdata && item->hwdata->gone) { + RemoveJoylistItem(item, prev); + + if (prev) { + item = prev->next; + } else { + item = SDL_joylist; + } + } else { + prev = item; + item = item->next; + } + } + + item_sensor = SDL_sensorlist; + while (item_sensor) { + if (item_sensor->hwdata && item_sensor->hwdata->sensor_gone) { + RemoveSensorlistItem(item_sensor, prev_sensor); + + if (prev_sensor) { + item_sensor = prev_sensor->next; + } else { + item_sensor = SDL_sensorlist; + } + } else { + prev_sensor = item_sensor; + item_sensor = item_sensor->next; + } + } +} + +static bool StrIsInteger(const char *string) +{ + const char *p; + + if (*string == '\0') { + return false; + } + + for (p = string; *p != '\0'; p++) { + if (*p < '0' || *p > '9') { + return false; + } + } + + return true; +} + +static bool IsJoystickJSNode(const char *node) +{ + const char *last_slash = SDL_strrchr(node, '/'); + if (last_slash) { + node = last_slash + 1; + } + return SDL_startswith(node, "js") && StrIsInteger(node + 2); +} + +static bool IsJoystickEventNode(const char *node) +{ + const char *last_slash = SDL_strrchr(node, '/'); + if (last_slash) { + node = last_slash + 1; + } + return SDL_startswith(node, "event") && StrIsInteger(node + 5); +} + +static bool IsJoystickDeviceNode(const char *node) +{ + if (SDL_classic_joysticks) { + return IsJoystickJSNode(node); + } else { + return IsJoystickEventNode(node); + } +} + +#ifdef HAVE_INOTIFY +#ifdef HAVE_INOTIFY_INIT1 +static int SDL_inotify_init1(void) +{ + return inotify_init1(IN_NONBLOCK | IN_CLOEXEC); +} +#else +static int SDL_inotify_init1(void) +{ + int fd = inotify_init(); + if (fd < 0) { + return -1; + } + fcntl(fd, F_SETFL, O_NONBLOCK); + fcntl(fd, F_SETFD, FD_CLOEXEC); + return fd; +} +#endif + +static void LINUX_InotifyJoystickDetect(void) +{ + union + { + struct inotify_event event; + char storage[4096]; + char enough_for_inotify[sizeof(struct inotify_event) + NAME_MAX + 1]; + } buf; + ssize_t bytes; + size_t remain = 0; + size_t len; + char path[PATH_MAX]; + + bytes = read(inotify_fd, &buf, sizeof(buf)); + + if (bytes > 0) { + remain = (size_t)bytes; + } + + while (remain > 0) { + if (buf.event.len > 0) { + if (IsJoystickDeviceNode(buf.event.name)) { + (void)SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", buf.event.name); + + if (buf.event.mask & (IN_CREATE | IN_MOVED_TO | IN_ATTRIB)) { + MaybeAddDevice(path); + } else if (buf.event.mask & (IN_DELETE | IN_MOVED_FROM)) { + MaybeRemoveDevice(path); + } + } + } + + len = sizeof(struct inotify_event) + buf.event.len; + remain -= len; + + if (remain != 0) { + SDL_memmove(&buf.storage[0], &buf.storage[len], remain); + } + } +} +#endif // HAVE_INOTIFY + +static int get_event_joystick_index(int event) +{ + int joystick_index = -1; + int i, count; + struct dirent **entries = NULL; + char path[PATH_MAX]; + + (void)SDL_snprintf(path, SDL_arraysize(path), "/sys/class/input/event%d/device", event); + count = scandir(path, &entries, NULL, alphasort); + for (i = 0; i < count; ++i) { + if (SDL_strncmp(entries[i]->d_name, "js", 2) == 0) { + joystick_index = SDL_atoi(entries[i]->d_name + 2); + } + free(entries[i]); // This should NOT be SDL_free() + } + free(entries); // This should NOT be SDL_free() + + return joystick_index; +} + +/* Detect devices by reading /dev/input. In the inotify code path we + * have to do this the first time, to detect devices that already existed + * before we started; in the non-inotify code path we do this repeatedly + * (polling). */ +static int filter_entries(const struct dirent *entry) +{ + return IsJoystickDeviceNode(entry->d_name); +} +static int SDLCALL sort_entries(const void *_a, const void *_b) +{ + const struct dirent **a = (const struct dirent **)_a; + const struct dirent **b = (const struct dirent **)_b; + int numA, numB; + int offset; + + if (SDL_classic_joysticks) { + offset = 2; // strlen("js") + numA = SDL_atoi((*a)->d_name + offset); + numB = SDL_atoi((*b)->d_name + offset); + } else { + offset = 5; // strlen("event") + numA = SDL_atoi((*a)->d_name + offset); + numB = SDL_atoi((*b)->d_name + offset); + + // See if we can get the joystick ordering + { + int jsA = get_event_joystick_index(numA); + int jsB = get_event_joystick_index(numB); + if (jsA >= 0 && jsB >= 0) { + numA = jsA; + numB = jsB; + } else if (jsA >= 0) { + return -1; + } else if (jsB >= 0) { + return 1; + } + } + } + return numA - numB; +} + +typedef struct +{ + char *path; + int slot; +} VirtualGamepadEntry; + +static int SDLCALL sort_virtual_gamepads(const void *_a, const void *_b) +{ + const VirtualGamepadEntry *a = (const VirtualGamepadEntry *)_a; + const VirtualGamepadEntry *b = (const VirtualGamepadEntry *)_b; + return a->slot - b->slot; +} + +static void LINUX_ScanSteamVirtualGamepads(void) +{ + int i, count; + int fd; + struct dirent **entries = NULL; + char path[PATH_MAX]; + struct input_id inpid; + int num_virtual_gamepads = 0; + int virtual_gamepad_slot; + VirtualGamepadEntry *virtual_gamepads = NULL; +#ifdef SDL_USE_LIBUDEV + int class; +#endif + + count = scandir("/dev/input", &entries, filter_entries, NULL); + for (i = 0; i < count; ++i) { + (void)SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", entries[i]->d_name); + +#ifdef SDL_USE_LIBUDEV + // Opening input devices can generate synchronous device I/O, so avoid it if we can + class = 0; + SDL_zero(inpid); + if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) && + (inpid.vendor != USB_VENDOR_VALVE || inpid.product != USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) { + free(entries[i]); // This should NOT be SDL_free() + continue; + } +#endif + fd = open(path, O_RDONLY | O_CLOEXEC, 0); + if (fd >= 0) { + if (ioctl(fd, EVIOCGID, &inpid) == 0 && + inpid.vendor == USB_VENDOR_VALVE && + inpid.product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD && + GetSteamVirtualGamepadSlot(fd, &virtual_gamepad_slot)) { + VirtualGamepadEntry *new_virtual_gamepads = (VirtualGamepadEntry *)SDL_realloc(virtual_gamepads, (num_virtual_gamepads + 1) * sizeof(*virtual_gamepads)); + if (new_virtual_gamepads) { + VirtualGamepadEntry *entry = &new_virtual_gamepads[num_virtual_gamepads]; + entry->path = SDL_strdup(path); + entry->slot = virtual_gamepad_slot; + if (entry->path) { + virtual_gamepads = new_virtual_gamepads; + ++num_virtual_gamepads; + } else { + SDL_free(entry->path); + SDL_free(new_virtual_gamepads); + } + } + } + close(fd); + } + free(entries[i]); // This should NOT be SDL_free() + } + free(entries); // This should NOT be SDL_free() + + if (num_virtual_gamepads > 1) { + SDL_qsort(virtual_gamepads, num_virtual_gamepads, sizeof(*virtual_gamepads), sort_virtual_gamepads); + } + for (i = 0; i < num_virtual_gamepads; ++i) { + MaybeAddDevice(virtual_gamepads[i].path); + SDL_free(virtual_gamepads[i].path); + } + SDL_free(virtual_gamepads); +} + +static void LINUX_ScanInputDevices(void) +{ + int i, count; + struct dirent **entries = NULL; + char path[PATH_MAX]; + + count = scandir("/dev/input", &entries, filter_entries, NULL); + if (count > 1) { + SDL_qsort(entries, count, sizeof(*entries), sort_entries); + } + for (i = 0; i < count; ++i) { + (void)SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", entries[i]->d_name); + MaybeAddDevice(path); + + free(entries[i]); // This should NOT be SDL_free() + } + free(entries); // This should NOT be SDL_free() +} + +static void LINUX_FallbackJoystickDetect(void) +{ + const Uint32 SDL_JOY_DETECT_INTERVAL_MS = 3000; // Update every 3 seconds + Uint64 now = SDL_GetTicks(); + + if (!last_joy_detect_time || now >= (last_joy_detect_time + SDL_JOY_DETECT_INTERVAL_MS)) { + struct stat sb; + + // Opening input devices can generate synchronous device I/O, so avoid it if we can + if (stat("/dev/input", &sb) == 0 && sb.st_mtime != last_input_dir_mtime) { + // Look for Steam virtual gamepads first, and sort by Steam controller slot + LINUX_ScanSteamVirtualGamepads(); + + LINUX_ScanInputDevices(); + + last_input_dir_mtime = sb.st_mtime; + } + + last_joy_detect_time = now; + } +} + +static void LINUX_JoystickDetect(void) +{ +#ifdef SDL_USE_LIBUDEV + if (enumeration_method == ENUMERATION_LIBUDEV) { + SDL_UDEV_Poll(); + } else +#endif +#ifdef HAVE_INOTIFY + if (inotify_fd >= 0 && last_joy_detect_time != 0) { + LINUX_InotifyJoystickDetect(); + } else +#endif + { + LINUX_FallbackJoystickDetect(); + } + + HandlePendingRemovals(); +} + +static bool LINUX_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers + return false; +} + +static bool LINUX_JoystickInit(void) +{ + const char *devices = SDL_GetHint(SDL_HINT_JOYSTICK_DEVICE); +#ifdef SDL_USE_LIBUDEV + bool udev_initialized = SDL_UDEV_Init(); +#endif + + SDL_classic_joysticks = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_LINUX_CLASSIC, false); + + enumeration_method = ENUMERATION_UNSET; + + // First see if the user specified one or more joysticks to use + if (devices) { + char *envcopy, *envpath, *delim; + envcopy = SDL_strdup(devices); + envpath = envcopy; + while (envpath) { + delim = SDL_strchr(envpath, ':'); + if (delim) { + *delim++ = '\0'; + } + MaybeAddDevice(envpath); + envpath = delim; + } + SDL_free(envcopy); + } + + // Force immediate joystick detection if using fallback + last_joy_detect_time = 0; + last_input_dir_mtime = 0; + + // Manually scan first, since we sort by device number and udev doesn't + LINUX_JoystickDetect(); + +#ifdef SDL_USE_LIBUDEV + if (enumeration_method == ENUMERATION_UNSET) { + if (SDL_GetHintBoolean("SDL_JOYSTICK_DISABLE_UDEV", false)) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "udev disabled by SDL_JOYSTICK_DISABLE_UDEV"); + enumeration_method = ENUMERATION_FALLBACK; + } else if (SDL_GetSandbox() != SDL_SANDBOX_NONE) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "Container detected, disabling udev integration"); + enumeration_method = ENUMERATION_FALLBACK; + + } else { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "Using udev for joystick device discovery"); + enumeration_method = ENUMERATION_LIBUDEV; + } + } + + if (enumeration_method == ENUMERATION_LIBUDEV) { + if (udev_initialized) { + // Set up the udev callback + if (!SDL_UDEV_AddCallback(joystick_udev_callback)) { + SDL_UDEV_Quit(); + return SDL_SetError("Could not set up joystick <-> udev callback"); + } + + // Force a scan to build the initial device list + SDL_UDEV_Scan(); + } else { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "udev init failed, disabling udev integration"); + enumeration_method = ENUMERATION_FALLBACK; + } + } else { + if (udev_initialized) { + SDL_UDEV_Quit(); + } + } +#endif + + if (enumeration_method != ENUMERATION_LIBUDEV) { +#ifdef HAVE_INOTIFY + inotify_fd = SDL_inotify_init1(); + + if (inotify_fd < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "Unable to initialize inotify, falling back to polling: %s", + strerror(errno)); + } else { + /* We need to watch for attribute changes in addition to + * creation, because when a device is first created, it has + * permissions that we can't read. When udev chmods it to + * something that we maybe *can* read, we'll get an + * IN_ATTRIB event to tell us. */ + if (inotify_add_watch(inotify_fd, "/dev/input", + IN_CREATE | IN_DELETE | IN_MOVE | IN_ATTRIB) < 0) { + close(inotify_fd); + inotify_fd = -1; + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "Unable to add inotify watch, falling back to polling: %s", + strerror(errno)); + } + } +#endif // HAVE_INOTIFY + } + + return true; +} + +static int LINUX_JoystickGetCount(void) +{ + SDL_AssertJoysticksLocked(); + + return numjoysticks; +} + +static SDL_joylist_item *GetJoystickByDevIndex(int device_index) +{ + SDL_joylist_item *item; + + SDL_AssertJoysticksLocked(); + + if ((device_index < 0) || (device_index >= numjoysticks)) { + return NULL; + } + + item = SDL_joylist; + while (device_index > 0) { + SDL_assert(item != NULL); + device_index--; + item = item->next; + } + + return item; +} + +static const char *LINUX_JoystickGetDeviceName(int device_index) +{ + return GetJoystickByDevIndex(device_index)->name; +} + +static const char *LINUX_JoystickGetDevicePath(int device_index) +{ + return GetJoystickByDevIndex(device_index)->path; +} + +static int LINUX_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return GetJoystickByDevIndex(device_index)->steam_virtual_gamepad_slot; +} + +static int LINUX_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void LINUX_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static SDL_GUID LINUX_JoystickGetDeviceGUID(int device_index) +{ + return GetJoystickByDevIndex(device_index)->guid; +} + +// Function to perform the mapping from device index to the instance id for this index +static SDL_JoystickID LINUX_JoystickGetDeviceInstanceID(int device_index) +{ + return GetJoystickByDevIndex(device_index)->device_instance; +} + +static bool allocate_balldata(SDL_Joystick *joystick) +{ + joystick->hwdata->balls = + (struct hwdata_ball *)SDL_calloc(joystick->nballs, sizeof(struct hwdata_ball)); + if (joystick->hwdata->balls == NULL) { + return false; + } + return true; +} + +static bool allocate_hatdata(SDL_Joystick *joystick) +{ + int i; + + SDL_AssertJoysticksLocked(); + + joystick->hwdata->hats = + (struct hwdata_hat *)SDL_malloc(joystick->nhats * + sizeof(struct hwdata_hat)); + if (!joystick->hwdata->hats) { + return false; + } + for (i = 0; i < joystick->nhats; ++i) { + joystick->hwdata->hats[i].axis[0] = 1; + joystick->hwdata->hats[i].axis[1] = 1; + } + return true; +} + +static bool GuessIfAxesAreDigitalHat(struct input_absinfo *absinfo_x, struct input_absinfo *absinfo_y) +{ + /* A "hat" is assumed to be a digital input with at most 9 possible states + * (3 per axis: negative/zero/positive), as opposed to a true "axis" which + * can report a continuous range of possible values. Unfortunately the Linux + * joystick interface makes no distinction between digital hat axes and any + * other continuous analog axis, so we have to guess. */ + + // If both axes are missing, they're not anything. + if (!absinfo_x && !absinfo_y) { + return false; + } + + // If the hint says so, treat all hats as digital. + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_LINUX_DIGITAL_HATS, false)) { + return true; + } + + // If both axes have ranges constrained between -1 and 1, they're definitely digital. + if ((!absinfo_x || (absinfo_x->minimum == -1 && absinfo_x->maximum == 1)) && (!absinfo_y || (absinfo_y->minimum == -1 && absinfo_y->maximum == 1))) { + return true; + } + + // If both axes lack fuzz, flat, and resolution values, they're probably digital. + if ((!absinfo_x || (!absinfo_x->fuzz && !absinfo_x->flat && !absinfo_x->resolution)) && (!absinfo_y || (!absinfo_y->fuzz && !absinfo_y->flat && !absinfo_y->resolution))) { + return true; + } + + // Otherwise, treat them as analog. + return false; +} + +static void ConfigJoystick(SDL_Joystick *joystick, int fd, int fd_sensor) +{ + int i, t; + unsigned long keybit[NBITS(KEY_MAX)] = { 0 }; + unsigned long absbit[NBITS(ABS_MAX)] = { 0 }; + unsigned long relbit[NBITS(REL_MAX)] = { 0 }; + unsigned long ffbit[NBITS(FF_MAX)] = { 0 }; + Uint8 key_pam_size, abs_pam_size; + bool use_deadzones = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_LINUX_DEADZONES, false); + bool use_hat_deadzones = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_LINUX_HAT_DEADZONES, true); + + SDL_AssertJoysticksLocked(); + + // See if this device uses the new unified event API + if ((ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) >= 0) && + (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) >= 0) && + (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit) >= 0)) { + + // Get the number of buttons, axes, and other thingamajigs + for (i = BTN_JOYSTICK; i < KEY_MAX; ++i) { + if (test_bit(i, keybit)) { +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has button: 0x%x", i); +#endif + joystick->hwdata->key_map[i] = joystick->nbuttons; + joystick->hwdata->has_key[i] = true; + ++joystick->nbuttons; + } + } + for (i = 0; i < BTN_JOYSTICK; ++i) { + if (test_bit(i, keybit)) { +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has button: 0x%x", i); +#endif + joystick->hwdata->key_map[i] = joystick->nbuttons; + joystick->hwdata->has_key[i] = true; + ++joystick->nbuttons; + } + } + for (i = ABS_HAT0X; i <= ABS_HAT3Y; i += 2) { + int hat_x = -1; + int hat_y = -1; + struct input_absinfo absinfo_x; + struct input_absinfo absinfo_y; + if (test_bit(i, absbit)) { + hat_x = ioctl(fd, EVIOCGABS(i), &absinfo_x); + } + if (test_bit(i + 1, absbit)) { + hat_y = ioctl(fd, EVIOCGABS(i + 1), &absinfo_y); + } + if (GuessIfAxesAreDigitalHat((hat_x < 0 ? (void *)0 : &absinfo_x), + (hat_y < 0 ? (void *)0 : &absinfo_y))) { + const int hat_index = (i - ABS_HAT0X) / 2; + struct hat_axis_correct *correct = &joystick->hwdata->hat_correct[hat_index]; +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has digital hat: #%d", hat_index); + if (hat_x >= 0) { + SDL_Log("X Values = { val:%d, min:%d, max:%d, fuzz:%d, flat:%d, res:%d }", + absinfo_x.value, absinfo_x.minimum, absinfo_x.maximum, + absinfo_x.fuzz, absinfo_x.flat, absinfo_x.resolution); + } + if (hat_y >= 0) { + SDL_Log("Y Values = { val:%d, min:%d, max:%d, fuzz:%d, flat:%d, res:%d }", + absinfo_y.value, absinfo_y.minimum, absinfo_y.maximum, + absinfo_y.fuzz, absinfo_y.flat, absinfo_y.resolution); + } +#endif // DEBUG_INPUT_EVENTS + joystick->hwdata->hats_indices[hat_index] = joystick->nhats; + joystick->hwdata->has_hat[hat_index] = true; + correct->use_deadzones = use_hat_deadzones; + correct->minimum[0] = (hat_x < 0) ? -1 : absinfo_x.minimum; + correct->maximum[0] = (hat_x < 0) ? 1 : absinfo_x.maximum; + correct->minimum[1] = (hat_y < 0) ? -1 : absinfo_y.minimum; + correct->maximum[1] = (hat_y < 0) ? 1 : absinfo_y.maximum; + ++joystick->nhats; + } + } + for (i = 0; i < ABS_MAX; ++i) { + // Skip digital hats + if (i >= ABS_HAT0X && i <= ABS_HAT3Y && joystick->hwdata->has_hat[(i - ABS_HAT0X) / 2]) { + continue; + } + if (test_bit(i, absbit)) { + struct input_absinfo absinfo; + struct axis_correct *correct = &joystick->hwdata->abs_correct[i]; + + if (ioctl(fd, EVIOCGABS(i), &absinfo) < 0) { + continue; + } +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has absolute axis: 0x%.2x", i); + SDL_Log("Values = { val:%d, min:%d, max:%d, fuzz:%d, flat:%d, res:%d }", + absinfo.value, absinfo.minimum, absinfo.maximum, + absinfo.fuzz, absinfo.flat, absinfo.resolution); +#endif // DEBUG_INPUT_EVENTS + joystick->hwdata->abs_map[i] = joystick->naxes; + joystick->hwdata->has_abs[i] = true; + + correct->minimum = absinfo.minimum; + correct->maximum = absinfo.maximum; + if (correct->minimum != correct->maximum) { + if (use_deadzones) { + correct->use_deadzones = true; + correct->coef[0] = (absinfo.maximum + absinfo.minimum) - 2 * absinfo.flat; + correct->coef[1] = (absinfo.maximum + absinfo.minimum) + 2 * absinfo.flat; + t = ((absinfo.maximum - absinfo.minimum) - 4 * absinfo.flat); + if (t != 0) { + correct->coef[2] = (1 << 28) / t; + } else { + correct->coef[2] = 0; + } + } else { + float value_range = (correct->maximum - correct->minimum); + float output_range = (SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN); + + correct->scale = (output_range / value_range); + } + } + ++joystick->naxes; + } + } + if (test_bit(REL_X, relbit) || test_bit(REL_Y, relbit)) { + ++joystick->nballs; + } + + } else if ((ioctl(fd, JSIOCGBUTTONS, &key_pam_size, sizeof(key_pam_size)) >= 0) && + (ioctl(fd, JSIOCGAXES, &abs_pam_size, sizeof(abs_pam_size)) >= 0)) { + size_t len; + + joystick->hwdata->classic = true; + + len = (KEY_MAX - BTN_MISC + 1) * sizeof(*joystick->hwdata->key_pam); + joystick->hwdata->key_pam = (Uint16 *)SDL_calloc(1, len); + if (joystick->hwdata->key_pam) { + if (ioctl(fd, JSIOCGBTNMAP, joystick->hwdata->key_pam, len) < 0) { + SDL_free(joystick->hwdata->key_pam); + joystick->hwdata->key_pam = NULL; + key_pam_size = 0; + } + } else { + key_pam_size = 0; + } + for (i = 0; i < key_pam_size; ++i) { + Uint16 code = joystick->hwdata->key_pam[i]; +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has button: 0x%x", code); +#endif + joystick->hwdata->key_map[code] = joystick->nbuttons; + joystick->hwdata->has_key[code] = true; + ++joystick->nbuttons; + } + + len = ABS_CNT * sizeof(*joystick->hwdata->abs_pam); + joystick->hwdata->abs_pam = (Uint8 *)SDL_calloc(1, len); + if (joystick->hwdata->abs_pam) { + if (ioctl(fd, JSIOCGAXMAP, joystick->hwdata->abs_pam, len) < 0) { + SDL_free(joystick->hwdata->abs_pam); + joystick->hwdata->abs_pam = NULL; + abs_pam_size = 0; + } + } else { + abs_pam_size = 0; + } + for (i = 0; i < abs_pam_size; ++i) { + Uint8 code = joystick->hwdata->abs_pam[i]; + + // TODO: is there any way to detect analog hats in advance via this API? + if (code >= ABS_HAT0X && code <= ABS_HAT3Y) { + int hat_index = (code - ABS_HAT0X) / 2; + if (!joystick->hwdata->has_hat[hat_index]) { +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has digital hat: #%d", hat_index); +#endif + joystick->hwdata->hats_indices[hat_index] = joystick->nhats++; + joystick->hwdata->has_hat[hat_index] = true; + joystick->hwdata->hat_correct[hat_index].minimum[0] = -1; + joystick->hwdata->hat_correct[hat_index].maximum[0] = 1; + joystick->hwdata->hat_correct[hat_index].minimum[1] = -1; + joystick->hwdata->hat_correct[hat_index].maximum[1] = 1; + } + } else { +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has absolute axis: 0x%.2x", code); +#endif + joystick->hwdata->abs_map[code] = joystick->naxes; + joystick->hwdata->has_abs[code] = true; + ++joystick->naxes; + } + } + } + + // Sensors are only available through the new unified event API + if (fd_sensor >= 0 && (ioctl(fd_sensor, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) >= 0)) { + if (test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit) && test_bit(ABS_Z, absbit)) { + joystick->hwdata->has_accelerometer = true; + for (i = 0; i < 3; ++i) { + struct input_absinfo absinfo; + if (ioctl(fd_sensor, EVIOCGABS(ABS_X + i), &absinfo) < 0) { + joystick->hwdata->has_accelerometer = false; + break; // do not report an accelerometer if we can't read all axes + } + joystick->hwdata->accelerometer_scale[i] = absinfo.resolution; +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has accelerometer axis: 0x%.2x", ABS_X + i); + SDL_Log("Values = { val:%d, min:%d, max:%d, fuzz:%d, flat:%d, res:%d }", + absinfo.value, absinfo.minimum, absinfo.maximum, + absinfo.fuzz, absinfo.flat, absinfo.resolution); +#endif // DEBUG_INPUT_EVENTS + } + } + + if (test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit) && test_bit(ABS_RZ, absbit)) { + joystick->hwdata->has_gyro = true; + for (i = 0; i < 3; ++i) { + struct input_absinfo absinfo; + if (ioctl(fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) < 0) { + joystick->hwdata->has_gyro = false; + break; // do not report a gyro if we can't read all axes + } + joystick->hwdata->gyro_scale[i] = absinfo.resolution; +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has gyro axis: 0x%.2x", ABS_RX + i); + SDL_Log("Values = { val:%d, min:%d, max:%d, fuzz:%d, flat:%d, res:%d }", + absinfo.value, absinfo.minimum, absinfo.maximum, + absinfo.fuzz, absinfo.flat, absinfo.resolution); +#endif // DEBUG_INPUT_EVENTS + } + } + } + + // Allocate data to keep track of these thingamajigs + if (joystick->nballs > 0) { + if (!allocate_balldata(joystick)) { + joystick->nballs = 0; + } + } + if (joystick->nhats > 0) { + if (!allocate_hatdata(joystick)) { + joystick->nhats = 0; + } + } + + if (ioctl(fd, EVIOCGBIT(EV_FF, sizeof(ffbit)), ffbit) >= 0) { + if (test_bit(FF_RUMBLE, ffbit)) { + joystick->hwdata->ff_rumble = true; + } + if (test_bit(FF_SINE, ffbit)) { + joystick->hwdata->ff_sine = true; + } + } +} + +/* This is used to do the heavy lifting for LINUX_JoystickOpen and + also LINUX_JoystickGetGamepadMapping, so we can query the hardware + without adding an opened SDL_Joystick object to the system. + This expects `joystick->hwdata` to be allocated and will not free it + on error. Returns -1 on error, 0 on success. */ +static bool PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item, SDL_sensorlist_item *item_sensor) +{ + SDL_AssertJoysticksLocked(); + + joystick->hwdata->item = item; + joystick->hwdata->item_sensor = item_sensor; + joystick->hwdata->guid = item->guid; + joystick->hwdata->effect.id = -1; + SDL_memset(joystick->hwdata->key_map, 0xFF, sizeof(joystick->hwdata->key_map)); + SDL_memset(joystick->hwdata->abs_map, 0xFF, sizeof(joystick->hwdata->abs_map)); + + int fd = -1, fd_sensor = -1; + // Try read-write first, so we can do rumble + fd = open(item->path, O_RDWR | O_CLOEXEC, 0); + if (fd < 0) { + // Try read-only again, at least we'll get events in this case + fd = open(item->path, O_RDONLY | O_CLOEXEC, 0); + } + if (fd < 0) { + return SDL_SetError("Unable to open %s", item->path); + } + // If opening sensor fail, continue with buttons and axes only + if (item_sensor) { + fd_sensor = open(item_sensor->path, O_RDONLY | O_CLOEXEC, 0); + } + + joystick->hwdata->fd = fd; + joystick->hwdata->fd_sensor = fd_sensor; + joystick->hwdata->fname = SDL_strdup(item->path); + if (!joystick->hwdata->fname) { + close(fd); + if (fd_sensor >= 0) { + close(fd_sensor); + } + return false; + } + + // Set the joystick to non-blocking read mode + fcntl(fd, F_SETFL, O_NONBLOCK); + if (fd_sensor >= 0) { + fcntl(fd_sensor, F_SETFL, O_NONBLOCK); + } + + // Get the number of buttons and axes on the joystick + ConfigJoystick(joystick, fd, fd_sensor); + return true; +} + +static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item) +{ + SDL_sensorlist_item *item_sensor; + char uniq_item[128]; + int fd_item = -1; + + SDL_AssertJoysticksLocked(); + + if (!item || !SDL_sensorlist) { + return NULL; + } + + SDL_memset(uniq_item, 0, sizeof(uniq_item)); + fd_item = open(item->path, O_RDONLY | O_CLOEXEC, 0); + if (fd_item < 0) { + return NULL; + } + if (ioctl(fd_item, EVIOCGUNIQ(sizeof(uniq_item) - 1), &uniq_item) < 0) { + close(fd_item); + return NULL; + } + close(fd_item); +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick UNIQ: %s", uniq_item); +#endif // DEBUG_INPUT_EVENTS + + for (item_sensor = SDL_sensorlist; item_sensor; item_sensor = item_sensor->next) { + char uniq_sensor[128]; + int fd_sensor = -1; + if (item_sensor->hwdata) { + // already associated with another joystick + continue; + } + + SDL_memset(uniq_sensor, 0, sizeof(uniq_sensor)); + fd_sensor = open(item_sensor->path, O_RDONLY | O_CLOEXEC, 0); + if (fd_sensor < 0) { + continue; + } + if (ioctl(fd_sensor, EVIOCGUNIQ(sizeof(uniq_sensor) - 1), &uniq_sensor) < 0) { + close(fd_sensor); + continue; + } + close(fd_sensor); +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Sensor UNIQ: %s", uniq_sensor); +#endif // DEBUG_INPUT_EVENTS + + if (SDL_strcmp(uniq_item, uniq_sensor) == 0) { + return item_sensor; + } + } + return NULL; +} + +static bool LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + SDL_joylist_item *item; + SDL_sensorlist_item *item_sensor; + + SDL_AssertJoysticksLocked(); + + item = GetJoystickByDevIndex(device_index); + if (!item) { + return SDL_SetError("No such device"); + } + + joystick->hwdata = (struct joystick_hwdata *) + SDL_calloc(1, sizeof(*joystick->hwdata)); + if (!joystick->hwdata) { + return false; + } + + item_sensor = GetSensor(item); + if (!PrepareJoystickHwdata(joystick, item, item_sensor)) { + SDL_free(joystick->hwdata); + joystick->hwdata = NULL; + return false; // SDL_SetError will already have been called + } + + SDL_assert(item->hwdata == NULL); + SDL_assert(!item_sensor || item_sensor->hwdata == NULL); + item->hwdata = joystick->hwdata; + if (item_sensor) { + item_sensor->hwdata = joystick->hwdata; + } + + // mark joystick as fresh and ready + joystick->hwdata->fresh = true; + + if (joystick->hwdata->has_gyro) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f); + } + if (joystick->hwdata->has_accelerometer) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f); + } + if (joystick->hwdata->fd_sensor >= 0) { + // Don't keep fd_sensor opened while sensor is disabled + close(joystick->hwdata->fd_sensor); + joystick->hwdata->fd_sensor = -1; + } + + if (joystick->hwdata->ff_rumble || joystick->hwdata->ff_sine) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } + return true; +} + +static bool LINUX_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + struct input_event event; + + SDL_AssertJoysticksLocked(); + + if (joystick->hwdata->ff_rumble) { + struct ff_effect *effect = &joystick->hwdata->effect; + + effect->type = FF_RUMBLE; + effect->replay.length = SDL_MAX_RUMBLE_DURATION_MS; + effect->u.rumble.strong_magnitude = low_frequency_rumble; + effect->u.rumble.weak_magnitude = high_frequency_rumble; + } else if (joystick->hwdata->ff_sine) { + // Scale and average the two rumble strengths + Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2); + struct ff_effect *effect = &joystick->hwdata->effect; + + effect->type = FF_PERIODIC; + effect->replay.length = SDL_MAX_RUMBLE_DURATION_MS; + effect->u.periodic.waveform = FF_SINE; + effect->u.periodic.magnitude = magnitude; + } else { + return SDL_Unsupported(); + } + + if (ioctl(joystick->hwdata->fd, EVIOCSFF, &joystick->hwdata->effect) < 0) { + // The kernel may have lost this effect, try to allocate a new one + joystick->hwdata->effect.id = -1; + if (ioctl(joystick->hwdata->fd, EVIOCSFF, &joystick->hwdata->effect) < 0) { + return SDL_SetError("Couldn't update rumble effect: %s", strerror(errno)); + } + } + + event.type = EV_FF; + event.code = joystick->hwdata->effect.id; + event.value = 1; + if (write(joystick->hwdata->fd, &event, sizeof(event)) < 0) { + return SDL_SetError("Couldn't start rumble effect: %s", strerror(errno)); + } + return true; +} + +static bool LINUX_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static bool LINUX_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool LINUX_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool LINUX_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + SDL_AssertJoysticksLocked(); + + if (!joystick->hwdata->has_accelerometer && !joystick->hwdata->has_gyro) { + return SDL_Unsupported(); + } + if (enabled == joystick->hwdata->report_sensor) { + return true; + } + + if (enabled) { + if (!joystick->hwdata->item_sensor) { + return SDL_SetError("Sensors unplugged."); + } + joystick->hwdata->fd_sensor = open(joystick->hwdata->item_sensor->path, O_RDONLY | O_CLOEXEC, 0); + if (joystick->hwdata->fd_sensor < 0) { + return SDL_SetError("Couldn't open sensor file %s.", joystick->hwdata->item_sensor->path); + } + fcntl(joystick->hwdata->fd_sensor, F_SETFL, O_NONBLOCK); + } else { + SDL_assert(joystick->hwdata->fd_sensor >= 0); + close(joystick->hwdata->fd_sensor); + joystick->hwdata->fd_sensor = -1; + } + + joystick->hwdata->report_sensor = enabled; + return true; +} + +static void HandleHat(Uint64 timestamp, SDL_Joystick *stick, int hatidx, int axis, int value) +{ + int hatnum; + struct hwdata_hat *the_hat; + struct hat_axis_correct *correct; + const Uint8 position_map[3][3] = { + { SDL_HAT_LEFTUP, SDL_HAT_UP, SDL_HAT_RIGHTUP }, + { SDL_HAT_LEFT, SDL_HAT_CENTERED, SDL_HAT_RIGHT }, + { SDL_HAT_LEFTDOWN, SDL_HAT_DOWN, SDL_HAT_RIGHTDOWN } + }; + + SDL_AssertJoysticksLocked(); + + hatnum = stick->hwdata->hats_indices[hatidx]; + the_hat = &stick->hwdata->hats[hatnum]; + correct = &stick->hwdata->hat_correct[hatidx]; + /* Hopefully we detected any analog axes and left them as is rather than trying + * to use them as digital hats, but just in case, the deadzones here will + * prevent the slightest of twitches on an analog axis from registering as a hat + * movement. If the axes really are digital, this won't hurt since they should + * only ever be sending min, 0, or max anyway. */ + if (value < 0) { + if (value <= correct->minimum[axis]) { + correct->minimum[axis] = value; + value = 0; + } else if (!correct->use_deadzones || value < correct->minimum[axis] / 3) { + value = 0; + } else { + value = 1; + } + } else if (value > 0) { + if (value >= correct->maximum[axis]) { + correct->maximum[axis] = value; + value = 2; + } else if (!correct->use_deadzones || value > correct->maximum[axis] / 3) { + value = 2; + } else { + value = 1; + } + } else { // value == 0 + value = 1; + } + if (value != the_hat->axis[axis]) { + the_hat->axis[axis] = value; + SDL_SendJoystickHat(timestamp, stick, hatnum, + position_map[the_hat->axis[1]][the_hat->axis[0]]); + } +} + +static void HandleBall(SDL_Joystick *stick, Uint8 ball, int axis, int value) +{ + stick->hwdata->balls[ball].axis[axis] += value; +} + +static int AxisCorrect(SDL_Joystick *joystick, int which, int value) +{ + struct axis_correct *correct; + + SDL_AssertJoysticksLocked(); + + correct = &joystick->hwdata->abs_correct[which]; + if (correct->minimum != correct->maximum) { + if (correct->use_deadzones) { + value *= 2; + if (value > correct->coef[0]) { + if (value < correct->coef[1]) { + return 0; + } + value -= correct->coef[1]; + } else { + value -= correct->coef[0]; + } + value *= correct->coef[2]; + value >>= 13; + } else { + value = (int)SDL_floorf((value - correct->minimum) * correct->scale + SDL_JOYSTICK_AXIS_MIN + 0.5f); + } + } + + // Clamp and return + if (value < SDL_JOYSTICK_AXIS_MIN) { + return SDL_JOYSTICK_AXIS_MIN; + } + if (value > SDL_JOYSTICK_AXIS_MAX) { + return SDL_JOYSTICK_AXIS_MAX; + } + return value; +} + +static void PollAllValues(Uint64 timestamp, SDL_Joystick *joystick) +{ + struct input_absinfo absinfo; + unsigned long keyinfo[NBITS(KEY_MAX)]; + int i; + + SDL_AssertJoysticksLocked(); + + // Poll all axis + for (i = ABS_X; i < ABS_MAX; i++) { + // We don't need to test for digital hats here, they won't have has_abs[] set + if (joystick->hwdata->has_abs[i]) { + if (ioctl(joystick->hwdata->fd, EVIOCGABS(i), &absinfo) >= 0) { + absinfo.value = AxisCorrect(joystick, i, absinfo.value); + +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick : Re-read Axis %d (%d) val= %d", + joystick->hwdata->abs_map[i], i, absinfo.value); +#endif + SDL_SendJoystickAxis(timestamp, joystick, + joystick->hwdata->abs_map[i], + absinfo.value); + } + } + } + + // Poll all digital hats + for (i = ABS_HAT0X; i <= ABS_HAT3Y; i++) { + const int baseaxis = i - ABS_HAT0X; + const int hatidx = baseaxis / 2; + SDL_assert(hatidx < SDL_arraysize(joystick->hwdata->has_hat)); + // We don't need to test for analog axes here, they won't have has_hat[] set + if (joystick->hwdata->has_hat[hatidx]) { + if (ioctl(joystick->hwdata->fd, EVIOCGABS(i), &absinfo) >= 0) { + const int hataxis = baseaxis % 2; + HandleHat(timestamp, joystick, hatidx, hataxis, absinfo.value); + } + } + } + + // Poll all buttons + SDL_zeroa(keyinfo); + if (ioctl(joystick->hwdata->fd, EVIOCGKEY(sizeof(keyinfo)), keyinfo) >= 0) { + for (i = 0; i < KEY_MAX; i++) { + if (joystick->hwdata->has_key[i]) { + bool down = test_bit(i, keyinfo); +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick : Re-read Button %d (%d) val= %d", + joystick->hwdata->key_map[i], i, down); +#endif + SDL_SendJoystickButton(timestamp, joystick, + joystick->hwdata->key_map[i], down); + } + } + } + + // Joyballs are relative input, so there's no poll state. Events only! +} + +static void PollAllSensors(Uint64 timestamp, SDL_Joystick *joystick) +{ + struct input_absinfo absinfo; + int i; + + SDL_AssertJoysticksLocked(); + + SDL_assert(joystick->hwdata->fd_sensor >= 0); + + if (joystick->hwdata->has_gyro) { + float data[3] = {0.0f, 0.0f, 0.0f}; + for (i = 0; i < 3; i++) { + if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) >= 0) { + data[i] = absinfo.value * (SDL_PI_F / 180.f) / joystick->hwdata->gyro_scale[i]; +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick : Re-read Gyro (axis %d) val= %f", i, data[i]); +#endif + } + } + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3); + } + if (joystick->hwdata->has_accelerometer) { + float data[3] = {0.0f, 0.0f, 0.0f}; + for (i = 0; i < 3; i++) { + if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_X + i), &absinfo) >= 0) { + data[i] = absinfo.value * SDL_STANDARD_GRAVITY / joystick->hwdata->accelerometer_scale[i]; +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick : Re-read Accelerometer (axis %d) val= %f", i, data[i]); +#endif + } + } + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3); + } +} + +static void HandleInputEvents(SDL_Joystick *joystick) +{ + struct input_event events[32]; + int i, len, code, hat_index; + + SDL_AssertJoysticksLocked(); + + if (joystick->hwdata->fresh) { + Uint64 ticks = SDL_GetTicksNS(); + PollAllValues(ticks, joystick); + if (joystick->hwdata->report_sensor) { + PollAllSensors(ticks, joystick); + } + joystick->hwdata->fresh = false; + } + + errno = 0; + + while ((len = read(joystick->hwdata->fd, events, sizeof(events))) > 0) { + len /= sizeof(events[0]); + for (i = 0; i < len; ++i) { + struct input_event *event = &events[i]; + + code = event->code; + + /* If the kernel sent a SYN_DROPPED, we are supposed to ignore the + rest of the packet (the end of it signified by a SYN_REPORT) */ + if (joystick->hwdata->recovering_from_dropped && + ((event->type != EV_SYN) || (code != SYN_REPORT))) { + continue; + } + + switch (event->type) { + case EV_KEY: +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Key 0x%.2x %s", code, event->value ? "PRESSED" : "RELEASED"); +#endif + SDL_SendJoystickButton(SDL_EVDEV_GetEventTimestamp(event), joystick, + joystick->hwdata->key_map[code], + (event->value != 0)); + break; + case EV_ABS: + switch (code) { + case ABS_HAT0X: + case ABS_HAT0Y: + case ABS_HAT1X: + case ABS_HAT1Y: + case ABS_HAT2X: + case ABS_HAT2Y: + case ABS_HAT3X: + case ABS_HAT3Y: + hat_index = (code - ABS_HAT0X) / 2; + if (joystick->hwdata->has_hat[hat_index]) { +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Axis 0x%.2x = %d", code, event->value); +#endif + HandleHat(SDL_EVDEV_GetEventTimestamp(event), joystick, hat_index, code % 2, event->value); + break; + } + SDL_FALLTHROUGH; + default: +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Axis 0x%.2x = %d", code, event->value); +#endif + event->value = AxisCorrect(joystick, code, event->value); + SDL_SendJoystickAxis(SDL_EVDEV_GetEventTimestamp(event), joystick, + joystick->hwdata->abs_map[code], + event->value); + break; + } + break; + case EV_REL: + switch (code) { + case REL_X: + case REL_Y: + code -= REL_X; + HandleBall(joystick, code / 2, code % 2, event->value); + break; + default: + break; + } + break; + case EV_SYN: + switch (code) { + case SYN_DROPPED: +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Event SYN_DROPPED detected"); +#endif + joystick->hwdata->recovering_from_dropped = true; + break; + case SYN_REPORT: + if (joystick->hwdata->recovering_from_dropped) { + joystick->hwdata->recovering_from_dropped = false; + PollAllValues(SDL_GetTicksNS(), joystick); // try to sync up to current state now + } + break; + default: + break; + } + break; + default: + break; + } + } + } + + if (errno == ENODEV) { + // We have to wait until the JoystickDetect callback to remove this + joystick->hwdata->gone = true; + errno = 0; + } + + if (joystick->hwdata->report_sensor) { + SDL_assert(joystick->hwdata->fd_sensor >= 0); + + while ((len = read(joystick->hwdata->fd_sensor, events, sizeof(events))) > 0) { + len /= sizeof(events[0]); + for (i = 0; i < len; ++i) { + unsigned int j; + struct input_event *event = &events[i]; + + code = event->code; + + /* If the kernel sent a SYN_DROPPED, we are supposed to ignore the + rest of the packet (the end of it signified by a SYN_REPORT) */ + if (joystick->hwdata->recovering_from_dropped_sensor && + ((event->type != EV_SYN) || (code != SYN_REPORT))) { + continue; + } + + switch (event->type) { + case EV_KEY: + SDL_assert(0); + break; + case EV_ABS: + switch (code) { + case ABS_X: + case ABS_Y: + case ABS_Z: + j = code - ABS_X; + joystick->hwdata->accel_data[j] = event->value * SDL_STANDARD_GRAVITY + / joystick->hwdata->accelerometer_scale[j]; + break; + case ABS_RX: + case ABS_RY: + case ABS_RZ: + j = code - ABS_RX; + joystick->hwdata->gyro_data[j] = event->value * (SDL_PI_F / 180.f) + / joystick->hwdata->gyro_scale[j]; + break; + } + break; + case EV_MSC: + if (code == MSC_TIMESTAMP) { + Sint32 tick = event->value; + Sint32 delta; + if (joystick->hwdata->last_tick < tick) { + delta = (tick - joystick->hwdata->last_tick); + } else { + delta = (SDL_MAX_SINT32 - joystick->hwdata->last_tick + tick + 1); + } + joystick->hwdata->sensor_tick += delta; + joystick->hwdata->last_tick = tick; + } + break; + case EV_SYN: + switch (code) { + case SYN_DROPPED: + #ifdef DEBUG_INPUT_EVENTS + SDL_Log("Event SYN_DROPPED detected"); + #endif + joystick->hwdata->recovering_from_dropped_sensor = true; + break; + case SYN_REPORT: + if (joystick->hwdata->recovering_from_dropped_sensor) { + joystick->hwdata->recovering_from_dropped_sensor = false; + PollAllSensors(SDL_GetTicksNS(), joystick); // try to sync up to current state now + } else { + Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, + SDL_US_TO_NS(joystick->hwdata->sensor_tick), + joystick->hwdata->gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, + SDL_US_TO_NS(joystick->hwdata->sensor_tick), + joystick->hwdata->accel_data, 3); + } + break; + default: + break; + } + break; + default: + break; + } + } + } + } + + if (errno == ENODEV) { + // We have to wait until the JoystickDetect callback to remove this + joystick->hwdata->sensor_gone = true; + } +} + +static void HandleClassicEvents(SDL_Joystick *joystick) +{ + struct js_event events[32]; + int i, len, code, hat_index; + Uint64 timestamp = SDL_GetTicksNS(); + + SDL_AssertJoysticksLocked(); + + joystick->hwdata->fresh = false; + while ((len = read(joystick->hwdata->fd, events, sizeof(events))) > 0) { + len /= sizeof(events[0]); + for (i = 0; i < len; ++i) { + switch (events[i].type) { + case JS_EVENT_BUTTON: + code = joystick->hwdata->key_pam[events[i].number]; + SDL_SendJoystickButton(timestamp, joystick, + joystick->hwdata->key_map[code], + (events[i].value != 0)); + break; + case JS_EVENT_AXIS: + code = joystick->hwdata->abs_pam[events[i].number]; + switch (code) { + case ABS_HAT0X: + case ABS_HAT0Y: + case ABS_HAT1X: + case ABS_HAT1Y: + case ABS_HAT2X: + case ABS_HAT2Y: + case ABS_HAT3X: + case ABS_HAT3Y: + hat_index = (code - ABS_HAT0X) / 2; + if (joystick->hwdata->has_hat[hat_index]) { + HandleHat(timestamp, joystick, hat_index, code % 2, events[i].value); + break; + } + SDL_FALLTHROUGH; + default: + SDL_SendJoystickAxis(timestamp, joystick, + joystick->hwdata->abs_map[code], + events[i].value); + break; + } + } + } + } +} + +static void LINUX_JoystickUpdate(SDL_Joystick *joystick) +{ + int i; + + SDL_AssertJoysticksLocked(); + + if (joystick->hwdata->classic) { + HandleClassicEvents(joystick); + } else { + HandleInputEvents(joystick); + } + + // Deliver ball motion updates + for (i = 0; i < joystick->nballs; ++i) { + int xrel, yrel; + + xrel = joystick->hwdata->balls[i].axis[0]; + yrel = joystick->hwdata->balls[i].axis[1]; + if (xrel || yrel) { + joystick->hwdata->balls[i].axis[0] = 0; + joystick->hwdata->balls[i].axis[1] = 0; + SDL_SendJoystickBall(0, joystick, (Uint8)i, xrel, yrel); + } + } +} + +// Function to close a joystick after use +static void LINUX_JoystickClose(SDL_Joystick *joystick) +{ + SDL_AssertJoysticksLocked(); + + if (joystick->hwdata) { + if (joystick->hwdata->effect.id >= 0) { + ioctl(joystick->hwdata->fd, EVIOCRMFF, joystick->hwdata->effect.id); + joystick->hwdata->effect.id = -1; + } + if (joystick->hwdata->fd >= 0) { + close(joystick->hwdata->fd); + } + if (joystick->hwdata->fd_sensor >= 0) { + close(joystick->hwdata->fd_sensor); + } + if (joystick->hwdata->item) { + joystick->hwdata->item->hwdata = NULL; + } + if (joystick->hwdata->item_sensor) { + joystick->hwdata->item_sensor->hwdata = NULL; + } + SDL_free(joystick->hwdata->key_pam); + SDL_free(joystick->hwdata->abs_pam); + SDL_free(joystick->hwdata->hats); + SDL_free(joystick->hwdata->balls); + SDL_free(joystick->hwdata->fname); + SDL_free(joystick->hwdata); + } +} + +// Function to perform any system-specific joystick related cleanup +static void LINUX_JoystickQuit(void) +{ + SDL_joylist_item *item = NULL; + SDL_joylist_item *next = NULL; + SDL_sensorlist_item *item_sensor = NULL; + SDL_sensorlist_item *next_sensor = NULL; + + SDL_AssertJoysticksLocked(); + + if (inotify_fd >= 0) { + close(inotify_fd); + inotify_fd = -1; + } + + for (item = SDL_joylist; item; item = next) { + next = item->next; + FreeJoylistItem(item); + } + for (item_sensor = SDL_sensorlist; item_sensor; item_sensor = next_sensor) { + next_sensor = item_sensor->next; + FreeSensorlistItem(item_sensor); + } + + SDL_joylist = SDL_joylist_tail = NULL; + SDL_sensorlist = NULL; + + numjoysticks = 0; + +#ifdef SDL_USE_LIBUDEV + if (enumeration_method == ENUMERATION_LIBUDEV) { + SDL_UDEV_DelCallback(joystick_udev_callback); + SDL_UDEV_Quit(); + } +#endif +} + +/* + This is based on the Linux Gamepad Specification + available at: https://www.kernel.org/doc/html/v4.15/input/gamepad.html + and the Android gamepad documentation, + https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input + */ +static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + SDL_Joystick *joystick; + SDL_joylist_item *item = GetJoystickByDevIndex(device_index); + enum { + MAPPED_TRIGGER_LEFT = 0x1, + MAPPED_TRIGGER_RIGHT = 0x2, + MAPPED_TRIGGER_BOTH = 0x3, + + MAPPED_DPAD_UP = 0x1, + MAPPED_DPAD_DOWN = 0x2, + MAPPED_DPAD_LEFT = 0x4, + MAPPED_DPAD_RIGHT = 0x8, + MAPPED_DPAD_ALL = 0xF, + }; + unsigned int mapped; + bool result = false; + + SDL_AssertJoysticksLocked(); + + if (item->checked_mapping) { + if (item->mapping) { + SDL_memcpy(out, item->mapping, sizeof(*out)); +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Prior mapping for device %d", device_index); +#endif + return true; + } else { + return false; + } + } + + /* We temporarily open the device to check how it's configured. Make + a fake SDL_Joystick object to do so. */ + joystick = (SDL_Joystick *)SDL_calloc(1, sizeof(*joystick)); + if (!joystick) { + return false; + } + SDL_memcpy(&joystick->guid, &item->guid, sizeof(item->guid)); + + joystick->hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*joystick->hwdata)); + if (!joystick->hwdata) { + SDL_free(joystick); + return false; + } + SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, true); + + item->checked_mapping = true; + + if (!PrepareJoystickHwdata(joystick, item, NULL)) { + goto done; // SDL_SetError will already have been called + } + + // don't assign `item->hwdata` so it's not in any global state. + + // it is now safe to call LINUX_JoystickClose on this fake joystick. + + if (!joystick->hwdata->has_key[BTN_GAMEPAD]) { + // Not a gamepad according to the specs. + goto done; + } + + // We have a gamepad, start filling out the mappings + +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapping %s (VID/PID 0x%.4x/0x%.4x)", item->name, SDL_GetJoystickVendor(joystick), SDL_GetJoystickProduct(joystick)); +#endif + + if (joystick->hwdata->has_key[BTN_A]) { + out->a.kind = EMappingKind_Button; + out->a.target = joystick->hwdata->key_map[BTN_A]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped A to button %d (BTN_A)", out->a.target); +#endif + } + + if (joystick->hwdata->has_key[BTN_B]) { + out->b.kind = EMappingKind_Button; + out->b.target = joystick->hwdata->key_map[BTN_B]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped B to button %d (BTN_B)", out->b.target); +#endif + } + + // Xbox controllers use BTN_X and BTN_Y, and PS4 controllers use BTN_WEST and BTN_NORTH + if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_SONY) { + if (joystick->hwdata->has_key[BTN_WEST]) { + out->x.kind = EMappingKind_Button; + out->x.target = joystick->hwdata->key_map[BTN_WEST]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped X to button %d (BTN_WEST)", out->x.target); +#endif + } + + if (joystick->hwdata->has_key[BTN_NORTH]) { + out->y.kind = EMappingKind_Button; + out->y.target = joystick->hwdata->key_map[BTN_NORTH]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped Y to button %d (BTN_NORTH)", out->y.target); +#endif + } + } else { + if (joystick->hwdata->has_key[BTN_X]) { + out->x.kind = EMappingKind_Button; + out->x.target = joystick->hwdata->key_map[BTN_X]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped X to button %d (BTN_X)", out->x.target); +#endif + } + + if (joystick->hwdata->has_key[BTN_Y]) { + out->y.kind = EMappingKind_Button; + out->y.target = joystick->hwdata->key_map[BTN_Y]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped Y to button %d (BTN_Y)", out->y.target); +#endif + } + } + + if (joystick->hwdata->has_key[BTN_SELECT]) { + out->back.kind = EMappingKind_Button; + out->back.target = joystick->hwdata->key_map[BTN_SELECT]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped BACK to button %d (BTN_SELECT)", out->back.target); +#endif + } + + if (joystick->hwdata->has_key[BTN_START]) { + out->start.kind = EMappingKind_Button; + out->start.target = joystick->hwdata->key_map[BTN_START]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped START to button %d (BTN_START)", out->start.target); +#endif + } + + if (joystick->hwdata->has_key[BTN_THUMBL]) { + out->leftstick.kind = EMappingKind_Button; + out->leftstick.target = joystick->hwdata->key_map[BTN_THUMBL]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFTSTICK to button %d (BTN_THUMBL)", out->leftstick.target); +#endif + } + + if (joystick->hwdata->has_key[BTN_THUMBR]) { + out->rightstick.kind = EMappingKind_Button; + out->rightstick.target = joystick->hwdata->key_map[BTN_THUMBR]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHTSTICK to button %d (BTN_THUMBR)", out->rightstick.target); +#endif + } + + if (joystick->hwdata->has_key[BTN_MODE]) { + out->guide.kind = EMappingKind_Button; + out->guide.target = joystick->hwdata->key_map[BTN_MODE]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped GUIDE to button %d (BTN_MODE)", out->guide.target); +#endif + } + + /* + According to the specs the D-Pad, the shoulder buttons and the triggers + can be digital, or analog, or both at the same time. + */ + + // Prefer digital shoulder buttons, but settle for digital or analog hat. + mapped = 0; + + if (joystick->hwdata->has_key[BTN_TL]) { + out->leftshoulder.kind = EMappingKind_Button; + out->leftshoulder.target = joystick->hwdata->key_map[BTN_TL]; + mapped |= 0x1; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFTSHOULDER to button %d (BTN_TL)", out->leftshoulder.target); +#endif + } + + if (joystick->hwdata->has_key[BTN_TR]) { + out->rightshoulder.kind = EMappingKind_Button; + out->rightshoulder.target = joystick->hwdata->key_map[BTN_TR]; + mapped |= 0x2; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHTSHOULDER to button %d (BTN_TR)", out->rightshoulder.target); +#endif + } + + if (mapped != 0x3 && joystick->hwdata->has_hat[1]) { + int hat = joystick->hwdata->hats_indices[1] << 4; + out->leftshoulder.kind = EMappingKind_Hat; + out->rightshoulder.kind = EMappingKind_Hat; + out->leftshoulder.target = hat | 0x4; + out->rightshoulder.target = hat | 0x2; + mapped |= 0x3; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFT+RIGHTSHOULDER to hat 1 (ABS_HAT1X, ABS_HAT1Y)"); +#endif + } + + if (!(mapped & 0x1) && joystick->hwdata->has_abs[ABS_HAT1Y]) { + out->leftshoulder.kind = EMappingKind_Axis; + out->leftshoulder.target = joystick->hwdata->abs_map[ABS_HAT1Y]; + mapped |= 0x1; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFTSHOULDER to axis %d (ABS_HAT1Y)", out->leftshoulder.target); +#endif + } + + if (!(mapped & 0x2) && joystick->hwdata->has_abs[ABS_HAT1X]) { + out->rightshoulder.kind = EMappingKind_Axis; + out->rightshoulder.target = joystick->hwdata->abs_map[ABS_HAT1X]; + mapped |= 0x2; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHTSHOULDER to axis %d (ABS_HAT1X)", out->rightshoulder.target); +#endif + } + + // Prefer analog triggers, but settle for digital hat or buttons. + mapped = 0; + + /* Unfortunately there are several conventions for how analog triggers + * are represented as absolute axes: + * + * - Linux Gamepad Specification: + * LT = ABS_HAT2Y, RT = ABS_HAT2X + * - Android (and therefore many Bluetooth controllers): + * LT = ABS_BRAKE, RT = ABS_GAS + * - De facto standard for older Xbox and Playstation controllers: + * LT = ABS_Z, RT = ABS_RZ + * + * We try each one in turn. */ + if (joystick->hwdata->has_abs[ABS_HAT2Y]) { + // Linux Gamepad Specification + out->lefttrigger.kind = EMappingKind_Axis; + out->lefttrigger.target = joystick->hwdata->abs_map[ABS_HAT2Y]; + mapped |= MAPPED_TRIGGER_LEFT; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFTTRIGGER to axis %d (ABS_HAT2Y)", out->lefttrigger.target); +#endif + } else if (joystick->hwdata->has_abs[ABS_BRAKE]) { + // Android convention + out->lefttrigger.kind = EMappingKind_Axis; + out->lefttrigger.target = joystick->hwdata->abs_map[ABS_BRAKE]; + mapped |= MAPPED_TRIGGER_LEFT; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFTTRIGGER to axis %d (ABS_BRAKE)", out->lefttrigger.target); +#endif + } else if (joystick->hwdata->has_abs[ABS_Z]) { + // De facto standard for Xbox 360 and Playstation gamepads + out->lefttrigger.kind = EMappingKind_Axis; + out->lefttrigger.target = joystick->hwdata->abs_map[ABS_Z]; + mapped |= MAPPED_TRIGGER_LEFT; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFTTRIGGER to axis %d (ABS_Z)", out->lefttrigger.target); +#endif + } + + if (joystick->hwdata->has_abs[ABS_HAT2X]) { + // Linux Gamepad Specification + out->righttrigger.kind = EMappingKind_Axis; + out->righttrigger.target = joystick->hwdata->abs_map[ABS_HAT2X]; + mapped |= MAPPED_TRIGGER_RIGHT; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHTTRIGGER to axis %d (ABS_HAT2X)", out->righttrigger.target); +#endif + } else if (joystick->hwdata->has_abs[ABS_GAS]) { + // Android convention + out->righttrigger.kind = EMappingKind_Axis; + out->righttrigger.target = joystick->hwdata->abs_map[ABS_GAS]; + mapped |= MAPPED_TRIGGER_RIGHT; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHTTRIGGER to axis %d (ABS_GAS)", out->righttrigger.target); +#endif + } else if (joystick->hwdata->has_abs[ABS_RZ]) { + // De facto standard for Xbox 360 and Playstation gamepads + out->righttrigger.kind = EMappingKind_Axis; + out->righttrigger.target = joystick->hwdata->abs_map[ABS_RZ]; + mapped |= MAPPED_TRIGGER_RIGHT; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHTTRIGGER to axis %d (ABS_RZ)", out->righttrigger.target); +#endif + } + + if (mapped != MAPPED_TRIGGER_BOTH && joystick->hwdata->has_hat[2]) { + int hat = joystick->hwdata->hats_indices[2] << 4; + out->lefttrigger.kind = EMappingKind_Hat; + out->righttrigger.kind = EMappingKind_Hat; + out->lefttrigger.target = hat | 0x4; + out->righttrigger.target = hat | 0x2; + mapped |= MAPPED_TRIGGER_BOTH; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFT+RIGHTTRIGGER to hat 2 (ABS_HAT2X, ABS_HAT2Y)"); +#endif + } + + if (!(mapped & MAPPED_TRIGGER_LEFT) && joystick->hwdata->has_key[BTN_TL2]) { + out->lefttrigger.kind = EMappingKind_Button; + out->lefttrigger.target = joystick->hwdata->key_map[BTN_TL2]; + mapped |= MAPPED_TRIGGER_LEFT; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFTTRIGGER to button %d (BTN_TL2)", out->lefttrigger.target); +#endif + } + + if (!(mapped & MAPPED_TRIGGER_RIGHT) && joystick->hwdata->has_key[BTN_TR2]) { + out->righttrigger.kind = EMappingKind_Button; + out->righttrigger.target = joystick->hwdata->key_map[BTN_TR2]; + mapped |= MAPPED_TRIGGER_RIGHT; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHTTRIGGER to button %d (BTN_TR2)", out->righttrigger.target); +#endif + } + + // Prefer digital D-Pad buttons, but settle for digital or analog hat. + mapped = 0; + + if (joystick->hwdata->has_key[BTN_DPAD_UP]) { + out->dpup.kind = EMappingKind_Button; + out->dpup.target = joystick->hwdata->key_map[BTN_DPAD_UP]; + mapped |= MAPPED_DPAD_UP; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped DPUP to button %d (BTN_DPAD_UP)", out->dpup.target); +#endif + } + + if (joystick->hwdata->has_key[BTN_DPAD_DOWN]) { + out->dpdown.kind = EMappingKind_Button; + out->dpdown.target = joystick->hwdata->key_map[BTN_DPAD_DOWN]; + mapped |= MAPPED_DPAD_DOWN; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped DPDOWN to button %d (BTN_DPAD_DOWN)", out->dpdown.target); +#endif + } + + if (joystick->hwdata->has_key[BTN_DPAD_LEFT]) { + out->dpleft.kind = EMappingKind_Button; + out->dpleft.target = joystick->hwdata->key_map[BTN_DPAD_LEFT]; + mapped |= MAPPED_DPAD_LEFT; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped DPLEFT to button %d (BTN_DPAD_LEFT)", out->dpleft.target); +#endif + } + + if (joystick->hwdata->has_key[BTN_DPAD_RIGHT]) { + out->dpright.kind = EMappingKind_Button; + out->dpright.target = joystick->hwdata->key_map[BTN_DPAD_RIGHT]; + mapped |= MAPPED_DPAD_RIGHT; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped DPRIGHT to button %d (BTN_DPAD_RIGHT)", out->dpright.target); +#endif + } + + if (mapped != MAPPED_DPAD_ALL) { + if (joystick->hwdata->has_hat[0]) { + int hat = joystick->hwdata->hats_indices[0] << 4; + out->dpleft.kind = EMappingKind_Hat; + out->dpright.kind = EMappingKind_Hat; + out->dpup.kind = EMappingKind_Hat; + out->dpdown.kind = EMappingKind_Hat; + out->dpleft.target = hat | 0x8; + out->dpright.target = hat | 0x2; + out->dpup.target = hat | 0x1; + out->dpdown.target = hat | 0x4; + mapped |= MAPPED_DPAD_ALL; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped DPUP+DOWN+LEFT+RIGHT to hat 0 (ABS_HAT0X, ABS_HAT0Y)"); +#endif + } else if (joystick->hwdata->has_abs[ABS_HAT0X] && joystick->hwdata->has_abs[ABS_HAT0Y]) { + out->dpleft.kind = EMappingKind_Axis; + out->dpright.kind = EMappingKind_Axis; + out->dpup.kind = EMappingKind_Axis; + out->dpdown.kind = EMappingKind_Axis; + out->dpleft.target = joystick->hwdata->abs_map[ABS_HAT0X]; + out->dpright.target = joystick->hwdata->abs_map[ABS_HAT0X]; + out->dpup.target = joystick->hwdata->abs_map[ABS_HAT0Y]; + out->dpdown.target = joystick->hwdata->abs_map[ABS_HAT0Y]; + mapped |= MAPPED_DPAD_ALL; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped DPUP+DOWN to axis %d (ABS_HAT0Y)", out->dpup.target); + SDL_Log("Mapped DPLEFT+RIGHT to axis %d (ABS_HAT0X)", out->dpleft.target); +#endif + } + } + + if (joystick->hwdata->has_abs[ABS_X] && joystick->hwdata->has_abs[ABS_Y]) { + out->leftx.kind = EMappingKind_Axis; + out->lefty.kind = EMappingKind_Axis; + out->leftx.target = joystick->hwdata->abs_map[ABS_X]; + out->lefty.target = joystick->hwdata->abs_map[ABS_Y]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFTX to axis %d (ABS_X)", out->leftx.target); + SDL_Log("Mapped LEFTY to axis %d (ABS_Y)", out->lefty.target); +#endif + } + + /* The Linux Gamepad Specification uses the RX and RY axes, + * originally intended to represent X and Y rotation, as a second + * joystick. This is common for USB gamepads, and also many Bluetooth + * gamepads, particularly older ones. + * + * The Android mapping convention used by many Bluetooth controllers + * instead uses the Z axis as a secondary X axis, and the RZ axis as + * a secondary Y axis. */ + if (joystick->hwdata->has_abs[ABS_RX] && joystick->hwdata->has_abs[ABS_RY]) { + // Linux Gamepad Specification, Xbox 360, Playstation etc. + out->rightx.kind = EMappingKind_Axis; + out->righty.kind = EMappingKind_Axis; + out->rightx.target = joystick->hwdata->abs_map[ABS_RX]; + out->righty.target = joystick->hwdata->abs_map[ABS_RY]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHTX to axis %d (ABS_RX)", out->rightx.target); + SDL_Log("Mapped RIGHTY to axis %d (ABS_RY)", out->righty.target); +#endif + } else if (joystick->hwdata->has_abs[ABS_Z] && joystick->hwdata->has_abs[ABS_RZ]) { + // Android convention + out->rightx.kind = EMappingKind_Axis; + out->righty.kind = EMappingKind_Axis; + out->rightx.target = joystick->hwdata->abs_map[ABS_Z]; + out->righty.target = joystick->hwdata->abs_map[ABS_RZ]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHTX to axis %d (ABS_Z)", out->rightx.target); + SDL_Log("Mapped RIGHTY to axis %d (ABS_RZ)", out->righty.target); +#endif + } + + if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) { + // The Xbox Elite controllers have the paddles as BTN_TRIGGER_HAPPY5 - BTN_TRIGGER_HAPPY8 + if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY5] && + joystick->hwdata->has_key[BTN_TRIGGER_HAPPY6] && + joystick->hwdata->has_key[BTN_TRIGGER_HAPPY7] && + joystick->hwdata->has_key[BTN_TRIGGER_HAPPY8]) { + out->right_paddle1.kind = EMappingKind_Button; + out->right_paddle1.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY5]; + out->left_paddle1.kind = EMappingKind_Button; + out->left_paddle1.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY7]; + out->right_paddle2.kind = EMappingKind_Button; + out->right_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY6]; + out->left_paddle2.kind = EMappingKind_Button; + out->left_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY8]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY5)", out->right_paddle1.target); + SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY7)", out->left_paddle1.target); + SDL_Log("Mapped RIGHT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY6)", out->right_paddle2.target); + SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY8)", out->left_paddle2.target); +#endif + } + + // The Xbox Series X controllers have the Share button as KEY_RECORD + if (joystick->hwdata->has_key[KEY_RECORD]) { + out->misc1.kind = EMappingKind_Button; + out->misc1.target = joystick->hwdata->key_map[KEY_RECORD]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target); +#endif + } + } + + // Cache the mapping for later + item->mapping = (SDL_GamepadMapping *)SDL_malloc(sizeof(*item->mapping)); + if (item->mapping) { + SDL_memcpy(item->mapping, out, sizeof(*out)); + } +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Generated mapping for device %d", device_index); +#endif + result = true; + +done: + LINUX_JoystickClose(joystick); + SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, false); + SDL_free(joystick); + + return result; +} + +SDL_JoystickDriver SDL_LINUX_JoystickDriver = { + LINUX_JoystickInit, + LINUX_JoystickGetCount, + LINUX_JoystickDetect, + LINUX_JoystickIsDevicePresent, + LINUX_JoystickGetDeviceName, + LINUX_JoystickGetDevicePath, + LINUX_JoystickGetDeviceSteamVirtualGamepadSlot, + LINUX_JoystickGetDevicePlayerIndex, + LINUX_JoystickSetDevicePlayerIndex, + LINUX_JoystickGetDeviceGUID, + LINUX_JoystickGetDeviceInstanceID, + LINUX_JoystickOpen, + LINUX_JoystickRumble, + LINUX_JoystickRumbleTriggers, + LINUX_JoystickSetLED, + LINUX_JoystickSendEffect, + LINUX_JoystickSetSensorsEnabled, + LINUX_JoystickUpdate, + LINUX_JoystickClose, + LINUX_JoystickQuit, + LINUX_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_LINUX diff --git a/contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick_c.h new file mode 100644 index 0000000..ae5384f --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick_c.h @@ -0,0 +1,117 @@ +/* + 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. +*/ + +#ifndef SDL_sysjoystick_c_h_ +#define SDL_sysjoystick_c_h_ + +#include + +struct SDL_joylist_item; +struct SDL_sensorlist_item; + +// The private structure used to keep track of a joystick +struct joystick_hwdata +{ + int fd; + // linux driver creates a separate device for gyro/accelerometer + int fd_sensor; + struct SDL_joylist_item *item; + struct SDL_sensorlist_item *item_sensor; + SDL_GUID guid; + char *fname; // Used in haptic subsystem + + bool ff_rumble; + bool ff_sine; + struct ff_effect effect; + Uint32 effect_expiration; + + // The current Linux joystick driver maps balls to two axes + struct hwdata_ball + { + int axis[2]; + } *balls; + + // The current Linux joystick driver maps hats to two axes + struct hwdata_hat + { + int axis[2]; + } *hats; + + // Support for the Linux 2.4 unified input interface + Uint8 key_map[KEY_MAX]; + Uint8 abs_map[ABS_MAX]; + bool has_key[KEY_MAX]; + bool has_abs[ABS_MAX]; + bool has_accelerometer; + bool has_gyro; + + // Support for the classic joystick interface + bool classic; + Uint16 *key_pam; + Uint8 *abs_pam; + + struct axis_correct + { + bool use_deadzones; + + // Deadzone coefficients + int coef[3]; + + // Raw coordinate scale + int minimum; + int maximum; + float scale; + } abs_correct[ABS_MAX]; + + float accelerometer_scale[3]; + float gyro_scale[3]; + + /* Each axis is read independently, if we don't get all axis this call to + * LINUX_JoystickUpdateupdate(), store them for the next one */ + float gyro_data[3]; + float accel_data[3]; + Uint64 sensor_tick; + Sint32 last_tick; + + bool report_sensor; + bool fresh; + bool recovering_from_dropped; + bool recovering_from_dropped_sensor; + + // Steam Controller support + bool m_bSteamController; + + // 4 = (ABS_HAT3X-ABS_HAT0X)/2 (see input-event-codes.h in kernel) + int hats_indices[4]; + bool has_hat[4]; + struct hat_axis_correct + { + bool use_deadzones; + int minimum[2]; + int maximum[2]; + } hat_correct[4]; + + // Set when gamepad is pending removal due to ENODEV read error + bool gone; + bool sensor_gone; +}; + +#endif // SDL_sysjoystick_c_h_ diff --git a/contrib/SDL-3.2.8/src/joystick/n3ds/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/n3ds/SDL_sysjoystick.c new file mode 100644 index 0000000..8396ac5 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/n3ds/SDL_sysjoystick.c @@ -0,0 +1,298 @@ +/* + 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_JOYSTICK_N3DS + +// This is the N3DS implementation of the SDL joystick API + +#include <3ds.h> + +#include "../SDL_sysjoystick.h" + +#define NB_BUTTONS 23 + +/* + N3DS sticks values are roughly within +/-160 + which is too small to pass the jitter tolerance. + This correction is applied to axis values + so they fit better in SDL's value range. +*/ +static inline int Correct_Axis_X(int X) { + if (X > 160) { + return SDL_JOYSTICK_AXIS_MAX; + } + else if (X < -160) { + return -SDL_JOYSTICK_AXIS_MAX; + } + return (X * SDL_JOYSTICK_AXIS_MAX) / 160; +} + +/* + The Y axis needs to be flipped because SDL's "up" + is reversed compared to libctru's "up" +*/ +static inline int Correct_Axis_Y(int Y) { + return Correct_Axis_X(-Y); +} + +static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick); +static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick); +static void UpdateN3DSCircle(Uint64 timestamp, SDL_Joystick *joystick); +static void UpdateN3DSCStick(Uint64 timestamp, SDL_Joystick *joystick); + +static bool N3DS_JoystickInit(void) +{ + hidInit(); + SDL_PrivateJoystickAdded(1); + return true; +} + +static const char *N3DS_JoystickGetDeviceName(int device_index) +{ + return "Nintendo 3DS"; +} + +static int N3DS_JoystickGetCount(void) +{ + return 1; +} + +static SDL_GUID N3DS_JoystickGetDeviceGUID(int device_index) +{ + SDL_GUID guid = SDL_CreateJoystickGUIDForName("Nintendo 3DS"); + return guid; +} + +static SDL_JoystickID N3DS_JoystickGetDeviceInstanceID(int device_index) +{ + return device_index + 1; +} + +static bool N3DS_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + joystick->nbuttons = NB_BUTTONS; + joystick->naxes = 4; + joystick->nhats = 0; + + return true; +} + +static bool N3DS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void N3DS_JoystickUpdate(SDL_Joystick *joystick) +{ + Uint64 timestamp = SDL_GetTicksNS(); + + UpdateN3DSPressedButtons(timestamp, joystick); + UpdateN3DSReleasedButtons(timestamp, joystick); + UpdateN3DSCircle(timestamp, joystick); + UpdateN3DSCStick(timestamp, joystick); +} + +static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick) +{ + static u32 previous_state = 0; + u32 updated_down; + u32 current_state = hidKeysDown(); + updated_down = previous_state ^ current_state; + if (updated_down) { + for (Uint8 i = 0; i < joystick->nbuttons; i++) { + if (current_state & BIT(i) & updated_down) { + SDL_SendJoystickButton(timestamp, joystick, i, true); + } + } + } + previous_state = current_state; +} + +static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick) +{ + static u32 previous_state = 0; + u32 updated_up; + u32 current_state = hidKeysUp(); + updated_up = previous_state ^ current_state; + if (updated_up) { + for (Uint8 i = 0; i < joystick->nbuttons; i++) { + if (current_state & BIT(i) & updated_up) { + SDL_SendJoystickButton(timestamp, joystick, i, false); + } + } + } + previous_state = current_state; +} + +static void UpdateN3DSCircle(Uint64 timestamp, SDL_Joystick *joystick) +{ + static circlePosition previous_state = { 0, 0 }; + circlePosition current_state; + hidCircleRead(¤t_state); + if (previous_state.dx != current_state.dx) { + SDL_SendJoystickAxis(timestamp, joystick, + 0, + Correct_Axis_X(current_state.dx)); + } + if (previous_state.dy != current_state.dy) { + SDL_SendJoystickAxis(timestamp, joystick, + 1, + Correct_Axis_Y(current_state.dy)); + } + previous_state = current_state; +} + +static void UpdateN3DSCStick(Uint64 timestamp, SDL_Joystick *joystick) +{ + static circlePosition previous_state = { 0, 0 }; + circlePosition current_state; + hidCstickRead(¤t_state); + if (previous_state.dx != current_state.dx) { + SDL_SendJoystickAxis(timestamp, joystick, + 2, + Correct_Axis_X(current_state.dx)); + } + if (previous_state.dy != current_state.dy) { + SDL_SendJoystickAxis(timestamp, joystick, + 3, + Correct_Axis_Y(current_state.dy)); + } + previous_state = current_state; +} + +static void N3DS_JoystickClose(SDL_Joystick *joystick) +{ +} + +static void N3DS_JoystickQuit(void) +{ + hidExit(); +} + +static bool N3DS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + // There is only one possible mapping. + *out = (SDL_GamepadMapping){ + .a = { EMappingKind_Button, 0 }, + .b = { EMappingKind_Button, 1 }, + .x = { EMappingKind_Button, 10 }, + .y = { EMappingKind_Button, 11 }, + .back = { EMappingKind_Button, 2 }, + .guide = { EMappingKind_None, 255 }, + .start = { EMappingKind_Button, 3 }, + .leftstick = { EMappingKind_None, 255 }, + .rightstick = { EMappingKind_None, 255 }, + .leftshoulder = { EMappingKind_Button, 9 }, + .rightshoulder = { EMappingKind_Button, 8 }, + .dpup = { EMappingKind_Button, 6 }, + .dpdown = { EMappingKind_Button, 7 }, + .dpleft = { EMappingKind_Button, 5 }, + .dpright = { EMappingKind_Button, 4 }, + .misc1 = { EMappingKind_None, 255 }, + .right_paddle1 = { EMappingKind_None, 255 }, + .left_paddle1 = { EMappingKind_None, 255 }, + .right_paddle2 = { EMappingKind_None, 255 }, + .left_paddle2 = { EMappingKind_None, 255 }, + .leftx = { EMappingKind_Axis, 0 }, + .lefty = { EMappingKind_Axis, 1 }, + .rightx = { EMappingKind_Axis, 2 }, + .righty = { EMappingKind_Axis, 3 }, + .lefttrigger = { EMappingKind_Button, 14 }, + .righttrigger = { EMappingKind_Button, 15 }, + }; + return true; +} + +static void N3DS_JoystickDetect(void) +{ +} + +static bool N3DS_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers + return false; +} + +static const char *N3DS_JoystickGetDevicePath(int device_index) +{ + return NULL; +} + +static int N3DS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + +static int N3DS_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void N3DS_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static bool N3DS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +static bool N3DS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static bool N3DS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool N3DS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +SDL_JoystickDriver SDL_N3DS_JoystickDriver = { + N3DS_JoystickInit, + N3DS_JoystickGetCount, + N3DS_JoystickDetect, + N3DS_JoystickIsDevicePresent, + N3DS_JoystickGetDeviceName, + N3DS_JoystickGetDevicePath, + N3DS_JoystickGetDeviceSteamVirtualGamepadSlot, + N3DS_JoystickGetDevicePlayerIndex, + N3DS_JoystickSetDevicePlayerIndex, + N3DS_JoystickGetDeviceGUID, + N3DS_JoystickGetDeviceInstanceID, + N3DS_JoystickOpen, + N3DS_JoystickRumble, + N3DS_JoystickRumbleTriggers, + N3DS_JoystickSetLED, + N3DS_JoystickSendEffect, + N3DS_JoystickSetSensorsEnabled, + N3DS_JoystickUpdate, + N3DS_JoystickClose, + N3DS_JoystickQuit, + N3DS_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_N3DS diff --git a/contrib/SDL-3.2.8/src/joystick/ps2/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/ps2/SDL_sysjoystick.c new file mode 100644 index 0000000..b938b1f --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/ps2/SDL_sysjoystick.c @@ -0,0 +1,366 @@ +/* + 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_JOYSTICK_PS2 + +// This is the PS2 implementation of the SDL joystick API +#include +#include +#include + +#include // For the definition of NULL +#include + +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" + +#define PS2_MAX_PORT 2 // each ps2 has 2 ports +#define PS2_MAX_SLOT 4 // maximum - 4 slots in one multitap +#define MAX_CONTROLLERS (PS2_MAX_PORT * PS2_MAX_SLOT) +#define PS2_ANALOG_STICKS 2 +#define PS2_ANALOG_AXIS 2 +#define PS2_BUTTONS 16 +#define PS2_TOTAL_AXIS (PS2_ANALOG_STICKS * PS2_ANALOG_AXIS) + +struct JoyInfo +{ + uint8_t padBuf[256]; + uint16_t btns; + uint8_t analog_state[PS2_TOTAL_AXIS]; + uint8_t port; + uint8_t slot; + int8_t rumble_ready; + int8_t opened; +} __attribute__((aligned(64))); + +static uint8_t enabled_pads = 0; +static struct JoyInfo joyInfo[MAX_CONTROLLERS]; + +static inline int16_t convert_u8_to_s16(uint8_t val) +{ + if (val == 0) { + return -0x7fff; + } + return val * 0x0101 - 0x8000; +} + +static inline uint8_t rumble_status(uint8_t index) +{ + char actAlign[6]; + int res; + struct JoyInfo *info = &joyInfo[index]; + + if (info->rumble_ready == 0) { + actAlign[0] = 0; + actAlign[1] = 1; + actAlign[2] = 0xff; + actAlign[3] = 0xff; + actAlign[4] = 0xff; + actAlign[5] = 0xff; + + res = padSetActAlign(info->port, info->slot, actAlign); + info->rumble_ready = res <= 0 ? -1 : 1; + } + + return info->rumble_ready == 1; +} + +// Function to scan the system for joysticks. +static bool PS2_JoystickInit(void) +{ + uint32_t port = 0; + uint32_t slot = 0; + + if (init_joystick_driver(true) < 0) { + return false; + } + + for (port = 0; port < PS2_MAX_PORT; port++) { + mtapPortOpen(port); + } + // it can fail - we dont care, we will check it more strictly when padPortOpen + + for (slot = 0; slot < PS2_MAX_SLOT; slot++) { + for (port = 0; port < PS2_MAX_PORT; port++) { + /* 2 main controller ports acts the same with and without multitap + Port 0,0 -> Connector 1 - the same as Port 0 + Port 1,0 -> Connector 2 - the same as Port 1 + Port 0,1 -> Connector 3 + Port 1,1 -> Connector 4 + Port 0,2 -> Connector 5 + Port 1,2 -> Connector 6 + Port 0,3 -> Connector 7 + Port 1,3 -> Connector 8 + */ + + struct JoyInfo *info = &joyInfo[enabled_pads]; + if (padPortOpen(port, slot, (void *)info->padBuf) > 0) { + info->port = (uint8_t)port; + info->slot = (uint8_t)slot; + info->opened = 1; + enabled_pads++; + SDL_PrivateJoystickAdded(enabled_pads); + } + } + } + + return (enabled_pads > 0); +} + +// Function to return the number of joystick devices plugged in right now +static int PS2_JoystickGetCount(void) +{ + return (int)enabled_pads; +} + +// Function to cause any queued joystick insertions to be processed +static void PS2_JoystickDetect(void) +{ +} + +static bool PS2_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers + return false; +} + +// Function to get the device-dependent name of a joystick +static const char *PS2_JoystickGetDeviceName(int index) +{ + if (index >= 0 && index < enabled_pads) { + return "PS2 Controller"; + } + + SDL_SetError("No joystick available with that index"); + return NULL; +} + +// Function to get the device-dependent path of a joystick +static const char *PS2_JoystickGetDevicePath(int index) +{ + return NULL; +} + +// Function to get the Steam virtual gamepad slot of a joystick +static int PS2_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + +// Function to get the player index of a joystick +static int PS2_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +// Function to set the player index of a joystick +static void PS2_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +// Function to return the stable GUID for a plugged in device +static SDL_GUID PS2_JoystickGetDeviceGUID(int device_index) +{ + // the GUID is just the name for now + const char *name = PS2_JoystickGetDeviceName(device_index); + return SDL_CreateJoystickGUIDForName(name); +} + +// Function to get the current instance id of the joystick located at device_index +static SDL_JoystickID PS2_JoystickGetDeviceInstanceID(int device_index) +{ + return device_index + 1; +} + +/* Function to open a joystick for use. + The joystick to open is specified by the device index. + This should fill the nbuttons and naxes fields of the joystick structure. + It returns 0, or -1 if there is an error. +*/ +static bool PS2_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + int index = joystick->instance_id; + struct JoyInfo *info = &joyInfo[index]; + + if (!info->opened) { + if (padPortOpen(info->port, info->slot, (void *)info->padBuf) > 0) { + info->opened = 1; + } else { + return false; + } + } + joystick->nbuttons = PS2_BUTTONS; + joystick->naxes = PS2_TOTAL_AXIS; + joystick->nhats = 0; + + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + + return true; +} + +// Rumble functionality +static bool PS2_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + char actAlign[6]; + int res; + int index = joystick->instance_id; + struct JoyInfo *info = &joyInfo[index]; + + if (!rumble_status(index)) { + return false; + } + + // Initial value + actAlign[0] = low_frequency_rumble >> 8; // Enable small engine + actAlign[1] = high_frequency_rumble >> 8; // Enable big engine + actAlign[2] = 0xff; + actAlign[3] = 0xff; + actAlign[4] = 0xff; + actAlign[5] = 0xff; + + res = padSetActDirect(info->port, info->slot, actAlign); + return (res == 1); +} + +// Rumble functionality +static bool PS2_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left, Uint16 right) +{ + return SDL_Unsupported(); +} + +// LED functionality +static bool PS2_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +// General effects +static bool PS2_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +// Sensor functionality +static bool PS2_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +/* Function to update the state of a joystick - called as a device poll. + * This function shouldn't update the joystick structure directly, + * but instead should call SDL_PrivateJoystick*() to deliver events + * and update joystick device state. + */ +static void PS2_JoystickUpdate(SDL_Joystick *joystick) +{ + uint8_t i; + uint8_t previous_axis, current_axis; + uint16_t mask, previous, current; + struct padButtonStatus buttons; + uint8_t all_axis[PS2_TOTAL_AXIS]; + int index = joystick->instance_id; + struct JoyInfo *info = &joyInfo[index]; + int state = padGetState(info->port, info->slot); + Uint64 timestamp = SDL_GetTicksNS(); + + if (state != PAD_STATE_DISCONN && state != PAD_STATE_EXECCMD && state != PAD_STATE_ERROR) { + int ret = padRead(info->port, info->slot, &buttons); // port, slot, buttons + if (ret != 0) { + // Buttons + int32_t pressed_buttons = 0xffff ^ buttons.btns; + ; + if (info->btns != pressed_buttons) { + for (i = 0; i < PS2_BUTTONS; i++) { + mask = (1 << i); + previous = info->btns & mask; + current = pressed_buttons & mask; + if (previous != current) { + SDL_SendJoystickButton(timestamp, joystick, i, (current != 0)); + } + } + } + info->btns = pressed_buttons; + + // Analog + all_axis[0] = buttons.ljoy_h; + all_axis[1] = buttons.ljoy_v; + all_axis[2] = buttons.rjoy_h; + all_axis[3] = buttons.rjoy_v; + + for (i = 0; i < PS2_TOTAL_AXIS; i++) { + previous_axis = info->analog_state[i]; + current_axis = all_axis[i]; + if (previous_axis != current_axis) { + SDL_SendJoystickAxis(timestamp, joystick, i, convert_u8_to_s16(current_axis)); + } + + info->analog_state[i] = current_axis; + } + } + } +} + +// Function to close a joystick after use +static void PS2_JoystickClose(SDL_Joystick *joystick) +{ + int index = joystick->instance_id; + struct JoyInfo *info = &joyInfo[index]; + padPortClose(info->port, info->slot); + info->opened = 0; +} + +// Function to perform any system-specific joystick related cleanup +static void PS2_JoystickQuit(void) +{ + deinit_joystick_driver(true); +} + +static bool PS2_GetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +SDL_JoystickDriver SDL_PS2_JoystickDriver = { + PS2_JoystickInit, + PS2_JoystickGetCount, + PS2_JoystickDetect, + PS2_JoystickIsDevicePresent, + PS2_JoystickGetDeviceName, + PS2_JoystickGetDevicePath, + PS2_JoystickGetDeviceSteamVirtualGamepadSlot, + PS2_JoystickGetDevicePlayerIndex, + PS2_JoystickSetDevicePlayerIndex, + PS2_JoystickGetDeviceGUID, + PS2_JoystickGetDeviceInstanceID, + PS2_JoystickOpen, + PS2_JoystickRumble, + PS2_JoystickRumbleTriggers, + PS2_JoystickSetLED, + PS2_JoystickSendEffect, + PS2_JoystickSetSensorsEnabled, + PS2_JoystickUpdate, + PS2_JoystickClose, + PS2_JoystickQuit, + PS2_GetGamepadMapping, +}; + +#endif // SDL_JOYSTICK_PS2 diff --git a/contrib/SDL-3.2.8/src/joystick/psp/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/psp/SDL_sysjoystick.c new file mode 100644 index 0000000..8a0154c --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/psp/SDL_sysjoystick.c @@ -0,0 +1,277 @@ +/* + 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_JOYSTICK_PSP + +// This is the PSP implementation of the SDL joystick API +#include + +#include // For the definition of NULL +#include + +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" + +// Current pad state +static SceCtrlData pad = { .Lx = 0, .Ly = 0, .Buttons = 0 }; +static const enum PspCtrlButtons button_map[] = { + PSP_CTRL_TRIANGLE, PSP_CTRL_CIRCLE, PSP_CTRL_CROSS, PSP_CTRL_SQUARE, + PSP_CTRL_LTRIGGER, PSP_CTRL_RTRIGGER, + PSP_CTRL_DOWN, PSP_CTRL_LEFT, PSP_CTRL_UP, PSP_CTRL_RIGHT, + PSP_CTRL_SELECT, PSP_CTRL_START, PSP_CTRL_HOME, PSP_CTRL_HOLD +}; +static int analog_map[256]; // Map analog inputs to -32768 -> 32767 + +// 4 points define the bezier-curve. +static SDL_Point a = { 0, 0 }; +static SDL_Point b = { 50, 0 }; +static SDL_Point c = { 78, 32767 }; +static SDL_Point d = { 128, 32767 }; + +// simple linear interpolation between two points +static SDL_INLINE void lerp(SDL_Point *dest, const SDL_Point *pt_a, const SDL_Point *pt_b, float t) +{ + dest->x = pt_a->x + (int)((pt_b->x - pt_a->x) * t); + dest->y = pt_a->y + (int)((pt_b->y - pt_a->y) * t); +} + +// evaluate a point on a bezier-curve. t goes from 0 to 1.0 +static int calc_bezier_y(float t) +{ + SDL_Point ab, bc, cd, abbc, bccd, dest; + lerp(&ab, &a, &b, t); // point between a and b + lerp(&bc, &b, &c, t); // point between b and c + lerp(&cd, &c, &d, t); // point between c and d + lerp(&abbc, &ab, &bc, t); // point between ab and bc + lerp(&bccd, &bc, &cd, t); // point between bc and cd + lerp(&dest, &abbc, &bccd, t); // point on the bezier-curve + return dest.y; +} + +/* Function to scan the system for joysticks. + * Joystick 0 should be the system default joystick. + * It should return number of joysticks, or -1 on an unrecoverable fatal error. + */ +static bool PSP_JoystickInit(void) +{ + int i; + + // Setup input + sceCtrlSetSamplingCycle(0); + sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG); + + /* Create an accurate map from analog inputs (0 to 255) + to SDL joystick positions (-32768 to 32767) */ + for (i = 0; i < 128; i++) { + float t = (float)i / 127.0f; + analog_map[i + 128] = calc_bezier_y(t); + analog_map[127 - i] = -1 * analog_map[i + 128]; + } + + SDL_PrivateJoystickAdded(1); + + return 1; +} + +static int PSP_JoystickGetCount(void) +{ + return 1; +} + +static void PSP_JoystickDetect(void) +{ +} + +static bool PSP_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers + return false; +} + +// Function to get the device-dependent name of a joystick +static const char *PSP_JoystickGetDeviceName(int device_index) +{ + if (device_index == 0) { + return "PSP builtin joypad"; + } + + SDL_SetError("No joystick available with that index"); + return NULL; +} + +static const char *PSP_JoystickGetDevicePath(int index) +{ + return NULL; +} + +static int PSP_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + +static int PSP_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void PSP_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static SDL_GUID PSP_JoystickGetDeviceGUID(int device_index) +{ + // the GUID is just the name for now + const char *name = PSP_JoystickGetDeviceName(device_index); + return SDL_CreateJoystickGUIDForName(name); +} + +// Function to perform the mapping from device index to the instance id for this index +static SDL_JoystickID PSP_JoystickGetDeviceInstanceID(int device_index) +{ + return device_index + 1; +} + +/* Function to open a joystick for use. + The joystick to open is specified by the device index. + This should fill the nbuttons and naxes fields of the joystick structure. + It returns 0, or -1 if there is an error. + */ +static bool PSP_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + joystick->nbuttons = SDL_arraysize(button_map); + joystick->naxes = 2; + joystick->nhats = 0; + + return true; +} + +static bool PSP_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +static bool PSP_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static bool PSP_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool PSP_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool PSP_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +/* Function to update the state of a joystick - called as a device poll. + * This function shouldn't update the joystick structure directly, + * but instead should call SDL_PrivateJoystick*() to deliver events + * and update joystick device state. + */ +static void PSP_JoystickUpdate(SDL_Joystick *joystick) +{ + int i; + enum PspCtrlButtons buttons; + enum PspCtrlButtons changed; + unsigned char x, y; + static enum PspCtrlButtons old_buttons = 0; + static unsigned char old_x = 0, old_y = 0; + Uint64 timestamp = SDL_GetTicksNS(); + + if (sceCtrlPeekBufferPositive(&pad, 1) <= 0) { + return; + } + buttons = pad.Buttons; + x = pad.Lx; + y = pad.Ly; + + // Axes + if (old_x != x) { + SDL_SendJoystickAxis(timestamp, joystick, 0, analog_map[x]); + old_x = x; + } + if (old_y != y) { + SDL_SendJoystickAxis(timestamp, joystick, 1, analog_map[y]); + old_y = y; + } + + // Buttons + changed = old_buttons ^ buttons; + old_buttons = buttons; + if (changed) { + for (i = 0; i < SDL_arraysize(button_map); i++) { + if (changed & button_map[i]) { + bool down = ((buttons & button_map[i]) != 0); + SDL_SendJoystickButton(timestamp, + joystick, i, down); + } + } + } +} + +// Function to close a joystick after use +static void PSP_JoystickClose(SDL_Joystick *joystick) +{ +} + +// Function to perform any system-specific joystick related cleanup +static void PSP_JoystickQuit(void) +{ +} + +static bool PSP_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +SDL_JoystickDriver SDL_PSP_JoystickDriver = { + PSP_JoystickInit, + PSP_JoystickGetCount, + PSP_JoystickDetect, + PSP_JoystickIsDevicePresent, + PSP_JoystickGetDeviceName, + PSP_JoystickGetDevicePath, + PSP_JoystickGetDeviceSteamVirtualGamepadSlot, + PSP_JoystickGetDevicePlayerIndex, + PSP_JoystickSetDevicePlayerIndex, + PSP_JoystickGetDeviceGUID, + PSP_JoystickGetDeviceInstanceID, + PSP_JoystickOpen, + PSP_JoystickRumble, + PSP_JoystickRumbleTriggers, + PSP_JoystickSetLED, + PSP_JoystickSendEffect, + PSP_JoystickSetSensorsEnabled, + PSP_JoystickUpdate, + PSP_JoystickClose, + PSP_JoystickQuit, + PSP_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_PSP diff --git a/contrib/SDL-3.2.8/src/joystick/sort_controllers.py b/contrib/SDL-3.2.8/src/joystick/sort_controllers.py new file mode 100755 index 0000000..19aec89 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/sort_controllers.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +# +# Script to sort the game controller database entries in SDL_gamepad.c + +import re + + +filename = "SDL_gamepad_db.h" +input = open(filename) +output = open(f"{filename}.new", "w") +parsing_controllers = False +controllers = [] +controller_guids = {} +conditionals = [] +split_pattern = re.compile(r'([^"]*")([^,]*,)([^,]*,)([^"]*)(".*)') +# BUS (1) CRC (3,2) VID (5,4) (6) PID (8,7) (9) VERSION (11,10) MISC (12) +standard_guid_pattern = re.compile(r'^([0-9a-fA-F]{4})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})(0000)([0-9a-fA-F]{2})([0-9a-fA-F]{2})(0000)([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{4},)$') + +# These chipsets are used in multiple controllers with different mappings, +# without enough unique information to differentiate them. e.g. +# https://github.com/gabomdq/SDL_GameControllerDB/issues/202 +invalid_controllers = ( + ('0079', '0006', '0000'), # DragonRise Inc. Generic USB Joystick + ('0079', '0006', '6120'), # DragonRise Inc. Generic USB Joystick + ('04b4', '2412', 'c529'), # Flydigi Vader 2, Vader 2 Pro, Apex 2, Apex 3, Apex 4 + ('16c0', '05e1', '0000'), # Xinmotek Controller +) + +def find_element(prefix, bindings): + i=0 + for element in bindings: + if element.startswith(prefix): + return i + i=(i + 1) + + return -1 + +def get_crc_from_entry(entry): + crc = "" + line = "".join(entry) + bindings = line.split(",") + pos = find_element("crc:", bindings) + if pos >= 0: + crc = bindings[pos][4:] + return crc + +def save_controller(line): + global controllers + match = split_pattern.match(line) + entry = [ match.group(1), match.group(2), match.group(3) ] + bindings = sorted(match.group(4).split(",")) + if (bindings[0] == ""): + bindings.pop(0) + + name = entry[2].rstrip(',') + + crc = "" + pos = find_element("crc:", bindings) + if pos >= 0: + crc = bindings[pos] + "," + bindings.pop(pos) + + guid_match = standard_guid_pattern.match(entry[1]) + if guid_match: + groups = guid_match.groups() + crc_value = groups[2] + groups[1] + vid_value = groups[4] + groups[3] + pid_value = groups[7] + groups[6] + version_value = groups[10] + groups[9] + #print("CRC: %s, VID: %s, PID: %s, VERSION: %s" % (crc_value, vid_value, pid_value, version_value)) + + if crc_value == "0000": + if crc != "": + crc_value = crc[4:-1] + else: + print("Extracting CRC from GUID of " + name) + entry[1] = groups[0] + "0000" + "".join(groups[3:]) + crc = "crc:" + crc_value + "," + + if (vid_value, pid_value, crc_value) in invalid_controllers: + print("Controller '%s' not unique, skipping" % name) + return + + pos = find_element("type", bindings) + if pos >= 0: + bindings.insert(0, bindings.pop(pos)) + + pos = find_element("platform", bindings) + if pos >= 0: + bindings.insert(0, bindings.pop(pos)) + + pos = find_element("sdk", bindings) + if pos >= 0: + bindings.append(bindings.pop(pos)) + + pos = find_element("hint:", bindings) + if pos >= 0: + bindings.append(bindings.pop(pos)) + + entry.extend(crc) + entry.extend(",".join(bindings) + ",") + entry.append(match.group(5)) + controllers.append(entry) + + entry_id = entry[1] + get_crc_from_entry(entry) + if ',sdk' in line or ',hint:' in line: + conditionals.append(entry_id) + +def write_controllers(): + global controllers + global controller_guids + # Check for duplicates + for entry in controllers: + entry_id = entry[1] + get_crc_from_entry(entry) + if (entry_id in controller_guids and entry_id not in conditionals): + current_name = entry[2] + existing_name = controller_guids[entry_id][2] + print("Warning: entry '%s' is duplicate of entry '%s'" % (current_name, existing_name)) + + if (not current_name.startswith("(DUPE)")): + entry[2] = f"(DUPE) {current_name}" + + if (not existing_name.startswith("(DUPE)")): + controller_guids[entry_id][2] = f"(DUPE) {existing_name}" + + controller_guids[entry_id] = entry + + for entry in sorted(controllers, key=lambda entry: f"{entry[2]}-{entry[1]}"): + line = "".join(entry) + "\n" + line = line.replace("\t", " ") + if not line.endswith(",\n") and not line.endswith("*/\n") and not line.endswith(",\r\n") and not line.endswith("*/\r\n"): + print("Warning: '%s' is missing a comma at the end of the line" % (line)) + output.write(line) + + controllers = [] + controller_guids = {} + +for line in input: + if parsing_controllers: + if (line.startswith("{")): + output.write(line) + elif (line.startswith(" NULL")): + parsing_controllers = False + write_controllers() + output.write(line) + elif (line.startswith("#if")): + print(f"Parsing {line.strip()}") + output.write(line) + elif ("SDL_PRIVATE_GAMEPAD_DEFINITIONS" in line): + write_controllers() + output.write(line) + elif (line.startswith("#endif")): + write_controllers() + output.write(line) + else: + save_controller(line) + else: + if (line.startswith("static const char *")): + parsing_controllers = True + + output.write(line) + +output.close() +print(f"Finished writing {filename}.new") diff --git a/contrib/SDL-3.2.8/src/joystick/usb_ids.h b/contrib/SDL-3.2.8/src/joystick/usb_ids.h new file mode 100644 index 0000000..794beb8 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/usb_ids.h @@ -0,0 +1,194 @@ +/* + 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. +*/ + +#ifndef usb_ids_h_ +#define usb_ids_h_ + +// Definitions of useful USB VID/PID values + +#define USB_VENDOR_8BITDO 0x2dc8 +#define USB_VENDOR_AMAZON 0x1949 +#define USB_VENDOR_APPLE 0x05ac +#define USB_VENDOR_ASTRO 0x9886 +#define USB_VENDOR_ASUS 0x0b05 +#define USB_VENDOR_BACKBONE 0x358a +#define USB_VENDOR_GAMESIR 0x3537 +#define USB_VENDOR_DRAGONRISE 0x0079 +#define USB_VENDOR_GOOGLE 0x18d1 +#define USB_VENDOR_HORI 0x0f0d +#define USB_VENDOR_HP 0x03f0 +#define USB_VENDOR_HYPERKIN 0x2e24 +#define USB_VENDOR_LOGITECH 0x046d +#define USB_VENDOR_MADCATZ 0x0738 +#define USB_VENDOR_MAYFLASH 0x33df +#define USB_VENDOR_MICROSOFT 0x045e +#define USB_VENDOR_NACON 0x146b +#define USB_VENDOR_NACON_ALT 0x3285 +#define USB_VENDOR_NINTENDO 0x057e +#define USB_VENDOR_NVIDIA 0x0955 +#define USB_VENDOR_PDP 0x0e6f +#define USB_VENDOR_POWERA 0x24c6 +#define USB_VENDOR_POWERA_ALT 0x20d6 +#define USB_VENDOR_QANBA 0x2c22 +#define USB_VENDOR_RAZER 0x1532 +#define USB_VENDOR_SAITEK 0x06a3 +#define USB_VENDOR_SHANWAN 0x2563 +#define USB_VENDOR_SHANWAN_ALT 0x20bc +#define USB_VENDOR_SONY 0x054c +#define USB_VENDOR_THRUSTMASTER 0x044f +#define USB_VENDOR_TURTLE_BEACH 0x10f5 +#define USB_VENDOR_SWITCH 0x2563 +#define USB_VENDOR_VALVE 0x28de +#define USB_VENDOR_ZEROPLUS 0x0c12 + +#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 0x2002 // Ultimate Wired Controller for Xbox +#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER2 0x3106 // Ultimate Wireless / Pro 2 Wired Controller +#define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419 +#define USB_PRODUCT_ASTRO_C40_XBOX360 0x0024 +#define USB_PRODUCT_BACKBONE_ONE_IOS 0x0103 +#define USB_PRODUCT_BACKBONE_ONE_IOS_PS5 0x0104 +#define USB_PRODUCT_GAMESIR_G7 0x1001 +#define USB_PRODUCT_GOOGLE_STADIA_CONTROLLER 0x9400 +#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 0x1843 +#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1846 +#define USB_PRODUCT_HORI_FIGHTING_COMMANDER_OCTA_SERIES_X 0x0150 +#define USB_PRODUCT_HORI_HORIPAD_PRO_SERIES_X 0x014f +#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS4 0x011c +#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184 +#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184 +#define USB_PRODUCT_HORI_STEAM_CONTROLLER 0x01AB +#define USB_PRODUCT_HORI_STEAM_CONTROLLER_BT 0x0196 +#define USB_PRODUCT_LOGITECH_F310 0xc216 +#define USB_PRODUCT_LOGITECH_CHILLSTREAM 0xcad1 +#define USB_PRODUCT_MADCATZ_SAITEK_SIDE_PANEL_CONTROL_DECK 0x2218 +#define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRELESS 0x0d16 +#define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRED 0x0d17 +#define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS 0x0d18 +#define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED 0x0d19 +#define USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER 0x0337 +#define USB_PRODUCT_NINTENDO_N64_CONTROLLER 0x2019 +#define USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER 0x201e +#define USB_PRODUCT_NINTENDO_SNES_CONTROLLER 0x2017 +#define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP 0x200e +#define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT 0x2006 +#define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR 0x2008 // Used by joycond +#define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT 0x2007 +#define USB_PRODUCT_NINTENDO_SWITCH_PRO 0x2009 +#define USB_PRODUCT_NINTENDO_WII_REMOTE 0x0306 +#define USB_PRODUCT_NINTENDO_WII_REMOTE2 0x0330 +#define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103 0x7210 +#define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104 0x7214 +#define USB_PRODUCT_RAZER_ATROX 0x0a00 +#define USB_PRODUCT_RAZER_KITSUNE 0x1012 +#define USB_PRODUCT_RAZER_PANTHERA 0x0401 +#define USB_PRODUCT_RAZER_PANTHERA_EVO 0x1008 +#define USB_PRODUCT_RAZER_RAIJU 0x1000 +#define USB_PRODUCT_RAZER_TOURNAMENT_EDITION_USB 0x1007 +#define USB_PRODUCT_RAZER_TOURNAMENT_EDITION_BLUETOOTH 0x100a +#define USB_PRODUCT_RAZER_ULTIMATE_EDITION_USB 0x1004 +#define USB_PRODUCT_RAZER_ULTIMATE_EDITION_BLUETOOTH 0x1009 +#define USB_PRODUCT_RAZER_WOLVERINE_V2 0x0a29 +#define USB_PRODUCT_RAZER_WOLVERINE_V2_CHROMA 0x0a2e +#define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRED 0x100b +#define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRELESS 0x100c +#define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_XBOX_WIRED 0x1010 +#define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_XBOX_WIRELESS 0x1011 +#define USB_PRODUCT_RAZER_WOLVERINE_V3_PRO 0x0a3f +#define USB_PRODUCT_ROG_RAIKIRI 0x1a38 +#define USB_PRODUCT_SAITEK_CYBORG_V3 0xf622 +#define USB_PRODUCT_SHANWAN_DS3 0x0523 +#define USB_PRODUCT_SONY_DS3 0x0268 +#define USB_PRODUCT_SONY_DS4 0x05c4 +#define USB_PRODUCT_SONY_DS4_DONGLE 0x0ba0 +#define USB_PRODUCT_SONY_DS4_SLIM 0x09cc +#define USB_PRODUCT_SONY_DS4_STRIKEPAD 0x05c5 +#define USB_PRODUCT_SONY_DS5 0x0ce6 +#define USB_PRODUCT_SONY_DS5_EDGE 0x0df2 +#define USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER 0x0575 +#define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_PS4 0xd00e +#define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_SERIES_X 0xd012 +#define USB_PRODUCT_TURTLE_BEACH_SERIES_X_REACT_R 0x7013 +#define USB_PRODUCT_TURTLE_BEACH_SERIES_X_RECON 0x7009 +#define USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE 0x1142 +#define USB_PRODUCT_VICTRIX_FS_PRO 0x0203 +#define USB_PRODUCT_VICTRIX_FS_PRO_V2 0x0207 +#define USB_PRODUCT_XBOX360_XUSB_CONTROLLER 0x02a1 // XUSB driver software PID +#define USB_PRODUCT_XBOX360_WIRED_CONTROLLER 0x028e +#define USB_PRODUCT_XBOX360_WIRELESS_RECEIVER 0x0719 +#define USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY1 0x02a9 +#define USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 0x0291 +#define USB_PRODUCT_XBOX_ONE_ADAPTIVE 0x0b0a +#define USB_PRODUCT_XBOX_ONE_ADAPTIVE_BLUETOOTH 0x0b0c +#define USB_PRODUCT_XBOX_ONE_ADAPTIVE_BLE 0x0b21 +#define USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1 0x02e3 +#define USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2 0x0b00 +#define USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH 0x0b05 +#define USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLE 0x0b22 +#define USB_PRODUCT_XBOX_ONE_S 0x02ea +#define USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH 0x02e0 +#define USB_PRODUCT_XBOX_ONE_S_REV2_BLUETOOTH 0x02fd +#define USB_PRODUCT_XBOX_ONE_S_REV2_BLE 0x0b20 +#define USB_PRODUCT_XBOX_SERIES_X 0x0b12 +#define USB_PRODUCT_XBOX_SERIES_X_BLE 0x0b13 +#define USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX 0x08b6 +#define USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX_RGB 0x07a0 +#define USB_PRODUCT_XBOX_SERIES_X_PDP_AFTERGLOW 0x02da +#define USB_PRODUCT_XBOX_SERIES_X_PDP_BLUE 0x02d9 +#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO2 0x4001 +#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO4 0x400b +#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_USB 0x4014 +#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_DONGLE 0x4016 +#define USB_PRODUCT_XBOX_SERIES_X_POWERA_MOGA_XP_ULTRA 0x890b +#define USB_PRODUCT_XBOX_SERIES_X_POWERA_SPECTRA 0x4002 +#define USB_PRODUCT_XBOX_SERIES_X_VICTRIX_GAMBIT 0x02d6 +#define USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER 0x02ff // XBOXGIP driver software PID +#define USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD 0x11ff + +// USB usage pages +#define USB_USAGEPAGE_GENERIC_DESKTOP 0x0001 +#define USB_USAGEPAGE_BUTTON 0x0009 + +// USB usages for USAGE_PAGE_GENERIC_DESKTOP +#define USB_USAGE_GENERIC_POINTER 0x0001 +#define USB_USAGE_GENERIC_MOUSE 0x0002 +#define USB_USAGE_GENERIC_JOYSTICK 0x0004 +#define USB_USAGE_GENERIC_GAMEPAD 0x0005 +#define USB_USAGE_GENERIC_KEYBOARD 0x0006 +#define USB_USAGE_GENERIC_KEYPAD 0x0007 +#define USB_USAGE_GENERIC_MULTIAXISCONTROLLER 0x0008 +#define USB_USAGE_GENERIC_X 0x0030 +#define USB_USAGE_GENERIC_Y 0x0031 +#define USB_USAGE_GENERIC_Z 0x0032 +#define USB_USAGE_GENERIC_RX 0x0033 +#define USB_USAGE_GENERIC_RY 0x0034 +#define USB_USAGE_GENERIC_RZ 0x0035 +#define USB_USAGE_GENERIC_SLIDER 0x0036 +#define USB_USAGE_GENERIC_DIAL 0x0037 +#define USB_USAGE_GENERIC_WHEEL 0x0038 +#define USB_USAGE_GENERIC_HAT 0x0039 + +/* Bluetooth SIG assigned Company Identifiers + https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/ */ +#define BLUETOOTH_VENDOR_AMAZON 0x0171 + +#define BLUETOOTH_PRODUCT_LUNA_CONTROLLER 0x0419 + +#endif // usb_ids_h_ diff --git a/contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick.c b/contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick.c new file mode 100644 index 0000000..6925662 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick.c @@ -0,0 +1,990 @@ +/* + 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_JOYSTICK_VIRTUAL + +// This is the virtual implementation of the SDL joystick API + +#include "SDL_virtualjoystick_c.h" +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" + +static joystick_hwdata *g_VJoys SDL_GUARDED_BY(SDL_joystick_lock) = NULL; + +static joystick_hwdata *VIRTUAL_HWDataForInstance(SDL_JoystickID instance_id) +{ + joystick_hwdata *vjoy; + + SDL_AssertJoysticksLocked(); + + for (vjoy = g_VJoys; vjoy; vjoy = vjoy->next) { + if (instance_id == vjoy->instance_id) { + return vjoy; + } + } + return NULL; +} + +static joystick_hwdata *VIRTUAL_HWDataForIndex(int device_index) +{ + joystick_hwdata *vjoy; + + SDL_AssertJoysticksLocked(); + + for (vjoy = g_VJoys; vjoy; vjoy = vjoy->next) { + if (device_index == 0) { + break; + } + --device_index; + } + return vjoy; +} + +static void VIRTUAL_FreeHWData(joystick_hwdata *hwdata) +{ + joystick_hwdata *cur; + joystick_hwdata *prev = NULL; + + SDL_AssertJoysticksLocked(); + + if (!hwdata) { + return; + } + + if (hwdata->desc.Cleanup) { + hwdata->desc.Cleanup(hwdata->desc.userdata); + } + + // Remove hwdata from SDL-global list + for (cur = g_VJoys; cur; prev = cur, cur = cur->next) { + if (hwdata == cur) { + if (prev) { + prev->next = cur->next; + } else { + g_VJoys = cur->next; + } + break; + } + } + + if (hwdata->joystick) { + hwdata->joystick->hwdata = NULL; + hwdata->joystick = NULL; + } + if (hwdata->name) { + SDL_free(hwdata->name); + hwdata->name = NULL; + } + if (hwdata->axes) { + SDL_free((void *)hwdata->axes); + hwdata->axes = NULL; + } + if (hwdata->buttons) { + SDL_free(hwdata->buttons); + hwdata->buttons = NULL; + } + if (hwdata->hats) { + SDL_free(hwdata->hats); + hwdata->hats = NULL; + } + if (hwdata->balls) { + SDL_free(hwdata->balls); + hwdata->balls = NULL; + } + if (hwdata->touchpads) { + for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) { + SDL_free(hwdata->touchpads[i].fingers); + hwdata->touchpads[i].fingers = NULL; + } + SDL_free(hwdata->touchpads); + hwdata->touchpads = NULL; + } + if (hwdata->sensors) { + SDL_free(hwdata->sensors); + hwdata->sensors = NULL; + } + if (hwdata->sensor_events) { + SDL_free(hwdata->sensor_events); + hwdata->sensor_events = NULL; + } + SDL_free(hwdata); +} + +SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *desc) +{ + joystick_hwdata *hwdata = NULL; + const char *name = NULL; + int axis_triggerleft = -1; + int axis_triggerright = -1; + + SDL_AssertJoysticksLocked(); + + if (!desc) { + SDL_InvalidParamError("desc"); + return 0; + } + if (desc->version < sizeof(*desc)) { + // Update this to handle older versions of this interface + SDL_SetError("Invalid desc, should be initialized with SDL_INIT_INTERFACE()"); + return 0; + } + + hwdata = (joystick_hwdata *)SDL_calloc(1, sizeof(joystick_hwdata)); + if (!hwdata) { + VIRTUAL_FreeHWData(hwdata); + return 0; + } + SDL_copyp(&hwdata->desc, desc); + hwdata->desc.touchpads = NULL; + hwdata->desc.sensors = NULL; + + if (hwdata->desc.name) { + name = hwdata->desc.name; + } else { + switch (hwdata->desc.type) { + case SDL_JOYSTICK_TYPE_GAMEPAD: + name = "Virtual Controller"; + break; + case SDL_JOYSTICK_TYPE_WHEEL: + name = "Virtual Wheel"; + break; + case SDL_JOYSTICK_TYPE_ARCADE_STICK: + name = "Virtual Arcade Stick"; + break; + case SDL_JOYSTICK_TYPE_FLIGHT_STICK: + name = "Virtual Flight Stick"; + break; + case SDL_JOYSTICK_TYPE_DANCE_PAD: + name = "Virtual Dance Pad"; + break; + case SDL_JOYSTICK_TYPE_GUITAR: + name = "Virtual Guitar"; + break; + case SDL_JOYSTICK_TYPE_DRUM_KIT: + name = "Virtual Drum Kit"; + break; + case SDL_JOYSTICK_TYPE_ARCADE_PAD: + name = "Virtual Arcade Pad"; + break; + case SDL_JOYSTICK_TYPE_THROTTLE: + name = "Virtual Throttle"; + break; + default: + name = "Virtual Joystick"; + break; + } + } + hwdata->name = SDL_strdup(name); + + if (hwdata->desc.type == SDL_JOYSTICK_TYPE_GAMEPAD) { + int i, axis; + + if (hwdata->desc.button_mask == 0) { + for (i = 0; i < hwdata->desc.nbuttons && i < sizeof(hwdata->desc.button_mask) * 8; ++i) { + hwdata->desc.button_mask |= (1 << i); + } + } + + if (hwdata->desc.axis_mask == 0) { + if (hwdata->desc.naxes >= 2) { + hwdata->desc.axis_mask |= ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY)); + } + if (hwdata->desc.naxes >= 4) { + hwdata->desc.axis_mask |= ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY)); + } + if (hwdata->desc.naxes >= 6) { + hwdata->desc.axis_mask |= ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)); + } + } + + // Find the trigger axes + axis = 0; + for (i = 0; axis < hwdata->desc.naxes && i < SDL_GAMEPAD_AXIS_COUNT; ++i) { + if (hwdata->desc.axis_mask & (1 << i)) { + if (i == SDL_GAMEPAD_AXIS_LEFT_TRIGGER) { + axis_triggerleft = axis; + } + if (i == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { + axis_triggerright = axis; + } + ++axis; + } + } + } + + hwdata->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_VIRTUAL, hwdata->desc.vendor_id, hwdata->desc.product_id, 0, NULL, name, 'v', (Uint8)hwdata->desc.type); + + // Allocate fields for different control-types + if (hwdata->desc.naxes > 0) { + hwdata->axes = (Sint16 *)SDL_calloc(hwdata->desc.naxes, sizeof(*hwdata->axes)); + if (!hwdata->axes) { + VIRTUAL_FreeHWData(hwdata); + return 0; + } + + // Trigger axes are at minimum value at rest + if (axis_triggerleft >= 0) { + hwdata->axes[axis_triggerleft] = SDL_JOYSTICK_AXIS_MIN; + } + if (axis_triggerright >= 0) { + hwdata->axes[axis_triggerright] = SDL_JOYSTICK_AXIS_MIN; + } + } + if (hwdata->desc.nbuttons > 0) { + hwdata->buttons = (bool *)SDL_calloc(hwdata->desc.nbuttons, sizeof(*hwdata->buttons)); + if (!hwdata->buttons) { + VIRTUAL_FreeHWData(hwdata); + return 0; + } + } + if (hwdata->desc.nhats > 0) { + hwdata->hats = (Uint8 *)SDL_calloc(hwdata->desc.nhats, sizeof(*hwdata->hats)); + if (!hwdata->hats) { + VIRTUAL_FreeHWData(hwdata); + return 0; + } + } + if (hwdata->desc.nballs > 0) { + hwdata->balls = (SDL_JoystickBallData *)SDL_calloc(hwdata->desc.nballs, sizeof(*hwdata->balls)); + if (!hwdata->balls) { + VIRTUAL_FreeHWData(hwdata); + return 0; + } + } + if (hwdata->desc.ntouchpads > 0) { + if (!desc->touchpads) { + VIRTUAL_FreeHWData(hwdata); + SDL_SetError("desc missing touchpad descriptions"); + return 0; + } + hwdata->touchpads = (SDL_JoystickTouchpadInfo *)SDL_calloc(hwdata->desc.ntouchpads, sizeof(*hwdata->touchpads)); + if (!hwdata->touchpads) { + VIRTUAL_FreeHWData(hwdata); + return 0; + } + for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) { + const SDL_VirtualJoystickTouchpadDesc *touchpad_desc = &desc->touchpads[i]; + hwdata->touchpads[i].nfingers = touchpad_desc->nfingers; + hwdata->touchpads[i].fingers = (SDL_JoystickTouchpadFingerInfo *)SDL_calloc(touchpad_desc->nfingers, sizeof(*hwdata->touchpads[i].fingers)); + if (!hwdata->touchpads[i].fingers) { + VIRTUAL_FreeHWData(hwdata); + return 0; + } + } + } + if (hwdata->desc.nsensors > 0) { + if (!desc->sensors) { + VIRTUAL_FreeHWData(hwdata); + SDL_SetError("desc missing sensor descriptions"); + return 0; + } + hwdata->sensors = (SDL_JoystickSensorInfo *)SDL_calloc(hwdata->desc.nsensors, sizeof(*hwdata->sensors)); + if (!hwdata->sensors) { + VIRTUAL_FreeHWData(hwdata); + return 0; + } + for (Uint16 i = 0; i < hwdata->desc.nsensors; ++i) { + const SDL_VirtualJoystickSensorDesc *sensor_desc = &desc->sensors[i]; + hwdata->sensors[i].type = sensor_desc->type; + hwdata->sensors[i].rate = sensor_desc->rate; + } + } + + // Allocate an instance ID for this device + hwdata->instance_id = SDL_GetNextObjectID(); + + // Add virtual joystick to SDL-global lists + if (g_VJoys) { + joystick_hwdata *last; + + for (last = g_VJoys; last->next; last = last->next) { + } + last->next = hwdata; + } else { + g_VJoys = hwdata; + } + SDL_PrivateJoystickAdded(hwdata->instance_id); + + return hwdata->instance_id; +} + +bool SDL_JoystickDetachVirtualInner(SDL_JoystickID instance_id) +{ + joystick_hwdata *hwdata = VIRTUAL_HWDataForInstance(instance_id); + if (!hwdata) { + return SDL_SetError("Virtual joystick data not found"); + } + VIRTUAL_FreeHWData(hwdata); + SDL_PrivateJoystickRemoved(instance_id); + return true; +} + +bool SDL_SetJoystickVirtualAxisInner(SDL_Joystick *joystick, int axis, Sint16 value) +{ + joystick_hwdata *hwdata; + + SDL_AssertJoysticksLocked(); + + if (!joystick || !joystick->hwdata) { + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (axis < 0 || axis >= hwdata->desc.naxes) { + return SDL_SetError("Invalid axis index"); + } + + hwdata->axes[axis] = value; + hwdata->changes |= AXES_CHANGED; + + return true; +} + +bool SDL_SetJoystickVirtualBallInner(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel) +{ + joystick_hwdata *hwdata; + + SDL_AssertJoysticksLocked(); + + if (!joystick || !joystick->hwdata) { + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (ball < 0 || ball >= hwdata->desc.nballs) { + return SDL_SetError("Invalid ball index"); + } + + hwdata->balls[ball].dx += xrel; + hwdata->balls[ball].dx = SDL_clamp(hwdata->balls[ball].dx, SDL_MIN_SINT16, SDL_MAX_SINT16); + hwdata->balls[ball].dy += yrel; + hwdata->balls[ball].dy = SDL_clamp(hwdata->balls[ball].dy, SDL_MIN_SINT16, SDL_MAX_SINT16); + hwdata->changes |= BALLS_CHANGED; + + return true; +} + +bool SDL_SetJoystickVirtualButtonInner(SDL_Joystick *joystick, int button, bool down) +{ + joystick_hwdata *hwdata; + + SDL_AssertJoysticksLocked(); + + if (!joystick || !joystick->hwdata) { + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (button < 0 || button >= hwdata->desc.nbuttons) { + return SDL_SetError("Invalid button index"); + } + + hwdata->buttons[button] = down; + hwdata->changes |= BUTTONS_CHANGED; + + return true; +} + +bool SDL_SetJoystickVirtualHatInner(SDL_Joystick *joystick, int hat, Uint8 value) +{ + joystick_hwdata *hwdata; + + SDL_AssertJoysticksLocked(); + + if (!joystick || !joystick->hwdata) { + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (hat < 0 || hat >= hwdata->desc.nhats) { + return SDL_SetError("Invalid hat index"); + } + + hwdata->hats[hat] = value; + hwdata->changes |= HATS_CHANGED; + + return true; +} + +bool SDL_SetJoystickVirtualTouchpadInner(SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure) +{ + joystick_hwdata *hwdata; + + SDL_AssertJoysticksLocked(); + + if (!joystick || !joystick->hwdata) { + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (touchpad < 0 || touchpad >= hwdata->desc.ntouchpads) { + return SDL_SetError("Invalid touchpad index"); + } + if (finger < 0 || finger >= hwdata->touchpads[touchpad].nfingers) { + return SDL_SetError("Invalid finger index"); + } + + SDL_JoystickTouchpadFingerInfo *info = &hwdata->touchpads[touchpad].fingers[finger]; + info->down = down; + info->x = x; + info->y = y; + info->pressure = pressure; + hwdata->changes |= TOUCHPADS_CHANGED; + + return true; +} + +bool SDL_SendJoystickVirtualSensorDataInner(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values) +{ + joystick_hwdata *hwdata; + + SDL_AssertJoysticksLocked(); + + if (!joystick || !joystick->hwdata) { + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (hwdata->num_sensor_events == hwdata->max_sensor_events) { + int new_max_sensor_events = (hwdata->max_sensor_events + 1); + VirtualSensorEvent *sensor_events = (VirtualSensorEvent *)SDL_realloc(hwdata->sensor_events, new_max_sensor_events * sizeof(*sensor_events)); + if (!sensor_events) { + return false; + } + hwdata->sensor_events = sensor_events; + hwdata->max_sensor_events = hwdata->max_sensor_events; + } + + VirtualSensorEvent *event = &hwdata->sensor_events[hwdata->num_sensor_events++]; + event->type = type; + event->sensor_timestamp = sensor_timestamp; + event->num_values = SDL_min(num_values, SDL_arraysize(event->data)); + SDL_memcpy(event->data, data, (event->num_values * sizeof(*event->data))); + + return true; +} + +static bool VIRTUAL_JoystickInit(void) +{ + return true; +} + +static int VIRTUAL_JoystickGetCount(void) +{ + joystick_hwdata *cur; + int count = 0; + + SDL_AssertJoysticksLocked(); + + for (cur = g_VJoys; cur; cur = cur->next) { + ++count; + } + return count; +} + +static void VIRTUAL_JoystickDetect(void) +{ +} + +static bool VIRTUAL_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers... or do we? + return false; +} + +static const char *VIRTUAL_JoystickGetDeviceName(int device_index) +{ + joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); + if (!hwdata) { + return NULL; + } + return hwdata->name; +} + +static const char *VIRTUAL_JoystickGetDevicePath(int device_index) +{ + return NULL; +} + +static int VIRTUAL_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + +static int VIRTUAL_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void VIRTUAL_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ + joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); + + if (hwdata && hwdata->desc.SetPlayerIndex) { + hwdata->desc.SetPlayerIndex(hwdata->desc.userdata, player_index); + } +} + +static SDL_GUID VIRTUAL_JoystickGetDeviceGUID(int device_index) +{ + joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); + if (!hwdata) { + SDL_GUID guid; + SDL_zero(guid); + return guid; + } + return hwdata->guid; +} + +static SDL_JoystickID VIRTUAL_JoystickGetDeviceInstanceID(int device_index) +{ + joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); + if (!hwdata) { + return true; + } + return hwdata->instance_id; +} + +static bool VIRTUAL_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + joystick_hwdata *hwdata; + + SDL_AssertJoysticksLocked(); + + hwdata = VIRTUAL_HWDataForIndex(device_index); + if (!hwdata) { + return SDL_SetError("No such device"); + } + joystick->hwdata = hwdata; + joystick->naxes = hwdata->desc.naxes; + joystick->nbuttons = hwdata->desc.nbuttons; + joystick->nhats = hwdata->desc.nhats; + hwdata->joystick = joystick; + + for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) { + const SDL_JoystickTouchpadInfo *touchpad = &hwdata->touchpads[i]; + SDL_PrivateJoystickAddTouchpad(joystick, touchpad->nfingers); + } + for (Uint16 i = 0; i < hwdata->desc.nsensors; ++i) { + const SDL_JoystickSensorInfo *sensor = &hwdata->sensors[i]; + SDL_PrivateJoystickAddSensor(joystick, sensor->type, sensor->rate); + } + + if (hwdata->desc.SetLED) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true); + } + if (hwdata->desc.Rumble) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } + if (hwdata->desc.RumbleTriggers) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); + } + return true; +} + +static bool VIRTUAL_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + bool result; + + SDL_AssertJoysticksLocked(); + + if (joystick->hwdata) { + joystick_hwdata *hwdata = joystick->hwdata; + if (hwdata->desc.Rumble) { + result = hwdata->desc.Rumble(hwdata->desc.userdata, low_frequency_rumble, high_frequency_rumble); + } else { + result = SDL_Unsupported(); + } + } else { + result = SDL_SetError("Rumble failed, device disconnected"); + } + + return result; +} + +static bool VIRTUAL_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + bool result; + + SDL_AssertJoysticksLocked(); + + if (joystick->hwdata) { + joystick_hwdata *hwdata = joystick->hwdata; + if (hwdata->desc.RumbleTriggers) { + result = hwdata->desc.RumbleTriggers(hwdata->desc.userdata, left_rumble, right_rumble); + } else { + result = SDL_Unsupported(); + } + } else { + result = SDL_SetError("Rumble failed, device disconnected"); + } + + return result; +} + +static bool VIRTUAL_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + bool result; + + SDL_AssertJoysticksLocked(); + + if (joystick->hwdata) { + joystick_hwdata *hwdata = joystick->hwdata; + if (hwdata->desc.SetLED) { + result = hwdata->desc.SetLED(hwdata->desc.userdata, red, green, blue); + } else { + result = SDL_Unsupported(); + } + } else { + result = SDL_SetError("SetLED failed, device disconnected"); + } + + return result; +} + +static bool VIRTUAL_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + bool result; + + SDL_AssertJoysticksLocked(); + + if (joystick->hwdata) { + joystick_hwdata *hwdata = joystick->hwdata; + if (hwdata->desc.SendEffect) { + result = hwdata->desc.SendEffect(hwdata->desc.userdata, data, size); + } else { + result = SDL_Unsupported(); + } + } else { + result = SDL_SetError("SendEffect failed, device disconnected"); + } + + return result; +} + +static bool VIRTUAL_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + bool result; + + SDL_AssertJoysticksLocked(); + + if (joystick->hwdata) { + joystick_hwdata *hwdata = joystick->hwdata; + if (hwdata->desc.SetSensorsEnabled) { + result = hwdata->desc.SetSensorsEnabled(hwdata->desc.userdata, enabled); + } else { + result = true; + } + if (result) { + hwdata->sensors_enabled = enabled; + } + } else { + result = SDL_SetError("SetSensorsEnabled failed, device disconnected"); + } + + return result; +} + +static void VIRTUAL_JoystickUpdate(SDL_Joystick *joystick) +{ + joystick_hwdata *hwdata; + Uint64 timestamp = SDL_GetTicksNS(); + + SDL_AssertJoysticksLocked(); + + if (!joystick) { + return; + } + if (!joystick->hwdata) { + return; + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + + if (hwdata->desc.Update) { + hwdata->desc.Update(hwdata->desc.userdata); + } + + if (hwdata->changes & AXES_CHANGED) { + for (Uint8 i = 0; i < hwdata->desc.naxes; ++i) { + SDL_SendJoystickAxis(timestamp, joystick, i, hwdata->axes[i]); + } + } + if (hwdata->changes & BALLS_CHANGED) { + for (Uint8 i = 0; i < hwdata->desc.nballs; ++i) { + SDL_JoystickBallData *ball = &hwdata->balls[i]; + if (ball->dx || ball->dy) { + SDL_SendJoystickBall(timestamp, joystick, i, (Sint16)ball->dx, (Sint16)ball->dy); + ball->dx = 0; + ball->dy = 0; + } + } + } + if (hwdata->changes & BUTTONS_CHANGED) { + for (Uint8 i = 0; i < hwdata->desc.nbuttons; ++i) { + SDL_SendJoystickButton(timestamp, joystick, i, hwdata->buttons[i]); + } + } + if (hwdata->changes & HATS_CHANGED) { + for (Uint8 i = 0; i < hwdata->desc.nhats; ++i) { + SDL_SendJoystickHat(timestamp, joystick, i, hwdata->hats[i]); + } + } + if (hwdata->changes & TOUCHPADS_CHANGED) { + for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) { + const SDL_JoystickTouchpadInfo *touchpad = &hwdata->touchpads[i]; + for (int j = 0; j < touchpad->nfingers; ++j) { + const SDL_JoystickTouchpadFingerInfo *finger = &touchpad->fingers[j]; + SDL_SendJoystickTouchpad(timestamp, joystick, i, j, finger->down, finger->x, finger->y, finger->pressure); + } + } + } + if (hwdata->num_sensor_events > 0) { + if (hwdata->sensors_enabled) { + for (int i = 0; i < hwdata->num_sensor_events; ++i) { + const VirtualSensorEvent *event = &hwdata->sensor_events[i]; + SDL_SendJoystickSensor(timestamp, joystick, event->type, event->sensor_timestamp, event->data, event->num_values); + } + } + hwdata->num_sensor_events = 0; + } + hwdata->changes = 0; +} + +static void VIRTUAL_JoystickClose(SDL_Joystick *joystick) +{ + SDL_AssertJoysticksLocked(); + + if (joystick->hwdata) { + joystick_hwdata *hwdata = joystick->hwdata; + hwdata->joystick = NULL; + joystick->hwdata = NULL; + } +} + +static void VIRTUAL_JoystickQuit(void) +{ + SDL_AssertJoysticksLocked(); + + while (g_VJoys) { + VIRTUAL_FreeHWData(g_VJoys); + } +} + +static bool VIRTUAL_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); + Uint8 current_button = 0; + Uint8 current_axis = 0; + + if (!hwdata || hwdata->desc.type != SDL_JOYSTICK_TYPE_GAMEPAD) { + return false; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_SOUTH))) { + out->a.kind = EMappingKind_Button; + out->a.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_EAST))) { + out->b.kind = EMappingKind_Button; + out->b.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_WEST))) { + out->x.kind = EMappingKind_Button; + out->x.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_NORTH))) { + out->y.kind = EMappingKind_Button; + out->y.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK))) { + out->back.kind = EMappingKind_Button; + out->back.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_GUIDE))) { + out->guide.kind = EMappingKind_Button; + out->guide.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_START))) { + out->start.kind = EMappingKind_Button; + out->start.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK))) { + out->leftstick.kind = EMappingKind_Button; + out->leftstick.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK))) { + out->rightstick.kind = EMappingKind_Button; + out->rightstick.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER))) { + out->leftshoulder.kind = EMappingKind_Button; + out->leftshoulder.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER))) { + out->rightshoulder.kind = EMappingKind_Button; + out->rightshoulder.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_UP))) { + out->dpup.kind = EMappingKind_Button; + out->dpup.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN))) { + out->dpdown.kind = EMappingKind_Button; + out->dpdown.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT))) { + out->dpleft.kind = EMappingKind_Button; + out->dpleft.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT))) { + out->dpright.kind = EMappingKind_Button; + out->dpright.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC1))) { + out->misc1.kind = EMappingKind_Button; + out->misc1.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1))) { + out->right_paddle1.kind = EMappingKind_Button; + out->right_paddle1.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE1))) { + out->left_paddle1.kind = EMappingKind_Button; + out->left_paddle1.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2))) { + out->right_paddle2.kind = EMappingKind_Button; + out->right_paddle2.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE2))) { + out->left_paddle2.kind = EMappingKind_Button; + out->left_paddle2.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_TOUCHPAD))) { + out->touchpad.kind = EMappingKind_Button; + out->touchpad.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC2))) { + out->misc2.kind = EMappingKind_Button; + out->misc2.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC3))) { + out->misc3.kind = EMappingKind_Button; + out->misc3.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC4))) { + out->misc4.kind = EMappingKind_Button; + out->misc4.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC5))) { + out->misc5.kind = EMappingKind_Button; + out->misc5.target = current_button++; + } + + if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC6))) { + out->misc6.kind = EMappingKind_Button; + out->misc6.target = current_button++; + } + + if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTX))) { + out->leftx.kind = EMappingKind_Axis; + out->leftx.target = current_axis++; + } + + if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTY))) { + out->lefty.kind = EMappingKind_Axis; + out->lefty.target = current_axis++; + } + + if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTX))) { + out->rightx.kind = EMappingKind_Axis; + out->rightx.target = current_axis++; + } + + if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTY))) { + out->righty.kind = EMappingKind_Axis; + out->righty.target = current_axis++; + } + + if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER))) { + out->lefttrigger.kind = EMappingKind_Axis; + out->lefttrigger.target = current_axis++; + } + + if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))) { + out->righttrigger.kind = EMappingKind_Axis; + out->righttrigger.target = current_axis++; + } + + return true; +} + +SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver = { + VIRTUAL_JoystickInit, + VIRTUAL_JoystickGetCount, + VIRTUAL_JoystickDetect, + VIRTUAL_JoystickIsDevicePresent, + VIRTUAL_JoystickGetDeviceName, + VIRTUAL_JoystickGetDevicePath, + VIRTUAL_JoystickGetDeviceSteamVirtualGamepadSlot, + VIRTUAL_JoystickGetDevicePlayerIndex, + VIRTUAL_JoystickSetDevicePlayerIndex, + VIRTUAL_JoystickGetDeviceGUID, + VIRTUAL_JoystickGetDeviceInstanceID, + VIRTUAL_JoystickOpen, + VIRTUAL_JoystickRumble, + VIRTUAL_JoystickRumbleTriggers, + VIRTUAL_JoystickSetLED, + VIRTUAL_JoystickSendEffect, + VIRTUAL_JoystickSetSensorsEnabled, + VIRTUAL_JoystickUpdate, + VIRTUAL_JoystickClose, + VIRTUAL_JoystickQuit, + VIRTUAL_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_VIRTUAL diff --git a/contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick_c.h new file mode 100644 index 0000000..14fe59e --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick_c.h @@ -0,0 +1,84 @@ +/* + 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" + +#ifndef SDL_VIRTUALJOYSTICK_C_H +#define SDL_VIRTUALJOYSTICK_C_H + +#ifdef SDL_JOYSTICK_VIRTUAL + +#include "../SDL_sysjoystick.h" + +#define AXES_CHANGED 0x00000001 +#define BALLS_CHANGED 0x00000002 +#define BUTTONS_CHANGED 0x00000004 +#define HATS_CHANGED 0x00000008 +#define TOUCHPADS_CHANGED 0x00000010 + +/** + * Data for a virtual, software-only joystick. + */ +typedef struct VirtualSensorEvent +{ + SDL_SensorType type; + Uint64 sensor_timestamp; + float data[3]; + int num_values; +} VirtualSensorEvent; + +typedef struct joystick_hwdata +{ + SDL_JoystickID instance_id; + bool attached; + char *name; + SDL_JoystickType type; + SDL_GUID guid; + SDL_VirtualJoystickDesc desc; + Uint32 changes; + Sint16 *axes; + bool *buttons; + Uint8 *hats; + SDL_JoystickBallData *balls; + SDL_JoystickTouchpadInfo *touchpads; + SDL_JoystickSensorInfo *sensors; + bool sensors_enabled; + int num_sensor_events; + int max_sensor_events; + VirtualSensorEvent *sensor_events; + + SDL_Joystick *joystick; + + struct joystick_hwdata *next; +} joystick_hwdata; + +extern SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *desc); +extern bool SDL_JoystickDetachVirtualInner(SDL_JoystickID instance_id); + +extern bool SDL_SetJoystickVirtualAxisInner(SDL_Joystick *joystick, int axis, Sint16 value); +extern bool SDL_SetJoystickVirtualBallInner(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel); +extern bool SDL_SetJoystickVirtualButtonInner(SDL_Joystick *joystick, int button, bool down); +extern bool SDL_SetJoystickVirtualHatInner(SDL_Joystick *joystick, int hat, Uint8 value); +extern bool SDL_SetJoystickVirtualTouchpadInner(SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure); +extern bool SDL_SendJoystickVirtualSensorDataInner(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values); + +#endif // SDL_JOYSTICK_VIRTUAL + +#endif // SDL_VIRTUALJOYSTICK_C_H diff --git a/contrib/SDL-3.2.8/src/joystick/vita/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/vita/SDL_sysjoystick.c new file mode 100644 index 0000000..6a69411 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/vita/SDL_sysjoystick.c @@ -0,0 +1,400 @@ +/* + 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_JOYSTICK_VITA + +// This is the PSVita implementation of the SDL joystick API +#include +#include +#include + +#include // For the definition of NULL +#include + +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" + +// Current pad state +static SceCtrlData pad0 = { .lx = 0, .ly = 0, .rx = 0, .ry = 0, .lt = 0, .rt = 0, .buttons = 0 }; +static SceCtrlData pad1 = { .lx = 0, .ly = 0, .rx = 0, .ry = 0, .lt = 0, .rt = 0, .buttons = 0 }; +static SceCtrlData pad2 = { .lx = 0, .ly = 0, .rx = 0, .ry = 0, .lt = 0, .rt = 0, .buttons = 0 }; +static SceCtrlData pad3 = { .lx = 0, .ly = 0, .rx = 0, .ry = 0, .lt = 0, .rt = 0, .buttons = 0 }; + +static int ext_port_map[4] = { 1, 2, 3, 4 }; // index: SDL joy number, entry: Vita port number. For external controllers + +static int SDL_numjoysticks = 1; + +static const unsigned int ext_button_map[] = { + SCE_CTRL_TRIANGLE, + SCE_CTRL_CIRCLE, + SCE_CTRL_CROSS, + SCE_CTRL_SQUARE, + SCE_CTRL_L1, + SCE_CTRL_R1, + SCE_CTRL_DOWN, + SCE_CTRL_LEFT, + SCE_CTRL_UP, + SCE_CTRL_RIGHT, + SCE_CTRL_SELECT, + SCE_CTRL_START, + SCE_CTRL_L2, + SCE_CTRL_R2, + SCE_CTRL_L3, + SCE_CTRL_R3 +}; + +static int analog_map[256]; // Map analog inputs to -32768 -> 32767 + +// 4 points define the bezier-curve. +// The Vita has a good amount of analog travel, so use a linear curve +static SDL_Point a = { 0, 0 }; +static SDL_Point b = { 0, 0 }; +static SDL_Point c = { 128, 32767 }; +static SDL_Point d = { 128, 32767 }; + +// simple linear interpolation between two points +static SDL_INLINE void lerp(SDL_Point *dest, const SDL_Point *first, const SDL_Point *second, float t) +{ + dest->x = first->x + (int)((second->x - first->x) * t); + dest->y = first->y + (int)((second->y - first->y) * t); +} + +// evaluate a point on a bezier-curve. t goes from 0 to 1.0 +static int calc_bezier_y(float t) +{ + SDL_Point ab, bc, cd, abbc, bccd, dest; + lerp(&ab, &a, &b, t); // point between a and b + lerp(&bc, &b, &c, t); // point between b and c + lerp(&cd, &c, &d, t); // point between c and d + lerp(&abbc, &ab, &bc, t); // point between ab and bc + lerp(&bccd, &bc, &cd, t); // point between bc and cd + lerp(&dest, &abbc, &bccd, t); // point on the bezier-curve + return dest.y; +} + +/* Function to scan the system for joysticks. + * Joystick 0 should be the system default joystick. + * It should return number of joysticks, or -1 on an unrecoverable fatal error. + */ +static bool VITA_JoystickInit(void) +{ + int i; + SceCtrlPortInfo myPortInfo; + + // Setup input + sceCtrlSetSamplingMode(SCE_CTRL_MODE_ANALOG_WIDE); + sceCtrlSetSamplingModeExt(SCE_CTRL_MODE_ANALOG_WIDE); + + /* Create an accurate map from analog inputs (0 to 255) + to SDL joystick positions (-32768 to 32767) */ + for (i = 0; i < 128; i++) { + float t = (float)i / 127.0f; + analog_map[i + 128] = calc_bezier_y(t); + analog_map[127 - i] = -1 * analog_map[i + 128]; + } + + // Assume we have at least one controller, even when nothing is paired + // This way the user can jump in, pair a controller + // and control things immediately even if it is paired + // after the app has already started. + + SDL_numjoysticks = 1; + SDL_PrivateJoystickAdded(SDL_numjoysticks); + + // How many additional paired controllers are there? + sceCtrlGetControllerPortInfo(&myPortInfo); + + // On Vita TV, port 0 and 1 are the same controller + // and that is the first one, so start at port 2 + for (i = 2; i <= 4; i++) { + if (myPortInfo.port[i] != SCE_CTRL_TYPE_UNPAIRED) { + ++SDL_numjoysticks; + SDL_PrivateJoystickAdded(SDL_numjoysticks); + } + } + return SDL_numjoysticks; +} + +static int VITA_JoystickGetCount(void) +{ + return SDL_numjoysticks; +} + +static void VITA_JoystickDetect(void) +{ +} + +static bool VITA_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers + return false; +} + +// Function to perform the mapping from device index to the instance id for this index +static SDL_JoystickID VITA_JoystickGetDeviceInstanceID(int device_index) +{ + return device_index + 1; +} + +static const char *VITA_JoystickGetDeviceName(int index) +{ + if (index == 0) { + return "PSVita Controller"; + } + + if (index == 1) { + return "PSVita Controller"; + } + + if (index == 2) { + return "PSVita Controller"; + } + + if (index == 3) { + return "PSVita Controller"; + } + + SDL_SetError("No joystick available with that index"); + return NULL; +} + +static const char *VITA_JoystickGetDevicePath(int index) +{ + return NULL; +} + +static int VITA_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + +static int VITA_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void VITA_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +/* Function to open a joystick for use. + The joystick to open is specified by the device index. + This should fill the nbuttons and naxes fields of the joystick structure. + It returns 0, or -1 if there is an error. + */ +static bool VITA_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + joystick->nbuttons = SDL_arraysize(ext_button_map); + joystick->naxes = 6; + joystick->nhats = 0; + + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true); + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + + return true; +} + +/* Function to update the state of a joystick - called as a device poll. + * This function shouldn't update the joystick structure directly, + * but instead should call SDL_PrivateJoystick*() to deliver events + * and update joystick device state. + */ +static void VITA_JoystickUpdate(SDL_Joystick *joystick) +{ + int i; + unsigned int buttons; + unsigned int changed; + unsigned char lx, ly, rx, ry, lt, rt; + static unsigned int old_buttons[] = { 0, 0, 0, 0 }; + static unsigned char old_lx[] = { 0, 0, 0, 0 }; + static unsigned char old_ly[] = { 0, 0, 0, 0 }; + static unsigned char old_rx[] = { 0, 0, 0, 0 }; + static unsigned char old_ry[] = { 0, 0, 0, 0 }; + static unsigned char old_lt[] = { 0, 0, 0, 0 }; + static unsigned char old_rt[] = { 0, 0, 0, 0 }; + SceCtrlData *pad = NULL; + Uint64 timestamp = SDL_GetTicksNS(); + + int index = (int)SDL_GetJoystickID(joystick) - 1; + + if (index == 0) + pad = &pad0; + else if (index == 1) + pad = &pad1; + else if (index == 2) + pad = &pad2; + else if (index == 3) + pad = &pad3; + else + return; + + if (index == 0) { + if (sceCtrlPeekBufferPositive2(ext_port_map[index], pad, 1) < 0) { + // on vita fallback to port 0 + sceCtrlPeekBufferPositive2(0, pad, 1); + } + } else { + sceCtrlPeekBufferPositive2(ext_port_map[index], pad, 1); + } + + buttons = pad->buttons; + + lx = pad->lx; + ly = pad->ly; + rx = pad->rx; + ry = pad->ry; + lt = pad->lt; + rt = pad->rt; + + // Axes + + if (old_lx[index] != lx) { + SDL_SendJoystickAxis(timestamp, joystick, 0, analog_map[lx]); + old_lx[index] = lx; + } + if (old_ly[index] != ly) { + SDL_SendJoystickAxis(timestamp, joystick, 1, analog_map[ly]); + old_ly[index] = ly; + } + if (old_rx[index] != rx) { + SDL_SendJoystickAxis(timestamp, joystick, 2, analog_map[rx]); + old_rx[index] = rx; + } + if (old_ry[index] != ry) { + SDL_SendJoystickAxis(timestamp, joystick, 3, analog_map[ry]); + old_ry[index] = ry; + } + + if (old_lt[index] != lt) { + SDL_SendJoystickAxis(timestamp, joystick, 4, analog_map[lt]); + old_lt[index] = lt; + } + if (old_rt[index] != rt) { + SDL_SendJoystickAxis(timestamp, joystick, 5, analog_map[rt]); + old_rt[index] = rt; + } + + // Buttons + changed = old_buttons[index] ^ buttons; + old_buttons[index] = buttons; + + if (changed) { + for (i = 0; i < SDL_arraysize(ext_button_map); i++) { + if (changed & ext_button_map[i]) { + bool down = ((buttons & ext_button_map[i]) != 0); + SDL_SendJoystickButton(timestamp, joystick, i, down); + } + } + } +} + +// Function to close a joystick after use +static void VITA_JoystickClose(SDL_Joystick *joystick) +{ +} + +// Function to perform any system-specific joystick related cleanup +static void VITA_JoystickQuit(void) +{ +} + +static SDL_GUID VITA_JoystickGetDeviceGUID(int device_index) +{ + // the GUID is just the name for now + const char *name = VITA_JoystickGetDeviceName(device_index); + return SDL_CreateJoystickGUIDForName(name); +} + +static bool VITA_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + int index = (int)SDL_GetJoystickID(joystick) - 1; + SceCtrlActuator act; + + if (index < 0 || index > 3) { + return false; + } + SDL_zero(act); + act.small = high_frequency_rumble / 256; + act.large = low_frequency_rumble / 256; + if (sceCtrlSetActuator(ext_port_map[index], &act) < 0) { + return SDL_Unsupported(); + } + return true; +} + +static bool VITA_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left, Uint16 right) +{ + return SDL_Unsupported(); +} + +static bool VITA_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + int index = (int)SDL_GetJoystickID(joystick) - 1; + if (index < 0 || index > 3) { + return false; + } + if (sceCtrlSetLightBar(ext_port_map[index], red, green, blue) < 0) { + return SDL_Unsupported(); + } + return true; +} + +static bool VITA_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool VITA_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static bool VITA_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +SDL_JoystickDriver SDL_VITA_JoystickDriver = { + VITA_JoystickInit, + VITA_JoystickGetCount, + VITA_JoystickDetect, + VITA_JoystickIsDevicePresent, + VITA_JoystickGetDeviceName, + VITA_JoystickGetDevicePath, + VITA_JoystickGetDeviceSteamVirtualGamepadSlot, + VITA_JoystickGetDevicePlayerIndex, + VITA_JoystickSetDevicePlayerIndex, + VITA_JoystickGetDeviceGUID, + VITA_JoystickGetDeviceInstanceID, + VITA_JoystickOpen, + VITA_JoystickRumble, + VITA_JoystickRumbleTriggers, + VITA_JoystickSetLED, + VITA_JoystickSendEffect, + VITA_JoystickSetSensorsEnabled, + VITA_JoystickUpdate, + VITA_JoystickClose, + VITA_JoystickQuit, + VITA_JoystickGetGamepadMapping, +}; + +#endif // SDL_JOYSTICK_VITA diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c new file mode 100644 index 0000000..b00218d --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c @@ -0,0 +1,1210 @@ +/* + 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" + +#include "../SDL_sysjoystick.h" + +#ifdef SDL_JOYSTICK_DINPUT + +#include "SDL_windowsjoystick_c.h" +#include "SDL_dinputjoystick_c.h" +#include "SDL_rawinputjoystick_c.h" +#include "SDL_xinputjoystick_c.h" +#include "../hidapi/SDL_hidapijoystick_c.h" + +#ifndef DIDFT_OPTIONAL +#define DIDFT_OPTIONAL 0x80000000 +#endif + +#define INPUT_QSIZE 128 // Buffer up to 128 input messages +#define JOY_AXIS_THRESHOLD (((SDL_JOYSTICK_AXIS_MAX) - (SDL_JOYSTICK_AXIS_MIN)) / 100) // 1% motion + +#define CONVERT_MAGNITUDE(x) (((x)*10000) / 0x7FFF) + +// external variables referenced. +#ifdef SDL_VIDEO_DRIVER_WINDOWS +extern HWND SDL_HelperWindow; +#else +static const HWND SDL_HelperWindow = NULL; +#endif + +// local variables +static bool coinitialized = false; +static LPDIRECTINPUT8 dinput = NULL; + +// Taken from Wine - Thanks! +static DIOBJECTDATAFORMAT dfDIJoystick2[] = { + { &GUID_XAxis, DIJOFS_X, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_YAxis, DIJOFS_Y, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_ZAxis, DIJOFS_Z, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_RxAxis, DIJOFS_RX, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_RyAxis, DIJOFS_RY, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_RzAxis, DIJOFS_RZ, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_POV, DIJOFS_POV(0), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 }, + { &GUID_POV, DIJOFS_POV(1), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 }, + { &GUID_POV, DIJOFS_POV(2), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 }, + { &GUID_POV, DIJOFS_POV(3), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(0), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(1), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(2), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(3), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(4), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(5), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(6), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(7), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(8), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(9), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(10), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(11), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(12), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(13), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(14), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(15), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(16), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(17), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(18), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(19), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(20), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(21), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(22), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(23), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(24), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(25), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(26), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(27), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(28), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(29), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(30), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(31), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(32), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(33), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(34), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(35), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(36), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(37), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(38), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(39), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(40), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(41), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(42), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(43), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(44), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(45), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(46), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(47), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(48), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(49), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(50), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(51), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(52), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(53), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(54), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(55), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(56), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(57), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(58), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(59), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(60), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(61), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(62), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(63), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(64), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(65), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(66), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(67), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(68), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(69), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(70), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(71), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(72), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(73), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(74), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(75), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(76), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(77), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(78), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(79), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(80), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(81), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(82), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(83), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(84), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(85), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(86), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(87), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(88), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(89), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(90), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(91), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(92), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(93), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(94), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(95), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(96), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(97), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(98), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(99), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(100), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(101), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(102), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(103), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(104), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(105), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(106), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(107), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(108), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(109), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(110), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(111), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(112), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(113), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(114), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(115), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(116), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(117), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(118), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(119), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(120), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(121), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(122), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(123), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(124), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(125), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(126), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(127), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lVX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lVY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lVZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lVRx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lVRy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lVRz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + // note: dwOfs value matches Windows + { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lAX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lAY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lAZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lARx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lARy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lARz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + // note: dwOfs value matches Windows + { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lFX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lFY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lFZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lFRx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lFRy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lFRz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + // note: dwOfs value matches Windows + { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, +}; + +const DIDATAFORMAT SDL_c_dfDIJoystick2 = { + sizeof(DIDATAFORMAT), + sizeof(DIOBJECTDATAFORMAT), + DIDF_ABSAXIS, + sizeof(DIJOYSTATE2), + SDL_arraysize(dfDIJoystick2), + dfDIJoystick2 +}; + +// Convert a DirectInput return code to a text message +static bool SetDIerror(const char *function, HRESULT code) +{ + return SDL_SetError("%s() DirectX error 0x%8.8lx", function, code); +} + +static bool SDL_IsXInputDevice(Uint16 vendor_id, Uint16 product_id, const char *hidPath) +{ +#if defined(SDL_JOYSTICK_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT) + SDL_GamepadType type; + + // XInput and RawInput backends will pick up XInput-compatible devices + if (!SDL_XINPUT_Enabled() +#ifdef SDL_JOYSTICK_RAWINPUT + && !RAWINPUT_IsEnabled() +#endif + ) { + return false; + } + + // If device path contains "IG_" then its an XInput device + // See: https://docs.microsoft.com/windows/win32/xinput/xinput-and-directinput + if (SDL_strstr(hidPath, "IG_") != NULL) { + return true; + } + + type = SDL_GetGamepadTypeFromVIDPID(vendor_id, product_id, NULL, false); + if (type == SDL_GAMEPAD_TYPE_XBOX360 || + type == SDL_GAMEPAD_TYPE_XBOXONE || + (vendor_id == USB_VENDOR_VALVE && product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) { + return true; + } +#endif // SDL_JOYSTICK_XINPUT || SDL_JOYSTICK_RAWINPUT + + return false; +} + +static bool QueryDeviceName(LPDIRECTINPUTDEVICE8 device, Uint16 vendor_id, Uint16 product_id, char **manufacturer_string, char **product_string) +{ + DIPROPSTRING dipstr; + + if (!device || !manufacturer_string || !product_string) { + return false; + } + +#ifdef SDL_JOYSTICK_HIDAPI + *manufacturer_string = HIDAPI_GetDeviceManufacturerName(vendor_id, product_id); + *product_string = HIDAPI_GetDeviceProductName(vendor_id, product_id); + if (*product_string) { + return true; + } +#endif + + dipstr.diph.dwSize = sizeof(dipstr); + dipstr.diph.dwHeaderSize = sizeof(dipstr.diph); + dipstr.diph.dwObj = 0; + dipstr.diph.dwHow = DIPH_DEVICE; + + if (FAILED(IDirectInputDevice8_GetProperty(device, DIPROP_PRODUCTNAME, &dipstr.diph))) { + return false; + } + + *manufacturer_string = NULL; + *product_string = WIN_StringToUTF8(dipstr.wsz); + + return true; +} + +static bool QueryDevicePath(LPDIRECTINPUTDEVICE8 device, char **device_path) +{ + DIPROPGUIDANDPATH dippath; + + if (!device || !device_path) { + return false; + } + + dippath.diph.dwSize = sizeof(dippath); + dippath.diph.dwHeaderSize = sizeof(dippath.diph); + dippath.diph.dwObj = 0; + dippath.diph.dwHow = DIPH_DEVICE; + + if (FAILED(IDirectInputDevice8_GetProperty(device, DIPROP_GUIDANDPATH, &dippath.diph))) { + return false; + } + + *device_path = WIN_StringToUTF8W(dippath.wszPath); + + // Normalize path to upper case. + SDL_strupr(*device_path); + + return true; +} + +static bool QueryDeviceInfo(LPDIRECTINPUTDEVICE8 device, Uint16 *vendor_id, Uint16 *product_id) +{ + DIPROPDWORD dipdw; + + if (!device || !vendor_id || !product_id) { + return false; + } + + dipdw.diph.dwSize = sizeof(dipdw); + dipdw.diph.dwHeaderSize = sizeof(dipdw.diph); + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = 0; + + if (FAILED(IDirectInputDevice8_GetProperty(device, DIPROP_VIDPID, &dipdw.diph))) { + return false; + } + + *vendor_id = LOWORD(dipdw.dwData); + *product_id = HIWORD(dipdw.dwData); + + return true; +} + +void FreeRumbleEffectData(DIEFFECT *effect) +{ + if (!effect) { + return; + } + SDL_free(effect->rgdwAxes); + SDL_free(effect->rglDirection); + SDL_free(effect->lpvTypeSpecificParams); + SDL_free(effect); +} + +DIEFFECT *CreateRumbleEffectData(Sint16 magnitude) +{ + DIEFFECT *effect; + DIPERIODIC *periodic; + + // Create the effect + effect = (DIEFFECT *)SDL_calloc(1, sizeof(*effect)); + if (!effect) { + return NULL; + } + effect->dwSize = sizeof(*effect); + effect->dwGain = 10000; + effect->dwFlags = DIEFF_OBJECTOFFSETS; + effect->dwDuration = SDL_MAX_RUMBLE_DURATION_MS * 1000; // In microseconds. + effect->dwTriggerButton = DIEB_NOTRIGGER; + + effect->cAxes = 2; + effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD)); + if (!effect->rgdwAxes) { + FreeRumbleEffectData(effect); + return NULL; + } + + effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG)); + if (!effect->rglDirection) { + FreeRumbleEffectData(effect); + return NULL; + } + effect->dwFlags |= DIEFF_CARTESIAN; + + periodic = (DIPERIODIC *)SDL_calloc(1, sizeof(*periodic)); + if (!periodic) { + FreeRumbleEffectData(effect); + return NULL; + } + periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude); + periodic->dwPeriod = 1000000; + + effect->cbTypeSpecificParams = sizeof(*periodic); + effect->lpvTypeSpecificParams = periodic; + + return effect; +} + +bool SDL_DINPUT_JoystickInit(void) +{ + HRESULT result; + HINSTANCE instance; + + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true)) { + // In some environments, IDirectInput8_Initialize / _EnumDevices can take a minute even with no controllers. + dinput = NULL; + return true; + } + + result = WIN_CoInitialize(); + if (FAILED(result)) { + return SetDIerror("CoInitialize", result); + } + + coinitialized = true; + + result = CoCreateInstance(&CLSID_DirectInput8, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectInput8, (LPVOID *)&dinput); + + if (FAILED(result)) { + return SetDIerror("CoCreateInstance", result); + } + + // Because we used CoCreateInstance, we need to Initialize it, first. + instance = GetModuleHandle(NULL); + if (!instance) { + IDirectInput8_Release(dinput); + dinput = NULL; + return SDL_SetError("GetModuleHandle() failed with error code %lu.", GetLastError()); + } + result = IDirectInput8_Initialize(dinput, instance, DIRECTINPUT_VERSION); + + if (FAILED(result)) { + IDirectInput8_Release(dinput); + dinput = NULL; + return SetDIerror("IDirectInput::Initialize", result); + } + return true; +} + +static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path) +{ + int slot = -1; + + if (vendor_id == USB_VENDOR_VALVE && + product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + (void)SDL_sscanf(device_path, "\\\\?\\HID#VID_28DE&PID_11FF&IG_0%d", &slot); + } + return slot; +} + +// helper function for direct input, gets called for each connected joystick +static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInstance, LPVOID pContext) +{ +#define CHECK(expression) \ + { \ + if (!(expression)) \ + goto err; \ + } + JoyStick_DeviceData *pNewJoystick = NULL; + JoyStick_DeviceData *pPrevJoystick = NULL; + Uint16 vendor = 0; + Uint16 product = 0; + Uint16 version = 0; + char *hidPath = NULL; + char *manufacturer_string = NULL; + char *product_string = NULL; + LPDIRECTINPUTDEVICE8 device = NULL; + + // We are only supporting HID devices. + CHECK(pDeviceInstance->dwDevType & DIDEVTYPE_HID); + + CHECK(SUCCEEDED(IDirectInput8_CreateDevice(dinput, &pDeviceInstance->guidInstance, &device, NULL))); + CHECK(QueryDevicePath(device, &hidPath)); + CHECK(QueryDeviceInfo(device, &vendor, &product)); + CHECK(QueryDeviceName(device, vendor, product, &manufacturer_string, &product_string)); + + CHECK(!SDL_IsXInputDevice(vendor, product, hidPath)); + CHECK(!SDL_ShouldIgnoreJoystick(vendor, product, version, product_string)); + CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, product_string)); + + pNewJoystick = *(JoyStick_DeviceData **)pContext; + while (pNewJoystick) { + // update GUIDs of joysticks with matching paths, in case they're not open yet + if (SDL_strcmp(pNewJoystick->path, hidPath) == 0) { + // if we are replacing the front of the list then update it + if (pNewJoystick == *(JoyStick_DeviceData **)pContext) { + *(JoyStick_DeviceData **)pContext = pNewJoystick->pNext; + } else if (pPrevJoystick) { + pPrevJoystick->pNext = pNewJoystick->pNext; + } + + // Update with new guid/etc, if it has changed + SDL_memcpy(&pNewJoystick->dxdevice, pDeviceInstance, sizeof(DIDEVICEINSTANCE)); + + pNewJoystick->pNext = SYS_Joystick; + SYS_Joystick = pNewJoystick; + + pNewJoystick = NULL; + CHECK(FALSE); + } + + pPrevJoystick = pNewJoystick; + pNewJoystick = pNewJoystick->pNext; + } + + pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData)); + CHECK(pNewJoystick); + + pNewJoystick->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(vendor, product, hidPath); + SDL_strlcpy(pNewJoystick->path, hidPath, SDL_arraysize(pNewJoystick->path)); + SDL_memcpy(&pNewJoystick->dxdevice, pDeviceInstance, sizeof(DIDEVICEINSTANCE)); + + pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string); + CHECK(pNewJoystick->joystickname); + + if (vendor && product) { + pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, manufacturer_string, product_string, 0, 0); + } else { + pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, version, manufacturer_string, product_string, 0, 0); + } + + WINDOWS_AddJoystickDevice(pNewJoystick); + pNewJoystick = NULL; + +err: + if (pNewJoystick) { + SDL_free(pNewJoystick->joystickname); + SDL_free(pNewJoystick); + } + + SDL_free(hidPath); + SDL_free(manufacturer_string); + SDL_free(product_string); + + if (device) { + IDirectInputDevice8_Release(device); + } + + return DIENUM_CONTINUE; // get next device, please +#undef CHECK +} + +void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext) +{ + if (!dinput) { + return; + } + + IDirectInput8_EnumDevices(dinput, DI8DEVCLASS_GAMECTRL, EnumJoystickDetectCallback, pContext, DIEDFL_ATTACHEDONLY); +} + +// helper function for direct input, gets called for each connected joystick +typedef struct +{ + Uint16 vendor; + Uint16 product; + bool present; +} Joystick_PresentData; + +static BOOL CALLBACK EnumJoystickPresentCallback(LPCDIDEVICEINSTANCE pDeviceInstance, LPVOID pContext) +{ +#define CHECK(expression) \ + { \ + if (!(expression)) \ + goto err; \ + } + Joystick_PresentData *pData = (Joystick_PresentData *)pContext; + Uint16 vendor = 0; + Uint16 product = 0; + LPDIRECTINPUTDEVICE8 device = NULL; + BOOL result = DIENUM_CONTINUE; + + // We are only supporting HID devices. + CHECK(pDeviceInstance->dwDevType & DIDEVTYPE_HID); + + CHECK(SUCCEEDED(IDirectInput8_CreateDevice(dinput, &pDeviceInstance->guidInstance, &device, NULL))); + CHECK(QueryDeviceInfo(device, &vendor, &product)); + + if (vendor == pData->vendor && product == pData->product) { + pData->present = true; + result = DIENUM_STOP; // found it + } + +err: + if (device) { + IDirectInputDevice8_Release(device); + } + + return result; +#undef CHECK +} + +bool SDL_DINPUT_JoystickPresent(Uint16 vendor_id, Uint16 product_id, Uint16 version_number) +{ + Joystick_PresentData data; + + if (!dinput) { + return false; + } + + data.vendor = vendor_id; + data.product = product_id; + data.present = false; + IDirectInput8_EnumDevices(dinput, DI8DEVCLASS_GAMECTRL, EnumJoystickPresentCallback, &data, DIEDFL_ATTACHEDONLY); + return data.present; +} + +static BOOL CALLBACK EnumDevObjectsCallback(LPCDIDEVICEOBJECTINSTANCE pDeviceObject, LPVOID pContext) +{ + SDL_Joystick *joystick = (SDL_Joystick *)pContext; + HRESULT result; + input_t *in = &joystick->hwdata->Inputs[joystick->hwdata->NumInputs]; + + if (pDeviceObject->dwType & DIDFT_BUTTON) { + in->type = BUTTON; + in->num = (Uint8)joystick->nbuttons; + in->ofs = DIJOFS_BUTTON(in->num); + joystick->nbuttons++; + } else if (pDeviceObject->dwType & DIDFT_POV) { + in->type = HAT; + in->num = (Uint8)joystick->nhats; + in->ofs = DIJOFS_POV(in->num); + joystick->nhats++; + } else if (pDeviceObject->dwType & DIDFT_AXIS) { + DIPROPRANGE diprg; + DIPROPDWORD dilong; + + in->type = AXIS; + in->num = (Uint8)joystick->naxes; + if (SDL_memcmp(&pDeviceObject->guidType, &GUID_XAxis, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_X; + } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_YAxis, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_Y; + } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_ZAxis, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_Z; + } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_RxAxis, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_RX; + } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_RyAxis, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_RY; + } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_RzAxis, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_RZ; + } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_Slider, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_SLIDER(joystick->hwdata->NumSliders); + ++joystick->hwdata->NumSliders; + } else { + return DIENUM_CONTINUE; // not an axis we can grok + } + + diprg.diph.dwSize = sizeof(diprg); + diprg.diph.dwHeaderSize = sizeof(diprg.diph); + diprg.diph.dwObj = pDeviceObject->dwType; + diprg.diph.dwHow = DIPH_BYID; + diprg.lMin = SDL_JOYSTICK_AXIS_MIN; + diprg.lMax = SDL_JOYSTICK_AXIS_MAX; + + result = + IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice, + DIPROP_RANGE, &diprg.diph); + if (FAILED(result)) { + return DIENUM_CONTINUE; // don't use this axis + } + + // Set dead zone to 0. + dilong.diph.dwSize = sizeof(dilong); + dilong.diph.dwHeaderSize = sizeof(dilong.diph); + dilong.diph.dwObj = pDeviceObject->dwType; + dilong.diph.dwHow = DIPH_BYID; + dilong.dwData = 0; + result = + IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice, + DIPROP_DEADZONE, &dilong.diph); + if (FAILED(result)) { + return DIENUM_CONTINUE; // don't use this axis + } + + joystick->naxes++; + } else { + // not supported at this time + return DIENUM_CONTINUE; + } + + joystick->hwdata->NumInputs++; + + if (joystick->hwdata->NumInputs == MAX_INPUTS) { + return DIENUM_STOP; // too many + } + + return DIENUM_CONTINUE; +} + +/* Sort using the data offset into the DInput struct. + * This gives a reasonable ordering for the inputs. + */ +static int SDLCALL SortDevFunc(const void *a, const void *b) +{ + const input_t *inputA = (const input_t *)a; + const input_t *inputB = (const input_t *)b; + + if (inputA->ofs < inputB->ofs) { + return -1; + } + if (inputA->ofs > inputB->ofs) { + return 1; + } + return 0; +} + +// Sort the input objects and recalculate the indices for each input. +static void SortDevObjects(SDL_Joystick *joystick) +{ + input_t *inputs = joystick->hwdata->Inputs; + Uint8 nButtons = 0; + Uint8 nHats = 0; + Uint8 nAxis = 0; + int n; + + SDL_qsort(inputs, joystick->hwdata->NumInputs, sizeof(input_t), SortDevFunc); + + for (n = 0; n < joystick->hwdata->NumInputs; n++) { + switch (inputs[n].type) { + case BUTTON: + inputs[n].num = nButtons; + nButtons++; + break; + + case HAT: + inputs[n].num = nHats; + nHats++; + break; + + case AXIS: + inputs[n].num = nAxis; + nAxis++; + break; + } + } +} + +bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice) +{ + HRESULT result; + DIPROPDWORD dipdw; + + joystick->hwdata->buffered = true; + joystick->hwdata->Capabilities.dwSize = sizeof(DIDEVCAPS); + + SDL_zero(dipdw); + dipdw.diph.dwSize = sizeof(DIPROPDWORD); + dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); + + result = + IDirectInput8_CreateDevice(dinput, + &joystickdevice->dxdevice.guidInstance, + &joystick->hwdata->InputDevice, + NULL); + if (FAILED(result)) { + return SetDIerror("IDirectInput::CreateDevice", result); + } + + /* Acquire shared access. Exclusive access is required for forces, + * though. */ + result = + IDirectInputDevice8_SetCooperativeLevel(joystick->hwdata->InputDevice, SDL_HelperWindow, + DISCL_EXCLUSIVE | + DISCL_BACKGROUND); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SetCooperativeLevel", result); + } + + // Use the extended data structure: DIJOYSTATE2. + result = + IDirectInputDevice8_SetDataFormat(joystick->hwdata->InputDevice, + &SDL_c_dfDIJoystick2); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SetDataFormat", result); + } + + // Get device capabilities + result = + IDirectInputDevice8_GetCapabilities(joystick->hwdata->InputDevice, + &joystick->hwdata->Capabilities); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::GetCapabilities", result); + } + + // Force capable? + if (joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK) { + result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::Acquire", result); + } + + // reset all actuators. + result = + IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, + DISFFC_RESET); + + /* Not necessarily supported, ignore if not supported. + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand", result); + } + */ + + result = IDirectInputDevice8_Unacquire(joystick->hwdata->InputDevice); + + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::Unacquire", result); + } + + /* Turn on auto-centering for a ForceFeedback device (until told + * otherwise). */ + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = DIPROPAUTOCENTER_ON; + + result = + IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice, + DIPROP_AUTOCENTER, &dipdw.diph); + + /* Not necessarily supported, ignore if not supported. + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SetProperty", result); + } + */ + + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } + + // What buttons and axes does it have? + IDirectInputDevice8_EnumObjects(joystick->hwdata->InputDevice, + EnumDevObjectsCallback, joystick, + DIDFT_BUTTON | DIDFT_AXIS | DIDFT_POV); + + /* Reorder the input objects. Some devices do not report the X axis as + * the first axis, for example. */ + SortDevObjects(joystick); + + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = INPUT_QSIZE; + + // Set the buffer size + result = + IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice, + DIPROP_BUFFERSIZE, &dipdw.diph); + + if (result == DI_POLLEDDEVICE) { + /* This device doesn't support buffering, so we're forced + * to use less reliable polling. */ + joystick->hwdata->buffered = false; + } else if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SetProperty", result); + } + joystick->hwdata->first_update = true; + + // Poll and wait for initial device state to be populated + result = IDirectInputDevice8_Poll(joystick->hwdata->InputDevice); + if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) { + IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + IDirectInputDevice8_Poll(joystick->hwdata->InputDevice); + } + SDL_Delay(50); + + return true; +} + +static bool SDL_DINPUT_JoystickInitRumble(SDL_Joystick *joystick, Sint16 magnitude) +{ + HRESULT result; + + // Reset and then enable actuators + result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_RESET); + if (result == DIERR_INPUTLOST || result == DIERR_NOTEXCLUSIVEACQUIRED) { + result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + if (SUCCEEDED(result)) { + result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_RESET); + } + } + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand(DISFFC_RESET)", result); + } + + result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_SETACTUATORSON); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand(DISFFC_SETACTUATORSON)", result); + } + + // Create the effect + joystick->hwdata->ffeffect = CreateRumbleEffectData(magnitude); + if (!joystick->hwdata->ffeffect) { + return false; + } + + result = IDirectInputDevice8_CreateEffect(joystick->hwdata->InputDevice, &GUID_Sine, + joystick->hwdata->ffeffect, &joystick->hwdata->ffeffect_ref, NULL); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::CreateEffect", result); + } + return true; +} + +bool SDL_DINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + HRESULT result; + + // Scale and average the two rumble strengths + Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2); + + if (!(joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK)) { + return SDL_Unsupported(); + } + + if (joystick->hwdata->ff_initialized) { + DIPERIODIC *periodic = ((DIPERIODIC *)joystick->hwdata->ffeffect->lpvTypeSpecificParams); + periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude); + + result = IDirectInputEffect_SetParameters(joystick->hwdata->ffeffect_ref, joystick->hwdata->ffeffect, (DIEP_DURATION | DIEP_TYPESPECIFICPARAMS)); + if (result == DIERR_INPUTLOST) { + result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + if (SUCCEEDED(result)) { + result = IDirectInputEffect_SetParameters(joystick->hwdata->ffeffect_ref, joystick->hwdata->ffeffect, (DIEP_DURATION | DIEP_TYPESPECIFICPARAMS)); + } + } + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SetParameters", result); + } + } else { + if (!SDL_DINPUT_JoystickInitRumble(joystick, magnitude)) { + return false; + } + joystick->hwdata->ff_initialized = true; + } + + result = IDirectInputEffect_Start(joystick->hwdata->ffeffect_ref, 1, 0); + if (result == DIERR_INPUTLOST || result == DIERR_NOTEXCLUSIVEACQUIRED) { + result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + if (SUCCEEDED(result)) { + result = IDirectInputEffect_Start(joystick->hwdata->ffeffect_ref, 1, 0); + } + } + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::Start", result); + } + return true; +} + +static Uint8 TranslatePOV(DWORD value) +{ + const Uint8 HAT_VALS[] = { + SDL_HAT_UP, + SDL_HAT_UP | SDL_HAT_RIGHT, + SDL_HAT_RIGHT, + SDL_HAT_DOWN | SDL_HAT_RIGHT, + SDL_HAT_DOWN, + SDL_HAT_DOWN | SDL_HAT_LEFT, + SDL_HAT_LEFT, + SDL_HAT_UP | SDL_HAT_LEFT + }; + + if (LOWORD(value) == 0xFFFF) { + return SDL_HAT_CENTERED; + } + + // Round the value up: + value += 4500 / 2; + value %= 36000; + value /= 4500; + + if (value >= 8) { + return SDL_HAT_CENTERED; // shouldn't happen + } + + return HAT_VALS[value]; +} + +/* Function to update the state of a joystick - called as a device poll. + * This function shouldn't update the joystick structure directly, + * but instead should call SDL_PrivateJoystick*() to deliver events + * and update joystick device state. + */ +static void UpdateDINPUTJoystickState_Polled(SDL_Joystick *joystick) +{ + DIJOYSTATE2 state; + HRESULT result; + int i; + Uint64 timestamp = SDL_GetTicksNS(); + + result = + IDirectInputDevice8_GetDeviceState(joystick->hwdata->InputDevice, + sizeof(DIJOYSTATE2), &state); + if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) { + IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + result = + IDirectInputDevice8_GetDeviceState(joystick->hwdata->InputDevice, + sizeof(DIJOYSTATE2), &state); + } + + if (result != DI_OK) { + return; + } + + // Set each known axis, button and POV. + for (i = 0; i < joystick->hwdata->NumInputs; ++i) { + const input_t *in = &joystick->hwdata->Inputs[i]; + + switch (in->type) { + case AXIS: + switch (in->ofs) { + case DIJOFS_X: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lX); + break; + case DIJOFS_Y: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lY); + break; + case DIJOFS_Z: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lZ); + break; + case DIJOFS_RX: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lRx); + break; + case DIJOFS_RY: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lRy); + break; + case DIJOFS_RZ: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lRz); + break; + case DIJOFS_SLIDER(0): + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.rglSlider[0]); + break; + case DIJOFS_SLIDER(1): + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.rglSlider[1]); + break; + } + break; + + case BUTTON: + SDL_SendJoystickButton(timestamp, joystick, in->num, + (state.rgbButtons[in->ofs - DIJOFS_BUTTON0] != 0)); + break; + case HAT: + { + Uint8 pos = TranslatePOV(state.rgdwPOV[in->ofs - DIJOFS_POV(0)]); + SDL_SendJoystickHat(timestamp, joystick, in->num, pos); + break; + } + } + } +} + +static void UpdateDINPUTJoystickState_Buffered(SDL_Joystick *joystick) +{ + int i; + HRESULT result; + DWORD numevents; + DIDEVICEOBJECTDATA evtbuf[INPUT_QSIZE]; + Uint64 timestamp = SDL_GetTicksNS(); + + numevents = INPUT_QSIZE; + result = + IDirectInputDevice8_GetDeviceData(joystick->hwdata->InputDevice, + sizeof(DIDEVICEOBJECTDATA), evtbuf, + &numevents, 0); + if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) { + IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + result = + IDirectInputDevice8_GetDeviceData(joystick->hwdata->InputDevice, + sizeof(DIDEVICEOBJECTDATA), + evtbuf, &numevents, 0); + } + + // Handle the events or punt + if (FAILED(result)) { + return; + } + + for (i = 0; i < (int)numevents; ++i) { + int j; + + for (j = 0; j < joystick->hwdata->NumInputs; ++j) { + const input_t *in = &joystick->hwdata->Inputs[j]; + + if (evtbuf[i].dwOfs != in->ofs) { + continue; + } + + switch (in->type) { + case AXIS: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)evtbuf[i].dwData); + break; + case BUTTON: + SDL_SendJoystickButton(timestamp, joystick, in->num, + (evtbuf[i].dwData != 0)); + break; + case HAT: + { + Uint8 pos = TranslatePOV(evtbuf[i].dwData); + SDL_SendJoystickHat(timestamp, joystick, in->num, pos); + } break; + } + } + } + + if (result == DI_BUFFEROVERFLOW) { + /* Our buffer wasn't big enough to hold all the queued events, + * so poll the device to make sure we have the complete state. + */ + UpdateDINPUTJoystickState_Polled(joystick); + } +} + +void SDL_DINPUT_JoystickUpdate(SDL_Joystick *joystick) +{ + HRESULT result; + + result = IDirectInputDevice8_Poll(joystick->hwdata->InputDevice); + if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) { + IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + IDirectInputDevice8_Poll(joystick->hwdata->InputDevice); + } + + if (joystick->hwdata->first_update) { + // Poll to get the initial state of the joystick + UpdateDINPUTJoystickState_Polled(joystick); + joystick->hwdata->first_update = false; + return; + } + + if (joystick->hwdata->buffered ) { + UpdateDINPUTJoystickState_Buffered(joystick); + } else { + UpdateDINPUTJoystickState_Polled(joystick); + } +} + +void SDL_DINPUT_JoystickClose(SDL_Joystick *joystick) +{ + if (joystick->hwdata->ffeffect_ref) { + IDirectInputEffect_Unload(joystick->hwdata->ffeffect_ref); + joystick->hwdata->ffeffect_ref = NULL; + } + if (joystick->hwdata->ffeffect) { + FreeRumbleEffectData(joystick->hwdata->ffeffect); + joystick->hwdata->ffeffect = NULL; + } + IDirectInputDevice8_Unacquire(joystick->hwdata->InputDevice); + IDirectInputDevice8_Release(joystick->hwdata->InputDevice); + joystick->hwdata->ff_initialized = false; +} + +void SDL_DINPUT_JoystickQuit(void) +{ + if (dinput != NULL) { + IDirectInput8_Release(dinput); + dinput = NULL; + } + + if (coinitialized) { + WIN_CoUninitialize(); + coinitialized = false; + } +} + +#else // !SDL_JOYSTICK_DINPUT + +typedef struct JoyStick_DeviceData JoyStick_DeviceData; + +bool SDL_DINPUT_JoystickInit(void) +{ + return true; +} + +void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext) +{ +} + +bool SDL_DINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version) +{ + return false; +} + +bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice) +{ + return SDL_Unsupported(); +} + +bool SDL_DINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +void SDL_DINPUT_JoystickUpdate(SDL_Joystick *joystick) +{ +} + +void SDL_DINPUT_JoystickClose(SDL_Joystick *joystick) +{ +} + +void SDL_DINPUT_JoystickQuit(void) +{ +} + +#endif // SDL_JOYSTICK_DINPUT diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h new file mode 100644 index 0000000..0643ce1 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h @@ -0,0 +1,40 @@ +/* + 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" + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +extern bool SDL_DINPUT_JoystickInit(void); +extern void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext); +extern bool SDL_DINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version); +extern bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice); +extern bool SDL_DINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); +extern void SDL_DINPUT_JoystickUpdate(SDL_Joystick *joystick); +extern void SDL_DINPUT_JoystickClose(SDL_Joystick *joystick); +extern void SDL_DINPUT_JoystickQuit(void); + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c new file mode 100644 index 0000000..d5166de --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c @@ -0,0 +1,2238 @@ +/* + Simple DirectMedia Layer + Copyright (C) 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. + +*/ +/* + RAWINPUT Joystick API for better handling XInput-capable devices on Windows. + + XInput is limited to 4 devices. + Windows.Gaming.Input does not get inputs from XBox One controllers when not in the foreground. + DirectInput does not get inputs from XBox One controllers when not in the foreground, nor rumble or accurate triggers. + RawInput does not get rumble or accurate triggers. + + So, combine them as best we can! +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_RAWINPUT + +#include "../usb_ids.h" +#include "../SDL_sysjoystick.h" +#include "../../core/windows/SDL_windows.h" +#include "../../core/windows/SDL_hid.h" +#include "../hidapi/SDL_hidapijoystick_c.h" + +/* SDL_JOYSTICK_RAWINPUT_XINPUT is disabled because using XInput at the same time as + raw input will turn off the Xbox Series X controller when it is connected via the + Xbox One Wireless Adapter. + */ +#ifdef HAVE_XINPUT_H +#define SDL_JOYSTICK_RAWINPUT_XINPUT +#endif +#ifdef HAVE_WINDOWS_GAMING_INPUT_H +#define SDL_JOYSTICK_RAWINPUT_WGI +#endif + +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT +#include "../../core/windows/SDL_xinput.h" +#endif + +#ifdef SDL_JOYSTICK_RAWINPUT_WGI +#include "../../core/windows/SDL_windows.h" +typedef struct WindowsGamingInputGamepadState WindowsGamingInputGamepadState; +#define GamepadButtons_GUIDE 0x40000000 +#define COBJMACROS +#include "windows.gaming.input.h" +#include +#endif + +#if defined(SDL_JOYSTICK_RAWINPUT_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT_WGI) +#define SDL_JOYSTICK_RAWINPUT_MATCHING +#define SDL_JOYSTICK_RAWINPUT_MATCH_AXES +#define SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS +#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS +#define SDL_JOYSTICK_RAWINPUT_MATCH_COUNT 6 // stick + trigger axes +#else +#define SDL_JOYSTICK_RAWINPUT_MATCH_COUNT 4 // stick axes +#endif +#endif + +#if 0 +#define DEBUG_RAWINPUT +#endif + +#ifndef RIDEV_EXINPUTSINK +#define RIDEV_EXINPUTSINK 0x00001000 +#define RIDEV_DEVNOTIFY 0x00002000 +#endif + +#ifndef WM_INPUT_DEVICE_CHANGE +#define WM_INPUT_DEVICE_CHANGE 0x00FE +#endif +#ifndef WM_INPUT +#define WM_INPUT 0x00FF +#endif +#ifndef GIDC_ARRIVAL +#define GIDC_ARRIVAL 1 +#define GIDC_REMOVAL 2 +#endif + +extern void WINDOWS_RAWINPUTEnabledChanged(void); +extern void WINDOWS_JoystickDetect(void); + +static bool SDL_RAWINPUT_inited = false; +static bool SDL_RAWINPUT_remote_desktop = false; +static int SDL_RAWINPUT_numjoysticks = 0; + +static void RAWINPUT_JoystickClose(SDL_Joystick *joystick); + +typedef struct SDL_RAWINPUT_Device +{ + SDL_AtomicInt refcount; + char *name; + char *path; + Uint16 vendor_id; + Uint16 product_id; + Uint16 version; + SDL_GUID guid; + bool is_xinput; + bool is_xboxone; + int steam_virtual_gamepad_slot; + PHIDP_PREPARSED_DATA preparsed_data; + + HANDLE hDevice; + SDL_Joystick *joystick; + SDL_JoystickID joystick_id; + + struct SDL_RAWINPUT_Device *next; +} SDL_RAWINPUT_Device; + +struct joystick_hwdata +{ + bool is_xinput; + bool is_xboxone; + PHIDP_PREPARSED_DATA preparsed_data; + ULONG max_data_length; + HIDP_DATA *data; + USHORT *button_indices; + USHORT *axis_indices; + USHORT *hat_indices; + bool guide_hack; + bool trigger_hack; + USHORT trigger_hack_index; + +#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING + Uint64 match_state; // Lowest 16 bits for button states, higher 24 for 6 4bit axes + Uint64 last_state_packet; +#endif + +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + bool xinput_enabled; + bool xinput_correlated; + Uint8 xinput_correlation_id; + Uint8 xinput_correlation_count; + Uint8 xinput_uncorrelate_count; + Uint8 xinput_slot; +#endif + +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + bool wgi_correlated; + Uint8 wgi_correlation_id; + Uint8 wgi_correlation_count; + Uint8 wgi_uncorrelate_count; + WindowsGamingInputGamepadState *wgi_slot; +#endif + + SDL_RAWINPUT_Device *device; +}; +typedef struct joystick_hwdata RAWINPUT_DeviceContext; + +SDL_RAWINPUT_Device *SDL_RAWINPUT_devices; + +static const Uint16 subscribed_devices[] = { + USB_USAGE_GENERIC_GAMEPAD, + /* Don't need Joystick for any devices we're handling here (XInput-capable) + USB_USAGE_GENERIC_JOYSTICK, + USB_USAGE_GENERIC_MULTIAXISCONTROLLER, + */ +}; + +#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING + +static struct +{ + Uint64 last_state_packet; + SDL_Joystick *joystick; + SDL_Joystick *last_joystick; +} guide_button_candidate; + +typedef struct WindowsMatchState +{ +#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES + SHORT match_axes[SDL_JOYSTICK_RAWINPUT_MATCH_COUNT]; +#endif +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + WORD xinput_buttons; +#endif +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + Uint32 wgi_buttons; +#endif + bool any_data; +} WindowsMatchState; + +static void RAWINPUT_FillMatchState(WindowsMatchState *state, Uint64 match_state) +{ +#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES + int ii; +#endif + + bool any_axes_data = false; +#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES + /* SHORT state->match_axes[4] = { + (match_state & 0x000F0000) >> 4, + (match_state & 0x00F00000) >> 8, + (match_state & 0x0F000000) >> 12, + (match_state & 0xF0000000) >> 16, + }; */ + for (ii = 0; ii < 4; ii++) { + state->match_axes[ii] = (SHORT)((match_state & (0x000F0000ull << (ii * 4))) >> (4 + ii * 4)); + any_axes_data |= ((Uint32)(state->match_axes[ii] + 0x1000) > 0x2000); // match_state bit is not 0xF, 0x1, or 0x2 + } +#endif // SDL_JOYSTICK_RAWINPUT_MATCH_AXES +#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS + for (; ii < SDL_JOYSTICK_RAWINPUT_MATCH_COUNT; ii++) { + state->match_axes[ii] = (SHORT)((match_state & (0x000F0000ull << (ii * 4))) >> (4 + ii * 4)); + any_axes_data |= (state->match_axes[ii] != SDL_MIN_SINT16); + } +#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS + + state->any_data = any_axes_data; + +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + // Match axes by checking if the distance between the high 4 bits of axis and the 4 bits from match_state is 1 or less +#define XInputAxesMatch(gamepad) ( \ + (Uint32)(gamepad.sThumbLX - state->match_axes[0] + 0x1000) <= 0x2fff && \ + (Uint32)(~gamepad.sThumbLY - state->match_axes[1] + 0x1000) <= 0x2fff && \ + (Uint32)(gamepad.sThumbRX - state->match_axes[2] + 0x1000) <= 0x2fff && \ + (Uint32)(~gamepad.sThumbRY - state->match_axes[3] + 0x1000) <= 0x2fff) + /* Explicit +#define XInputAxesMatch(gamepad) (\ + SDL_abs((Sint8)((gamepad.sThumbLX & 0xF000) >> 8) - ((match_state & 0x000F0000) >> 12)) <= 0x10 && \ + SDL_abs((Sint8)((~gamepad.sThumbLY & 0xF000) >> 8) - ((match_state & 0x00F00000) >> 16)) <= 0x10 && \ + SDL_abs((Sint8)((gamepad.sThumbRX & 0xF000) >> 8) - ((match_state & 0x0F000000) >> 20)) <= 0x10 && \ + SDL_abs((Sint8)((~gamepad.sThumbRY & 0xF000) >> 8) - ((match_state & 0xF0000000) >> 24)) <= 0x10) */ + + // Can only match trigger values if a single trigger has a value. +#define XInputTriggersMatch(gamepad) ( \ + ((state->match_axes[4] == SDL_MIN_SINT16) && (state->match_axes[5] == SDL_MIN_SINT16)) || \ + ((gamepad.bLeftTrigger != 0) && (gamepad.bRightTrigger != 0)) || \ + ((Uint32)((((int)gamepad.bLeftTrigger * 257) - 32768) - state->match_axes[4]) <= 0x2fff) || \ + ((Uint32)((((int)gamepad.bRightTrigger * 257) - 32768) - state->match_axes[5]) <= 0x2fff)) + + state->xinput_buttons = + // Bitwise map .RLDUWVQTS.KYXBA -> YXBA..WVQTKSRLDU + (WORD)(match_state << 12 | (match_state & 0x0780) >> 1 | (match_state & 0x0010) << 1 | (match_state & 0x0040) >> 2 | (match_state & 0x7800) >> 11); + /* Explicit + ((match_state & (1<xinput_buttons) { + state->any_data = true; + } +#endif + +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + // Match axes by checking if the distance between the high 4 bits of axis and the 4 bits from match_state is 1 or less +#define WindowsGamingInputAxesMatch(gamepad) ( \ + (Uint16)(((Sint16)(gamepad.LeftThumbstickX * SDL_MAX_SINT16) & 0xF000) - state->match_axes[0] + 0x1000) <= 0x2fff && \ + (Uint16)((~(Sint16)(gamepad.LeftThumbstickY * SDL_MAX_SINT16) & 0xF000) - state->match_axes[1] + 0x1000) <= 0x2fff && \ + (Uint16)(((Sint16)(gamepad.RightThumbstickX * SDL_MAX_SINT16) & 0xF000) - state->match_axes[2] + 0x1000) <= 0x2fff && \ + (Uint16)((~(Sint16)(gamepad.RightThumbstickY * SDL_MAX_SINT16) & 0xF000) - state->match_axes[3] + 0x1000) <= 0x2fff) + +#define WindowsGamingInputTriggersMatch(gamepad) ( \ + ((state->match_axes[4] == SDL_MIN_SINT16) && (state->match_axes[5] == SDL_MIN_SINT16)) || \ + ((gamepad.LeftTrigger == 0.0f) && (gamepad.RightTrigger == 0.0f)) || \ + ((Uint16)((((int)(gamepad.LeftTrigger * SDL_MAX_UINT16)) - 32768) - state->match_axes[4]) <= 0x2fff) || \ + ((Uint16)((((int)(gamepad.RightTrigger * SDL_MAX_UINT16)) - 32768) - state->match_axes[5]) <= 0x2fff)) + + state->wgi_buttons = + // Bitwise map .RLD UWVQ TS.K YXBA -> ..QT WVRL DUYX BAKS + // RStick/LStick (QT) RShould/LShould (WV) DPad R/L/D/U YXBA bac(K) (S)tart + (match_state & 0x0180) << 5 | (match_state & 0x0600) << 1 | (match_state & 0x7800) >> 5 | (match_state & 0x000F) << 2 | (match_state & 0x0010) >> 3 | (match_state & 0x0040) >> 6; + /* Explicit + ((match_state & (1<wgi_buttons) { + state->any_data = true; + } +#endif +} + +#endif // SDL_JOYSTICK_RAWINPUT_MATCHING + +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + +static struct +{ + XINPUT_STATE state; + XINPUT_BATTERY_INFORMATION_EX battery; + bool connected; // Currently has an active XInput device + bool used; // Is currently mapped to an SDL device + Uint8 correlation_id; +} xinput_state[XUSER_MAX_COUNT]; +static bool xinput_device_change = true; +static bool xinput_state_dirty = true; + +static void RAWINPUT_UpdateXInput(void) +{ + DWORD user_index; + if (xinput_device_change) { + for (user_index = 0; user_index < XUSER_MAX_COUNT; user_index++) { + XINPUT_CAPABILITIES capabilities; + xinput_state[user_index].connected = (XINPUTGETCAPABILITIES(user_index, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS); + } + xinput_device_change = false; + xinput_state_dirty = true; + } + if (xinput_state_dirty) { + xinput_state_dirty = false; + for (user_index = 0; user_index < SDL_arraysize(xinput_state); ++user_index) { + if (xinput_state[user_index].connected) { + if (XINPUTGETSTATE(user_index, &xinput_state[user_index].state) != ERROR_SUCCESS) { + xinput_state[user_index].connected = false; + } + xinput_state[user_index].battery.BatteryType = BATTERY_TYPE_UNKNOWN; + if (XINPUTGETBATTERYINFORMATION) { + XINPUTGETBATTERYINFORMATION(user_index, BATTERY_DEVTYPE_GAMEPAD, &xinput_state[user_index].battery); + } + } + } + } +} + +static void RAWINPUT_MarkXInputSlotUsed(Uint8 xinput_slot) +{ + if (xinput_slot != XUSER_INDEX_ANY) { + xinput_state[xinput_slot].used = true; + } +} + +static void RAWINPUT_MarkXInputSlotFree(Uint8 xinput_slot) +{ + if (xinput_slot != XUSER_INDEX_ANY) { + xinput_state[xinput_slot].used = false; + } +} +static bool RAWINPUT_MissingXInputSlot(void) +{ + int ii; + for (ii = 0; ii < SDL_arraysize(xinput_state); ii++) { + if (xinput_state[ii].connected && !xinput_state[ii].used) { + return true; + } + } + return false; +} + +static bool RAWINPUT_XInputSlotMatches(const WindowsMatchState *state, Uint8 slot_idx) +{ + if (xinput_state[slot_idx].connected) { + WORD xinput_buttons = xinput_state[slot_idx].state.Gamepad.wButtons; + if ((xinput_buttons & ~XINPUT_GAMEPAD_GUIDE) == state->xinput_buttons +#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES + && XInputAxesMatch(xinput_state[slot_idx].state.Gamepad) +#endif +#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS + && XInputTriggersMatch(xinput_state[slot_idx].state.Gamepad) +#endif + ) { + return true; + } + } + return false; +} + +static bool RAWINPUT_GuessXInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, Uint8 *slot_idx) +{ + Uint8 user_index; + int match_count; + + /* If there is only one available slot, let's use that + * That will be right most of the time, and uncorrelation will fix any bad guesses + */ + match_count = 0; + for (user_index = 0; user_index < XUSER_MAX_COUNT; ++user_index) { + if (xinput_state[user_index].connected && !xinput_state[user_index].used) { + *slot_idx = user_index; + ++match_count; + } + } + if (match_count == 1) { + *correlation_id = ++xinput_state[*slot_idx].correlation_id; + return true; + } + + *slot_idx = 0; + + match_count = 0; + for (user_index = 0; user_index < XUSER_MAX_COUNT; ++user_index) { + if (!xinput_state[user_index].used && RAWINPUT_XInputSlotMatches(state, user_index)) { + ++match_count; + *slot_idx = user_index; + // Incrementing correlation_id for any match, as negative evidence for others being correlated + *correlation_id = ++xinput_state[user_index].correlation_id; + } + } + /* Only return a match if we match exactly one, and we have some non-zero data (buttons or axes) that matched. + Note that we're still invalidating *other* potential correlations if we have more than one match or we have no + data. */ + if (match_count == 1 && state->any_data) { + return true; + } + return false; +} + +#endif // SDL_JOYSTICK_RAWINPUT_XINPUT + +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + +typedef struct WindowsGamingInputGamepadState +{ + __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad; + struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading state; + RAWINPUT_DeviceContext *correlated_context; + bool used; // Is currently mapped to an SDL device + bool connected; // Just used during update to track disconnected + Uint8 correlation_id; + struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; +} WindowsGamingInputGamepadState; + +static struct +{ + WindowsGamingInputGamepadState **per_gamepad; + int per_gamepad_count; + bool initialized; + bool dirty; + bool need_device_list_update; + int ref_count; + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics *gamepad_statics; + EventRegistrationToken gamepad_added_token; + EventRegistrationToken gamepad_removed_token; +} wgi_state; + +typedef struct GamepadDelegate +{ + __FIEventHandler_1_Windows__CGaming__CInput__CGamepad iface; + SDL_AtomicInt refcount; +} GamepadDelegate; + +static const IID IID_IEventHandler_Gamepad = { 0x8a7639ee, 0x624a, 0x501a, { 0xbb, 0x53, 0x56, 0x2d, 0x1e, 0xc1, 0x1b, 0x52 } }; + +static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_QueryInterface(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, REFIID riid, void **ppvObject) +{ + if (!ppvObject) { + return E_INVALIDARG; + } + + *ppvObject = NULL; + if (WIN_IsEqualIID(riid, &IID_IUnknown) || WIN_IsEqualIID(riid, &IID_IAgileObject) || WIN_IsEqualIID(riid, &IID_IEventHandler_Gamepad)) { + *ppvObject = This; + __FIEventHandler_1_Windows__CGaming__CInput__CGamepad_AddRef(This); + return S_OK; + } else if (WIN_IsEqualIID(riid, &IID_IMarshal)) { + // This seems complicated. Let's hope it doesn't happen. + return E_OUTOFMEMORY; + } else { + return E_NOINTERFACE; + } +} + +static ULONG STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_AddRef(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This) +{ + GamepadDelegate *self = (GamepadDelegate *)This; + return SDL_AddAtomicInt(&self->refcount, 1) + 1UL; +} + +static ULONG STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_Release(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This) +{ + GamepadDelegate *self = (GamepadDelegate *)This; + int rc = SDL_AddAtomicInt(&self->refcount, -1) - 1; + // Should never free the static delegate objects + SDL_assert(rc > 0); + return rc; +} + +static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_InvokeAdded(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIGamepad *e) +{ + wgi_state.need_device_list_update = true; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_InvokeRemoved(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIGamepad *e) +{ + wgi_state.need_device_list_update = true; + return S_OK; +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4028) // formal parameter 3 different from declaration, when using older buggy WGI headers +#pragma warning(disable : 4113) // X differs in parameter lists from Y, when using older buggy WGI headers +#endif + +static __FIEventHandler_1_Windows__CGaming__CInput__CGamepadVtbl gamepad_added_vtbl = { + IEventHandler_CGamepadVtbl_QueryInterface, + IEventHandler_CGamepadVtbl_AddRef, + IEventHandler_CGamepadVtbl_Release, + IEventHandler_CGamepadVtbl_InvokeAdded +}; +static GamepadDelegate gamepad_added = { + { &gamepad_added_vtbl }, + { 1 } +}; + +static __FIEventHandler_1_Windows__CGaming__CInput__CGamepadVtbl gamepad_removed_vtbl = { + IEventHandler_CGamepadVtbl_QueryInterface, + IEventHandler_CGamepadVtbl_AddRef, + IEventHandler_CGamepadVtbl_Release, + IEventHandler_CGamepadVtbl_InvokeRemoved +}; +static GamepadDelegate gamepad_removed = { + { &gamepad_removed_vtbl }, + { 1 } +}; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +static void RAWINPUT_MarkWindowsGamingInputSlotUsed(WindowsGamingInputGamepadState *wgi_slot, RAWINPUT_DeviceContext *ctx) +{ + wgi_slot->used = true; + wgi_slot->correlated_context = ctx; +} + +static void RAWINPUT_MarkWindowsGamingInputSlotFree(WindowsGamingInputGamepadState *wgi_slot) +{ + wgi_slot->used = false; + wgi_slot->correlated_context = NULL; +} + +static bool RAWINPUT_MissingWindowsGamingInputSlot(void) +{ + int ii; + for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) { + if (!wgi_state.per_gamepad[ii]->used) { + return true; + } + } + return false; +} + +static bool RAWINPUT_UpdateWindowsGamingInput(void) +{ + int ii; + if (!wgi_state.gamepad_statics) { + return true; + } + + if (!wgi_state.dirty) { + return true; + } + + wgi_state.dirty = false; + + if (wgi_state.need_device_list_update) { + HRESULT hr; + __FIVectorView_1_Windows__CGaming__CInput__CGamepad *gamepads; + wgi_state.need_device_list_update = false; + for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) { + wgi_state.per_gamepad[ii]->connected = false; + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_get_Gamepads(wgi_state.gamepad_statics, &gamepads); + if (SUCCEEDED(hr)) { + unsigned int num_gamepads; + + hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_get_Size(gamepads, &num_gamepads); + if (SUCCEEDED(hr)) { + unsigned int i; + for (i = 0; i < num_gamepads; ++i) { + __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad; + + hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_GetAt(gamepads, i, &gamepad); + if (SUCCEEDED(hr)) { + bool found = false; + int jj; + for (jj = 0; jj < wgi_state.per_gamepad_count; jj++) { + if (wgi_state.per_gamepad[jj]->gamepad == gamepad) { + found = true; + wgi_state.per_gamepad[jj]->connected = true; + break; + } + } + if (!found) { + // New device, add it + WindowsGamingInputGamepadState *gamepad_state; + WindowsGamingInputGamepadState **new_per_gamepad; + gamepad_state = SDL_calloc(1, sizeof(*gamepad_state)); + if (!gamepad_state) { + return false; + } + new_per_gamepad = SDL_realloc(wgi_state.per_gamepad, sizeof(wgi_state.per_gamepad[0]) * (wgi_state.per_gamepad_count + 1)); + if (!new_per_gamepad) { + SDL_free(gamepad_state); + return false; + } + wgi_state.per_gamepad = new_per_gamepad; + wgi_state.per_gamepad_count++; + wgi_state.per_gamepad[wgi_state.per_gamepad_count - 1] = gamepad_state; + gamepad_state->gamepad = gamepad; + gamepad_state->connected = true; + } else { + // Already tracked + __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad); + } + } + } + for (ii = wgi_state.per_gamepad_count - 1; ii >= 0; ii--) { + WindowsGamingInputGamepadState *gamepad_state = wgi_state.per_gamepad[ii]; + if (!gamepad_state->connected) { + // Device missing, must be disconnected + if (gamepad_state->correlated_context) { + gamepad_state->correlated_context->wgi_correlated = false; + gamepad_state->correlated_context->wgi_slot = NULL; + } + __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad_state->gamepad); + SDL_free(gamepad_state); + wgi_state.per_gamepad[ii] = wgi_state.per_gamepad[wgi_state.per_gamepad_count - 1]; + --wgi_state.per_gamepad_count; + } + } + } + __FIVectorView_1_Windows__CGaming__CInput__CGamepad_Release(gamepads); + } + } // need_device_list_update + + for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) { + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading(wgi_state.per_gamepad[ii]->gamepad, &wgi_state.per_gamepad[ii]->state); + if (!SUCCEEDED(hr)) { + wgi_state.per_gamepad[ii]->connected = false; // Not used by anything, currently + } + } + return true; +} +static void RAWINPUT_InitWindowsGamingInput(RAWINPUT_DeviceContext *ctx) +{ + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, true)) { + return; + } + + wgi_state.ref_count++; + if (!wgi_state.initialized) { + static const IID SDL_IID_IGamepadStatics = { 0x8BBCE529, 0xD49C, 0x39E9, { 0x95, 0x60, 0xE4, 0x7D, 0xDE, 0x96, 0xB7, 0xC8 } }; + HRESULT hr; + + if (FAILED(WIN_RoInitialize())) { + return; + } + wgi_state.initialized = true; + wgi_state.dirty = true; + + { + typedef HRESULT(WINAPI * WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER * hstringHeader, HSTRING * string); + typedef HRESULT(WINAPI * RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void **factory); + + WindowsCreateStringReference_t WindowsCreateStringReferenceFunc = (WindowsCreateStringReference_t)WIN_LoadComBaseFunction("WindowsCreateStringReference"); + RoGetActivationFactory_t RoGetActivationFactoryFunc = (RoGetActivationFactory_t)WIN_LoadComBaseFunction("RoGetActivationFactory"); + if (WindowsCreateStringReferenceFunc && RoGetActivationFactoryFunc) { + PCWSTR pNamespace = L"Windows.Gaming.Input.Gamepad"; + HSTRING_HEADER hNamespaceStringHeader; + HSTRING hNamespaceString; + + hr = WindowsCreateStringReferenceFunc(pNamespace, (UINT32)SDL_wcslen(pNamespace), &hNamespaceStringHeader, &hNamespaceString); + if (SUCCEEDED(hr)) { + RoGetActivationFactoryFunc(hNamespaceString, &SDL_IID_IGamepadStatics, (void **)&wgi_state.gamepad_statics); + } + + if (wgi_state.gamepad_statics) { + wgi_state.need_device_list_update = true; + + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_add_GamepadAdded(wgi_state.gamepad_statics, &gamepad_added.iface, &wgi_state.gamepad_added_token); + if (!SUCCEEDED(hr)) { + SDL_SetError("add_GamepadAdded() failed: 0x%lx", hr); + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_add_GamepadRemoved(wgi_state.gamepad_statics, &gamepad_removed.iface, &wgi_state.gamepad_removed_token); + if (!SUCCEEDED(hr)) { + SDL_SetError("add_GamepadRemoved() failed: 0x%lx", hr); + } + } + } + } + } +} + +static bool RAWINPUT_WindowsGamingInputSlotMatches(const WindowsMatchState *state, WindowsGamingInputGamepadState *slot, bool xinput_correlated) +{ + Uint32 wgi_buttons = slot->state.Buttons; + if ((wgi_buttons & 0x3FFF) == state->wgi_buttons +#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES + && WindowsGamingInputAxesMatch(slot->state) +#endif +#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS + // Don't try to match WGI triggers if getting values from XInput + && (xinput_correlated || WindowsGamingInputTriggersMatch(slot->state)) +#endif + ) { + return true; + } + return false; +} + +static bool RAWINPUT_GuessWindowsGamingInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, WindowsGamingInputGamepadState **slot, bool xinput_correlated) +{ + int match_count, user_index; + WindowsGamingInputGamepadState *gamepad_state = NULL; + + /* If there is only one available slot, let's use that + * That will be right most of the time, and uncorrelation will fix any bad guesses + */ + match_count = 0; + for (user_index = 0; user_index < wgi_state.per_gamepad_count; ++user_index) { + gamepad_state = wgi_state.per_gamepad[user_index]; + if (gamepad_state->connected && !gamepad_state->used) { + *slot = gamepad_state; + ++match_count; + } + } + if (match_count == 1) { + *correlation_id = ++gamepad_state->correlation_id; + return true; + } + + match_count = 0; + for (user_index = 0; user_index < wgi_state.per_gamepad_count; ++user_index) { + gamepad_state = wgi_state.per_gamepad[user_index]; + if (RAWINPUT_WindowsGamingInputSlotMatches(state, gamepad_state, xinput_correlated)) { + ++match_count; + *slot = gamepad_state; + // Incrementing correlation_id for any match, as negative evidence for others being correlated + *correlation_id = ++gamepad_state->correlation_id; + } + } + /* Only return a match if we match exactly one, and we have some non-zero data (buttons or axes) that matched. + Note that we're still invalidating *other* potential correlations if we have more than one match or we have no + data. */ + if (match_count == 1 && state->any_data) { + return true; + } + return false; +} + +static void RAWINPUT_QuitWindowsGamingInput(RAWINPUT_DeviceContext *ctx) +{ + --wgi_state.ref_count; + if (!wgi_state.ref_count && wgi_state.initialized) { + int ii; + for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) { + __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(wgi_state.per_gamepad[ii]->gamepad); + } + if (wgi_state.per_gamepad) { + SDL_free(wgi_state.per_gamepad); + wgi_state.per_gamepad = NULL; + } + wgi_state.per_gamepad_count = 0; + if (wgi_state.gamepad_statics) { + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_remove_GamepadAdded(wgi_state.gamepad_statics, wgi_state.gamepad_added_token); + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_remove_GamepadRemoved(wgi_state.gamepad_statics, wgi_state.gamepad_removed_token); + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_Release(wgi_state.gamepad_statics); + wgi_state.gamepad_statics = NULL; + } + WIN_RoUninitialize(); + wgi_state.initialized = false; + } +} + +#endif // SDL_JOYSTICK_RAWINPUT_WGI + +static SDL_RAWINPUT_Device *RAWINPUT_AcquireDevice(SDL_RAWINPUT_Device *device) +{ + SDL_AtomicIncRef(&device->refcount); + return device; +} + +static void RAWINPUT_ReleaseDevice(SDL_RAWINPUT_Device *device) +{ +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + if (device->joystick) { + RAWINPUT_DeviceContext *ctx = device->joystick->hwdata; + + if (ctx->xinput_enabled && ctx->xinput_correlated) { + RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot); + ctx->xinput_correlated = false; + } + } +#endif // SDL_JOYSTICK_RAWINPUT_XINPUT + + if (SDL_AtomicDecRef(&device->refcount)) { + SDL_free(device->preparsed_data); + SDL_free(device->name); + SDL_free(device->path); + SDL_free(device); + } +} + +static SDL_RAWINPUT_Device *RAWINPUT_DeviceFromHandle(HANDLE hDevice) +{ + SDL_RAWINPUT_Device *curr; + + for (curr = SDL_RAWINPUT_devices; curr; curr = curr->next) { + if (curr->hDevice == hDevice) { + return curr; + } + } + return NULL; +} + +static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path) +{ + int slot = -1; + + // The format for the raw input device path is documented here: + // https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices + if (vendor_id == USB_VENDOR_VALVE && + product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + (void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot); + } + return slot; +} + +static void RAWINPUT_AddDevice(HANDLE hDevice) +{ +#define CHECK(expression) \ + { \ + if (!(expression)) \ + goto err; \ + } + SDL_RAWINPUT_Device *device = NULL; + SDL_RAWINPUT_Device *curr, *last; + RID_DEVICE_INFO rdi; + UINT size; + char dev_name[MAX_PATH] = { 0 }; + HANDLE hFile = INVALID_HANDLE_VALUE; + + // Make sure we're not trying to add the same device twice + if (RAWINPUT_DeviceFromHandle(hDevice)) { + return; + } + + // Figure out what kind of device it is + size = sizeof(rdi); + SDL_zero(rdi); + CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICEINFO, &rdi, &size) != (UINT)-1); + CHECK(rdi.dwType == RIM_TYPEHID); + + // Get the device "name" (HID Path) + size = SDL_arraysize(dev_name); + CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, dev_name, &size) != (UINT)-1); + // Only take XInput-capable devices + CHECK(SDL_strstr(dev_name, "IG_") != NULL); + CHECK(!SDL_ShouldIgnoreJoystick((Uint16)rdi.hid.dwVendorId, (Uint16)rdi.hid.dwProductId, (Uint16)rdi.hid.dwVersionNumber, "")); + CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_RAWINPUT_JoystickDriver, (Uint16)rdi.hid.dwVendorId, (Uint16)rdi.hid.dwProductId, (Uint16)rdi.hid.dwVersionNumber, "")); + + device = (SDL_RAWINPUT_Device *)SDL_calloc(1, sizeof(SDL_RAWINPUT_Device)); + CHECK(device); + device->hDevice = hDevice; + device->vendor_id = (Uint16)rdi.hid.dwVendorId; + device->product_id = (Uint16)rdi.hid.dwProductId; + device->version = (Uint16)rdi.hid.dwVersionNumber; + device->is_xinput = true; + device->is_xboxone = SDL_IsJoystickXboxOne(device->vendor_id, device->product_id); + device->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(device->vendor_id, device->product_id, dev_name); + + // Get HID Top-Level Collection Preparsed Data + size = 0; + CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_PREPARSEDDATA, NULL, &size) != (UINT)-1); + device->preparsed_data = (PHIDP_PREPARSED_DATA)SDL_calloc(size, sizeof(BYTE)); + CHECK(device->preparsed_data); + CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_PREPARSEDDATA, device->preparsed_data, &size) != (UINT)-1); + + hFile = CreateFileA(dev_name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + CHECK(hFile != INVALID_HANDLE_VALUE); + + { + char *manufacturer_string = NULL; + char *product_string = NULL; + WCHAR string[128]; + + if (SDL_HidD_GetManufacturerString(hFile, string, sizeof(string))) { + manufacturer_string = WIN_StringToUTF8W(string); + } + if (SDL_HidD_GetProductString(hFile, string, sizeof(string))) { + product_string = WIN_StringToUTF8W(string); + } + + device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, manufacturer_string, product_string); + device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, device->vendor_id, device->product_id, device->version, manufacturer_string, product_string, 'r', 0); + + if (manufacturer_string) { + SDL_free(manufacturer_string); + } + if (product_string) { + SDL_free(product_string); + } + } + + device->path = SDL_strdup(dev_name); + + CloseHandle(hFile); + hFile = INVALID_HANDLE_VALUE; + + device->joystick_id = SDL_GetNextObjectID(); + +#ifdef DEBUG_RAWINPUT + SDL_Log("Adding RAWINPUT device '%s' VID 0x%.4x, PID 0x%.4x, version %d, handle 0x%.8x", device->name, device->vendor_id, device->product_id, device->version, device->hDevice); +#endif + + // Add it to the list + RAWINPUT_AcquireDevice(device); + for (curr = SDL_RAWINPUT_devices, last = NULL; curr; last = curr, curr = curr->next) { + } + if (last) { + last->next = device; + } else { + SDL_RAWINPUT_devices = device; + } + + ++SDL_RAWINPUT_numjoysticks; + + SDL_PrivateJoystickAdded(device->joystick_id); + + return; + +err: + if (hFile != INVALID_HANDLE_VALUE) { + CloseHandle(hFile); + } + if (device) { + if (device->name) { + SDL_free(device->name); + } + if (device->path) { + SDL_free(device->path); + } + SDL_free(device); + } +#undef CHECK +} + +static void RAWINPUT_DelDevice(SDL_RAWINPUT_Device *device, bool send_event) +{ + SDL_RAWINPUT_Device *curr, *last; + for (curr = SDL_RAWINPUT_devices, last = NULL; curr; last = curr, curr = curr->next) { + if (curr == device) { + if (last) { + last->next = curr->next; + } else { + SDL_RAWINPUT_devices = curr->next; + } + --SDL_RAWINPUT_numjoysticks; + + SDL_PrivateJoystickRemoved(device->joystick_id); + +#ifdef DEBUG_RAWINPUT + SDL_Log("Removing RAWINPUT device '%s' VID 0x%.4x, PID 0x%.4x, version %d, handle %p", device->name, device->vendor_id, device->product_id, device->version, device->hDevice); +#endif + RAWINPUT_ReleaseDevice(device); + return; + } + } +} + +static void RAWINPUT_DetectDevices(void) +{ + UINT device_count = 0; + + if ((GetRawInputDeviceList(NULL, &device_count, sizeof(RAWINPUTDEVICELIST)) != -1) && device_count > 0) { + PRAWINPUTDEVICELIST devices = NULL; + UINT i; + + devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * device_count); + if (devices) { + device_count = GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST)); + if (device_count != (UINT)-1) { + for (i = 0; i < device_count; ++i) { + RAWINPUT_AddDevice(devices[i].hDevice); + } + } + SDL_free(devices); + } + } +} + +static void RAWINPUT_RemoveDevices(void) +{ + while (SDL_RAWINPUT_devices) { + RAWINPUT_DelDevice(SDL_RAWINPUT_devices, false); + } + SDL_assert(SDL_RAWINPUT_numjoysticks == 0); +} + +static bool RAWINPUT_JoystickInit(void) +{ + SDL_assert(!SDL_RAWINPUT_inited); + + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, true)) { + return true; + } + + if (!WIN_IsWindowsVistaOrGreater()) { + // According to bug 6400, this doesn't work on Windows XP + return false; + } + + if (!WIN_LoadHIDDLL()) { + return false; + } + + SDL_RAWINPUT_inited = true; + + RAWINPUT_DetectDevices(); + + return true; +} + +static int RAWINPUT_JoystickGetCount(void) +{ + return SDL_RAWINPUT_numjoysticks; +} + +bool RAWINPUT_IsEnabled(void) +{ + return SDL_RAWINPUT_inited && !SDL_RAWINPUT_remote_desktop; +} + +static void RAWINPUT_PostUpdate(void) +{ +#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING + bool unmapped_guide_pressed = false; + +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + if (!wgi_state.dirty) { + int ii; + for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) { + WindowsGamingInputGamepadState *gamepad_state = wgi_state.per_gamepad[ii]; + if (!gamepad_state->used && (gamepad_state->state.Buttons & GamepadButtons_GUIDE)) { + unmapped_guide_pressed = true; + break; + } + } + } + wgi_state.dirty = true; +#endif + +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + if (!xinput_state_dirty) { + int ii; + for (ii = 0; ii < SDL_arraysize(xinput_state); ii++) { + if (xinput_state[ii].connected && !xinput_state[ii].used && (xinput_state[ii].state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE)) { + unmapped_guide_pressed = true; + break; + } + } + } + xinput_state_dirty = true; +#endif + + if (unmapped_guide_pressed) { + if (guide_button_candidate.joystick && !guide_button_candidate.last_joystick) { + SDL_Joystick *joystick = guide_button_candidate.joystick; + RAWINPUT_DeviceContext *ctx = joystick->hwdata; + if (ctx->guide_hack) { + int guide_button = joystick->nbuttons - 1; + + SDL_SendJoystickButton(SDL_GetTicksNS(), guide_button_candidate.joystick, (Uint8)guide_button, true); + } + guide_button_candidate.last_joystick = guide_button_candidate.joystick; + } + } else if (guide_button_candidate.last_joystick) { + SDL_Joystick *joystick = guide_button_candidate.last_joystick; + RAWINPUT_DeviceContext *ctx = joystick->hwdata; + if (ctx->guide_hack) { + int guide_button = joystick->nbuttons - 1; + + SDL_SendJoystickButton(SDL_GetTicksNS(), joystick, (Uint8)guide_button, false); + } + guide_button_candidate.last_joystick = NULL; + } + guide_button_candidate.joystick = NULL; + +#endif // SDL_JOYSTICK_RAWINPUT_MATCHING +} + +static void RAWINPUT_JoystickDetect(void) +{ + bool remote_desktop; + + if (!SDL_RAWINPUT_inited) { + return; + } + + remote_desktop = GetSystemMetrics(SM_REMOTESESSION) ? true : false; + if (remote_desktop != SDL_RAWINPUT_remote_desktop) { + SDL_RAWINPUT_remote_desktop = remote_desktop; + + WINDOWS_RAWINPUTEnabledChanged(); + + if (remote_desktop) { + RAWINPUT_RemoveDevices(); + WINDOWS_JoystickDetect(); + } else { + WINDOWS_JoystickDetect(); + RAWINPUT_DetectDevices(); + } + } + RAWINPUT_PostUpdate(); +} + +static bool RAWINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + SDL_RAWINPUT_Device *device; + + // If we're being asked about a device, that means another API just detected one, so rescan +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + xinput_device_change = true; +#endif + + device = SDL_RAWINPUT_devices; + while (device) { + if (vendor_id == device->vendor_id && product_id == device->product_id) { + return true; + } + + /* The Xbox 360 wireless controller shows up as product 0 in WGI. + Try to match it to a Raw Input device via name or known product ID. */ + if (vendor_id == device->vendor_id && product_id == 0 && + ((name && SDL_strstr(device->name, name) != NULL) || + (device->vendor_id == USB_VENDOR_MICROSOFT && + device->product_id == USB_PRODUCT_XBOX360_XUSB_CONTROLLER))) { + return true; + } + + // The Xbox One controller shows up as a hardcoded raw input VID/PID + if (name && SDL_strcmp(name, "Xbox One Game Controller") == 0 && + device->vendor_id == USB_VENDOR_MICROSOFT && + device->product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) { + return true; + } + + device = device->next; + } + return false; +} + +static SDL_RAWINPUT_Device *RAWINPUT_GetDeviceByIndex(int device_index) +{ + SDL_RAWINPUT_Device *device = SDL_RAWINPUT_devices; + while (device) { + if (device_index == 0) { + break; + } + --device_index; + device = device->next; + } + return device; +} + +static const char *RAWINPUT_JoystickGetDeviceName(int device_index) +{ + return RAWINPUT_GetDeviceByIndex(device_index)->name; +} + +static const char *RAWINPUT_JoystickGetDevicePath(int device_index) +{ + return RAWINPUT_GetDeviceByIndex(device_index)->path; +} + +static int RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return RAWINPUT_GetDeviceByIndex(device_index)->steam_virtual_gamepad_slot; +} + +static int RAWINPUT_JoystickGetDevicePlayerIndex(int device_index) +{ + return false; +} + +static void RAWINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static SDL_GUID RAWINPUT_JoystickGetDeviceGUID(int device_index) +{ + return RAWINPUT_GetDeviceByIndex(device_index)->guid; +} + +static SDL_JoystickID RAWINPUT_JoystickGetDeviceInstanceID(int device_index) +{ + return RAWINPUT_GetDeviceByIndex(device_index)->joystick_id; +} + +static int SDLCALL RAWINPUT_SortValueCaps(const void *A, const void *B) +{ + HIDP_VALUE_CAPS *capsA = (HIDP_VALUE_CAPS *)A; + HIDP_VALUE_CAPS *capsB = (HIDP_VALUE_CAPS *)B; + + // Sort by Usage for single values, or UsageMax for range of values + return (int)capsA->NotRange.Usage - capsB->NotRange.Usage; +} + +static bool RAWINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + SDL_RAWINPUT_Device *device = RAWINPUT_GetDeviceByIndex(device_index); + RAWINPUT_DeviceContext *ctx; + HIDP_CAPS caps; + HIDP_BUTTON_CAPS *button_caps; + HIDP_VALUE_CAPS *value_caps; + ULONG i; + + ctx = (RAWINPUT_DeviceContext *)SDL_calloc(1, sizeof(RAWINPUT_DeviceContext)); + if (!ctx) { + return false; + } + joystick->hwdata = ctx; + + ctx->device = RAWINPUT_AcquireDevice(device); + device->joystick = joystick; + + if (device->is_xinput) { + // We'll try to get guide button and trigger axes from XInput +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + xinput_device_change = true; + ctx->xinput_enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT, true); + if (ctx->xinput_enabled && (!WIN_LoadXInputDLL() || !XINPUTGETSTATE)) { + ctx->xinput_enabled = false; + } + ctx->xinput_slot = XUSER_INDEX_ANY; +#endif +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + RAWINPUT_InitWindowsGamingInput(ctx); +#endif + } + + ctx->is_xinput = device->is_xinput; + ctx->is_xboxone = device->is_xboxone; +#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING + ctx->match_state = 0x0000008800000000ULL; // Trigger axes at rest +#endif + ctx->preparsed_data = device->preparsed_data; + ctx->max_data_length = SDL_HidP_MaxDataListLength(HidP_Input, ctx->preparsed_data); + ctx->data = (HIDP_DATA *)SDL_malloc(ctx->max_data_length * sizeof(*ctx->data)); + if (!ctx->data) { + RAWINPUT_JoystickClose(joystick); + return false; + } + + if (SDL_HidP_GetCaps(ctx->preparsed_data, &caps) != HIDP_STATUS_SUCCESS) { + RAWINPUT_JoystickClose(joystick); + return SDL_SetError("Couldn't get device capabilities"); + } + + button_caps = SDL_stack_alloc(HIDP_BUTTON_CAPS, caps.NumberInputButtonCaps); + if (SDL_HidP_GetButtonCaps(HidP_Input, button_caps, &caps.NumberInputButtonCaps, ctx->preparsed_data) != HIDP_STATUS_SUCCESS) { + RAWINPUT_JoystickClose(joystick); + return SDL_SetError("Couldn't get device button capabilities"); + } + + value_caps = SDL_stack_alloc(HIDP_VALUE_CAPS, caps.NumberInputValueCaps); + if (SDL_HidP_GetValueCaps(HidP_Input, value_caps, &caps.NumberInputValueCaps, ctx->preparsed_data) != HIDP_STATUS_SUCCESS) { + RAWINPUT_JoystickClose(joystick); + SDL_stack_free(button_caps); + return SDL_SetError("Couldn't get device value capabilities"); + } + + // Sort the axes by usage, so X comes before Y, etc. + SDL_qsort(value_caps, caps.NumberInputValueCaps, sizeof(*value_caps), RAWINPUT_SortValueCaps); + + for (i = 0; i < caps.NumberInputButtonCaps; ++i) { + HIDP_BUTTON_CAPS *cap = &button_caps[i]; + + if (cap->UsagePage == USB_USAGEPAGE_BUTTON) { + int count; + + if (cap->IsRange) { + count = 1 + (cap->Range.DataIndexMax - cap->Range.DataIndexMin); + } else { + count = 1; + } + + joystick->nbuttons += count; + } + } + + if (joystick->nbuttons > 0) { + int button_index = 0; + + ctx->button_indices = (USHORT *)SDL_malloc(joystick->nbuttons * sizeof(*ctx->button_indices)); + if (!ctx->button_indices) { + RAWINPUT_JoystickClose(joystick); + SDL_stack_free(value_caps); + SDL_stack_free(button_caps); + return false; + } + + for (i = 0; i < caps.NumberInputButtonCaps; ++i) { + HIDP_BUTTON_CAPS *cap = &button_caps[i]; + + if (cap->UsagePage == USB_USAGEPAGE_BUTTON) { + if (cap->IsRange) { + int j, count = 1 + (cap->Range.DataIndexMax - cap->Range.DataIndexMin); + + for (j = 0; j < count; ++j) { + ctx->button_indices[button_index++] = (USHORT)(cap->Range.DataIndexMin + j); + } + } else { + ctx->button_indices[button_index++] = cap->NotRange.DataIndex; + } + } + } + } + if (ctx->is_xinput && joystick->nbuttons == 10) { + ctx->guide_hack = true; + joystick->nbuttons += 1; + } + + SDL_stack_free(button_caps); + + for (i = 0; i < caps.NumberInputValueCaps; ++i) { + HIDP_VALUE_CAPS *cap = &value_caps[i]; + + if (cap->IsRange) { + continue; + } + + if (ctx->trigger_hack && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) { + continue; + } + + if (cap->NotRange.Usage == USB_USAGE_GENERIC_HAT) { + joystick->nhats += 1; + continue; + } + + if (ctx->is_xinput && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) { + continue; + } + + joystick->naxes += 1; + } + + if (joystick->naxes > 0) { + int axis_index = 0; + + ctx->axis_indices = (USHORT *)SDL_malloc(joystick->naxes * sizeof(*ctx->axis_indices)); + if (!ctx->axis_indices) { + RAWINPUT_JoystickClose(joystick); + SDL_stack_free(value_caps); + return false; + } + + for (i = 0; i < caps.NumberInputValueCaps; ++i) { + HIDP_VALUE_CAPS *cap = &value_caps[i]; + + if (cap->IsRange) { + continue; + } + + if (cap->NotRange.Usage == USB_USAGE_GENERIC_HAT) { + continue; + } + + if (ctx->is_xinput && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) { + ctx->trigger_hack = true; + ctx->trigger_hack_index = cap->NotRange.DataIndex; + continue; + } + + ctx->axis_indices[axis_index++] = cap->NotRange.DataIndex; + } + } + if (ctx->trigger_hack) { + joystick->naxes += 2; + } + + if (joystick->nhats > 0) { + int hat_index = 0; + + ctx->hat_indices = (USHORT *)SDL_malloc(joystick->nhats * sizeof(*ctx->hat_indices)); + if (!ctx->hat_indices) { + RAWINPUT_JoystickClose(joystick); + SDL_stack_free(value_caps); + return false; + } + + for (i = 0; i < caps.NumberInputValueCaps; ++i) { + HIDP_VALUE_CAPS *cap = &value_caps[i]; + + if (cap->IsRange) { + continue; + } + + if (cap->NotRange.Usage != USB_USAGE_GENERIC_HAT) { + continue; + } + + ctx->hat_indices[hat_index++] = cap->NotRange.DataIndex; + } + } + + SDL_stack_free(value_caps); + +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + if (ctx->is_xinput) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } +#endif +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + if (ctx->is_xinput) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + + if (ctx->is_xboxone) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); + } + } +#endif + + return true; +} + +static bool RAWINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ +#if defined(SDL_JOYSTICK_RAWINPUT_WGI) || defined(SDL_JOYSTICK_RAWINPUT_XINPUT) + RAWINPUT_DeviceContext *ctx = joystick->hwdata; +#endif + bool rumbled = false; + +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + // Prefer XInput over WGI because it allows rumble in the background + if (!rumbled && ctx->xinput_correlated) { + XINPUT_VIBRATION XVibration; + + if (!XINPUTSETSTATE) { + return SDL_Unsupported(); + } + + XVibration.wLeftMotorSpeed = low_frequency_rumble; + XVibration.wRightMotorSpeed = high_frequency_rumble; + if (XINPUTSETSTATE(ctx->xinput_slot, &XVibration) == ERROR_SUCCESS) { + rumbled = true; + } else { + return SDL_SetError("XInputSetState() failed"); + } + } +#endif // SDL_JOYSTICK_RAWINPUT_XINPUT + +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + if (!rumbled && ctx->wgi_correlated) { + WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; + HRESULT hr; + gamepad_state->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; + gamepad_state->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration); + if (SUCCEEDED(hr)) { + rumbled = true; + } + } +#endif + + if (!rumbled) { +#if defined(SDL_JOYSTICK_RAWINPUT_WGI) || defined(SDL_JOYSTICK_RAWINPUT_XINPUT) + return SDL_SetError("Controller isn't correlated yet, try hitting a button first"); +#else + return SDL_Unsupported(); +#endif + } + return true; +} + +static bool RAWINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + RAWINPUT_DeviceContext *ctx = joystick->hwdata; + + if (ctx->wgi_correlated) { + WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; + HRESULT hr; + gamepad_state->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; + gamepad_state->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration); + if (!SUCCEEDED(hr)) { + return SDL_SetError("Setting vibration failed: 0x%lx", hr); + } + return true; + } else { + return SDL_SetError("Controller isn't correlated yet, try hitting a button first"); + } +#else + return SDL_Unsupported(); +#endif +} + +static bool RAWINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool RAWINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool RAWINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static HIDP_DATA *GetData(USHORT index, HIDP_DATA *data, ULONG length) +{ + ULONG i; + + // Check to see if the data is at the expected offset + if (index < length && data[index].DataIndex == index) { + return &data[index]; + } + + // Loop through the data to find it + for (i = 0; i < length; ++i) { + if (data[i].DataIndex == index) { + return &data[i]; + } + } + return NULL; +} + +/* This is the packet format for Xbox 360 and Xbox One controllers on Windows, + however with this interface there is no rumble support, no guide button, + and the left and right triggers are tied together as a single axis. + + We use XInput and Windows.Gaming.Input to make up for these shortcomings. + */ +static void RAWINPUT_HandleStatePacket(SDL_Joystick *joystick, Uint8 *data, int size) +{ + RAWINPUT_DeviceContext *ctx = joystick->hwdata; +#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING + // Map new buttons and axes into game controller controls + static const int button_map[] = { + SDL_GAMEPAD_BUTTON_SOUTH, + SDL_GAMEPAD_BUTTON_EAST, + SDL_GAMEPAD_BUTTON_WEST, + SDL_GAMEPAD_BUTTON_NORTH, + SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + SDL_GAMEPAD_BUTTON_BACK, + SDL_GAMEPAD_BUTTON_START, + SDL_GAMEPAD_BUTTON_LEFT_STICK, + SDL_GAMEPAD_BUTTON_RIGHT_STICK + }; +#define HAT_MASK ((1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) + static const int hat_map[] = { + 0, + (1 << SDL_GAMEPAD_BUTTON_DPAD_UP), + (1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT), + (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT), + (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT), + (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN), + (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT), + (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT), + (1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT), + 0, + }; + Uint64 match_state = ctx->match_state; + // Update match_state with button bit, then fall through +#define SDL_SendJoystickButton(timestamp, joystick, button, down) \ + if (button < SDL_arraysize(button_map)) { \ + Uint64 button_bit = 1ull << button_map[button]; \ + match_state = (match_state & ~button_bit) | (button_bit * (down)); \ + } \ + SDL_SendJoystickButton(timestamp, joystick, button, down) +#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES + // Grab high 4 bits of value, then fall through +#define AddAxisToMatchState(axis, value) \ + { \ + match_state = (match_state & ~(0xFull << (4 * axis + 16))) | ((value)&0xF000ull) << (4 * axis + 4); \ + } +#define SDL_SendJoystickAxis(timestamp, joystick, axis, value) \ + if (axis < 4) \ + AddAxisToMatchState(axis, value); \ + SDL_SendJoystickAxis(timestamp, joystick, axis, value) +#endif +#endif // SDL_JOYSTICK_RAWINPUT_MATCHING + + ULONG data_length = ctx->max_data_length; + int i; + int nbuttons = joystick->nbuttons - (ctx->guide_hack * 1); + int naxes = joystick->naxes - (ctx->trigger_hack * 2); + int nhats = joystick->nhats; + Uint32 button_mask = 0; + Uint64 timestamp = SDL_GetTicksNS(); + + if (SDL_HidP_GetData(HidP_Input, ctx->data, &data_length, ctx->preparsed_data, (PCHAR)data, size) != HIDP_STATUS_SUCCESS) { + return; + } + + for (i = 0; i < nbuttons; ++i) { + HIDP_DATA *item = GetData(ctx->button_indices[i], ctx->data, data_length); + if (item && item->On) { + button_mask |= (1 << i); + } + } + for (i = 0; i < nbuttons; ++i) { + SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, ((button_mask & (1 << i)) != 0)); + } + + for (i = 0; i < naxes; ++i) { + HIDP_DATA *item = GetData(ctx->axis_indices[i], ctx->data, data_length); + if (item) { + Sint16 axis = (int)(Uint16)item->RawValue - 0x8000; + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, axis); + } + } + + for (i = 0; i < nhats; ++i) { + HIDP_DATA *item = GetData(ctx->hat_indices[i], ctx->data, data_length); + if (item) { + Uint8 hat = SDL_HAT_CENTERED; + const Uint8 hat_states[] = { + SDL_HAT_CENTERED, + SDL_HAT_UP, + SDL_HAT_UP | SDL_HAT_RIGHT, + SDL_HAT_RIGHT, + SDL_HAT_DOWN | SDL_HAT_RIGHT, + SDL_HAT_DOWN, + SDL_HAT_DOWN | SDL_HAT_LEFT, + SDL_HAT_LEFT, + SDL_HAT_UP | SDL_HAT_LEFT, + SDL_HAT_CENTERED, + }; + ULONG state = item->RawValue; + + if (state < SDL_arraysize(hat_states)) { +#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING + match_state = (match_state & ~HAT_MASK) | hat_map[state]; +#endif + hat = hat_states[state]; + } + SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat); + } + } + +#ifdef SDL_SendJoystickButton +#undef SDL_SendJoystickButton +#endif +#ifdef SDL_SendJoystickAxis +#undef SDL_SendJoystickAxis +#endif + +#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS +#define AddTriggerToMatchState(axis, value) \ + { \ + int match_axis = axis + SDL_JOYSTICK_RAWINPUT_MATCH_COUNT - joystick->naxes; \ + AddAxisToMatchState(match_axis, value); \ + } +#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS + + if (ctx->trigger_hack) { + bool has_trigger_data = false; + int left_trigger = joystick->naxes - 2; + int right_trigger = joystick->naxes - 1; + +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + // Prefer XInput over WindowsGamingInput, it continues to provide data in the background + if (!has_trigger_data && ctx->xinput_enabled && ctx->xinput_correlated) { + has_trigger_data = true; + } +#endif // SDL_JOYSTICK_RAWINPUT_XINPUT + +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + if (!has_trigger_data && ctx->wgi_correlated) { + has_trigger_data = true; + } +#endif // SDL_JOYSTICK_RAWINPUT_WGI + +#ifndef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS + if (!has_trigger_data) +#endif + { + HIDP_DATA *item = GetData(ctx->trigger_hack_index, ctx->data, data_length); + if (item) { + Sint16 value = (int)(Uint16)item->RawValue - 0x8000; + Sint16 left_value = (value > 0) ? (value * 2 - 32767) : SDL_MIN_SINT16; + Sint16 right_value = (value < 0) ? (-value * 2 - 32769) : SDL_MIN_SINT16; + +#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS + AddTriggerToMatchState(left_trigger, left_value); + AddTriggerToMatchState(right_trigger, right_value); + if (!has_trigger_data) +#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS + { + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, left_value); + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, right_value); + } + } + } + } + +#ifdef AddAxisToMatchState +#undef AddAxisToMatchState +#endif +#ifdef AddTriggerToMatchState +#undef AddTriggerToMatchState +#endif + +#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING + if (ctx->is_xinput) { + ctx->match_state = match_state; + ctx->last_state_packet = SDL_GetTicks(); + } +#endif +} + +static void RAWINPUT_UpdateOtherAPIs(SDL_Joystick *joystick) +{ +#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING + RAWINPUT_DeviceContext *ctx = joystick->hwdata; + bool has_trigger_data = false; + bool correlated = false; + WindowsMatchState match_state_xinput; + int guide_button = joystick->nbuttons - 1; + int left_trigger = joystick->naxes - 2; + int right_trigger = joystick->naxes - 1; +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + bool xinput_correlated; +#endif + + RAWINPUT_FillMatchState(&match_state_xinput, ctx->match_state); + +#ifdef SDL_JOYSTICK_RAWINPUT_WGI +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + xinput_correlated = ctx->xinput_correlated; +#else + xinput_correlated = false; +#endif + // Parallel logic to WINDOWS_XINPUT below + RAWINPUT_UpdateWindowsGamingInput(); + if (ctx->wgi_correlated && + !joystick->low_frequency_rumble && !joystick->high_frequency_rumble && + !joystick->left_trigger_rumble && !joystick->right_trigger_rumble) { + // We have been previously correlated, ensure we are still matching, see comments in XINPUT section + if (RAWINPUT_WindowsGamingInputSlotMatches(&match_state_xinput, ctx->wgi_slot, xinput_correlated)) { + ctx->wgi_uncorrelate_count = 0; + } else { + ++ctx->wgi_uncorrelate_count; + /* Only un-correlate if this is consistent over multiple Update() calls - the timing of polling/event + pumping can easily cause this to uncorrelate for a frame. 2 seemed reliable in my testing, but + let's set it to 5 to be safe. An incorrect un-correlation will simply result in lower precision + triggers for a frame. */ + if (ctx->wgi_uncorrelate_count >= 5) { +#ifdef DEBUG_RAWINPUT + SDL_Log("UN-Correlated joystick %d to WindowsGamingInput device #%d", joystick->instance_id, ctx->wgi_slot); +#endif + RAWINPUT_MarkWindowsGamingInputSlotFree(ctx->wgi_slot); + ctx->wgi_correlated = false; + ctx->wgi_correlation_count = 0; + // Force release of Guide button, it can't possibly be down on this device now. + /* It gets left down if we were actually correlated incorrectly and it was released on the WindowsGamingInput + device but we didn't get a state packet. */ + if (ctx->guide_hack) { + SDL_SendJoystickButton(0, joystick, (Uint8)guide_button, false); + } + } + } + } + if (!ctx->wgi_correlated) { + Uint8 new_correlation_count = 0; + if (RAWINPUT_MissingWindowsGamingInputSlot()) { + Uint8 correlation_id = 0; + WindowsGamingInputGamepadState *slot_idx = NULL; + if (RAWINPUT_GuessWindowsGamingInputSlot(&match_state_xinput, &correlation_id, &slot_idx, xinput_correlated)) { + // we match exactly one WindowsGamingInput device + /* Probably can do without wgi_correlation_count, just check and clear wgi_slot to NULL, unless we need + even more frames to be sure. */ + if (ctx->wgi_correlation_count && ctx->wgi_slot == slot_idx) { + // was correlated previously, and still the same device + if (ctx->wgi_correlation_id + 1 == correlation_id) { + // no one else was correlated in the meantime + new_correlation_count = ctx->wgi_correlation_count + 1; + if (new_correlation_count == 2) { + // correlation stayed steady and uncontested across multiple frames, guaranteed match + ctx->wgi_correlated = true; +#ifdef DEBUG_RAWINPUT + SDL_Log("Correlated joystick %d to WindowsGamingInput device #%d", joystick->instance_id, slot_idx); +#endif + correlated = true; + RAWINPUT_MarkWindowsGamingInputSlotUsed(ctx->wgi_slot, ctx); + // If the generalized Guide button was using us, it doesn't need to anymore + if (guide_button_candidate.joystick == joystick) { + guide_button_candidate.joystick = NULL; + } + if (guide_button_candidate.last_joystick == joystick) { + guide_button_candidate.last_joystick = NULL; + } + } + } else { + // someone else also possibly correlated to this device, start over + new_correlation_count = 1; + } + } else { + // new possible correlation + new_correlation_count = 1; + ctx->wgi_slot = slot_idx; + } + ctx->wgi_correlation_id = correlation_id; + } else { + // Match multiple WindowsGamingInput devices, or none (possibly due to no buttons pressed) + } + } + ctx->wgi_correlation_count = new_correlation_count; + } else { + correlated = true; + } +#endif // SDL_JOYSTICK_RAWINPUT_WGI + +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + // Parallel logic to WINDOWS_GAMING_INPUT above + if (ctx->xinput_enabled) { + RAWINPUT_UpdateXInput(); + if (ctx->xinput_correlated && + !joystick->low_frequency_rumble && !joystick->high_frequency_rumble) { + // We have been previously correlated, ensure we are still matching + /* This is required to deal with two (mostly) un-preventable mis-correlation situations: + A) Since the HID data stream does not provide an initial state (but polling XInput does), if we open + 5 controllers (#1-4 XInput mapped, #5 is not), and controller 1 had the A button down (and we don't + know), and the user presses A on controller #5, we'll see exactly 1 controller with A down (#5) and + exactly 1 XInput device with A down (#1), and incorrectly correlate. This code will then un-correlate + when A is released from either controller #1 or #5. + B) Since the app may not open all controllers, we could have a similar situation where only controller #5 + is opened, and the user holds A on controllers #1 and #5 simultaneously - again we see only 1 controller + with A down and 1 XInput device with A down, and incorrectly correlate. This should be very unusual + (only when apps do not open all controllers, yet are listening to Guide button presses, yet + for some reason want to ignore guide button presses on the un-opened controllers, yet users are + pressing buttons on the unopened controllers), and will resolve itself when either button is released + and we un-correlate. We could prevent this by processing the state packets for *all* controllers, + even un-opened ones, as that would allow more precise correlation. + */ + if (RAWINPUT_XInputSlotMatches(&match_state_xinput, ctx->xinput_slot)) { + ctx->xinput_uncorrelate_count = 0; + } else { + ++ctx->xinput_uncorrelate_count; + /* Only un-correlate if this is consistent over multiple Update() calls - the timing of polling/event + pumping can easily cause this to uncorrelate for a frame. 2 seemed reliable in my testing, but + let's set it to 5 to be safe. An incorrect un-correlation will simply result in lower precision + triggers for a frame. */ + if (ctx->xinput_uncorrelate_count >= 5) { +#ifdef DEBUG_RAWINPUT + SDL_Log("UN-Correlated joystick %d to XInput device #%d", joystick->instance_id, ctx->xinput_slot); +#endif + RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot); + ctx->xinput_correlated = false; + ctx->xinput_correlation_count = 0; + // Force release of Guide button, it can't possibly be down on this device now. + /* It gets left down if we were actually correlated incorrectly and it was released on the XInput + device but we didn't get a state packet. */ + if (ctx->guide_hack) { + SDL_SendJoystickButton(0, joystick, (Uint8)guide_button, false); + } + } + } + } + if (!ctx->xinput_correlated) { + Uint8 new_correlation_count = 0; + if (RAWINPUT_MissingXInputSlot()) { + Uint8 correlation_id = 0; + Uint8 slot_idx = 0; + if (RAWINPUT_GuessXInputSlot(&match_state_xinput, &correlation_id, &slot_idx)) { + // we match exactly one XInput device + /* Probably can do without xinput_correlation_count, just check and clear xinput_slot to ANY, unless + we need even more frames to be sure */ + if (ctx->xinput_correlation_count && ctx->xinput_slot == slot_idx) { + // was correlated previously, and still the same device + if (ctx->xinput_correlation_id + 1 == correlation_id) { + // no one else was correlated in the meantime + new_correlation_count = ctx->xinput_correlation_count + 1; + if (new_correlation_count == 2) { + // correlation stayed steady and uncontested across multiple frames, guaranteed match + ctx->xinput_correlated = true; +#ifdef DEBUG_RAWINPUT + SDL_Log("Correlated joystick %d to XInput device #%d", joystick->instance_id, slot_idx); +#endif + correlated = true; + RAWINPUT_MarkXInputSlotUsed(ctx->xinput_slot); + // If the generalized Guide button was using us, it doesn't need to anymore + if (guide_button_candidate.joystick == joystick) { + guide_button_candidate.joystick = NULL; + } + if (guide_button_candidate.last_joystick == joystick) { + guide_button_candidate.last_joystick = NULL; + } + } + } else { + // someone else also possibly correlated to this device, start over + new_correlation_count = 1; + } + } else { + // new possible correlation + new_correlation_count = 1; + ctx->xinput_slot = slot_idx; + } + ctx->xinput_correlation_id = correlation_id; + } else { + // Match multiple XInput devices, or none (possibly due to no buttons pressed) + } + } + ctx->xinput_correlation_count = new_correlation_count; + } else { + correlated = true; + } + } +#endif // SDL_JOYSTICK_RAWINPUT_XINPUT + + // Poll for trigger data once (not per-state-packet) +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + // Prefer XInput over WindowsGamingInput, it continues to provide data in the background + if (!has_trigger_data && ctx->xinput_enabled && ctx->xinput_correlated) { + RAWINPUT_UpdateXInput(); + if (xinput_state[ctx->xinput_slot].connected) { + XINPUT_BATTERY_INFORMATION_EX *battery_info = &xinput_state[ctx->xinput_slot].battery; + Uint64 timestamp; + + if (ctx->guide_hack || ctx->trigger_hack) { + timestamp = SDL_GetTicksNS(); + } else { + // timestamp won't be used + timestamp = 0; + } + + if (ctx->guide_hack) { + bool down = ((xinput_state[ctx->xinput_slot].state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE) != 0); + SDL_SendJoystickButton(timestamp, joystick, (Uint8)guide_button, down); + } + if (ctx->trigger_hack) { + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, ((int)xinput_state[ctx->xinput_slot].state.Gamepad.bLeftTrigger * 257) - 32768); + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, ((int)xinput_state[ctx->xinput_slot].state.Gamepad.bRightTrigger * 257) - 32768); + } + has_trigger_data = true; + + SDL_PowerState state; + int percent; + switch (battery_info->BatteryType) { + case BATTERY_TYPE_WIRED: + state = SDL_POWERSTATE_CHARGING; + break; + case BATTERY_TYPE_UNKNOWN: + case BATTERY_TYPE_DISCONNECTED: + state = SDL_POWERSTATE_UNKNOWN; + break; + default: + state = SDL_POWERSTATE_ON_BATTERY; + break; + } + switch (battery_info->BatteryLevel) { + case BATTERY_LEVEL_EMPTY: + percent = 10; + break; + case BATTERY_LEVEL_LOW: + percent = 40; + break; + case BATTERY_LEVEL_MEDIUM: + percent = 70; + break; + default: + case BATTERY_LEVEL_FULL: + percent = 100; + break; + } + SDL_SendJoystickPowerInfo(joystick, state, percent); + } + } +#endif // SDL_JOYSTICK_RAWINPUT_XINPUT + +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + if (!has_trigger_data && ctx->wgi_correlated) { + RAWINPUT_UpdateWindowsGamingInput(); // May detect disconnect / cause uncorrelation + if (ctx->wgi_correlated) { // Still connected + struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading *state = &ctx->wgi_slot->state; + Uint64 timestamp; + + if (ctx->guide_hack || ctx->trigger_hack) { + timestamp = SDL_GetTicksNS(); + } else { + // timestamp won't be used + timestamp = 0; + } + + if (ctx->guide_hack) { + bool down = ((state->Buttons & GamepadButtons_GUIDE) != 0); + SDL_SendJoystickButton(timestamp, joystick, (Uint8)guide_button, down); + } + if (ctx->trigger_hack) { + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, (Sint16)(((int)(state->LeftTrigger * SDL_MAX_UINT16)) - 32768)); + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, (Sint16)(((int)(state->RightTrigger * SDL_MAX_UINT16)) - 32768)); + } + has_trigger_data = true; + } + } +#endif // SDL_JOYSTICK_RAWINPUT_WGI + + if (!correlated) { + if (!guide_button_candidate.joystick || + (ctx->last_state_packet && (!guide_button_candidate.last_state_packet || + ctx->last_state_packet >= guide_button_candidate.last_state_packet))) { + guide_button_candidate.joystick = joystick; + guide_button_candidate.last_state_packet = ctx->last_state_packet; + } + } +#endif // SDL_JOYSTICK_RAWINPUT_MATCHING +} + +static void RAWINPUT_JoystickUpdate(SDL_Joystick *joystick) +{ + RAWINPUT_UpdateOtherAPIs(joystick); +} + +static void RAWINPUT_JoystickClose(SDL_Joystick *joystick) +{ + RAWINPUT_DeviceContext *ctx = joystick->hwdata; + +#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING + if (guide_button_candidate.joystick == joystick) { + guide_button_candidate.joystick = NULL; + } + if (guide_button_candidate.last_joystick == joystick) { + guide_button_candidate.last_joystick = NULL; + } +#endif + + if (ctx) { + SDL_RAWINPUT_Device *device; + +#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT + xinput_device_change = true; + if (ctx->xinput_enabled) { + if (ctx->xinput_correlated) { + RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot); + } + WIN_UnloadXInputDLL(); + } +#endif +#ifdef SDL_JOYSTICK_RAWINPUT_WGI + RAWINPUT_QuitWindowsGamingInput(ctx); +#endif + + device = ctx->device; + if (device) { + SDL_assert(device->joystick == joystick); + device->joystick = NULL; + RAWINPUT_ReleaseDevice(device); + } + + SDL_free(ctx->data); + SDL_free(ctx->button_indices); + SDL_free(ctx->axis_indices); + SDL_free(ctx->hat_indices); + SDL_free(ctx); + joystick->hwdata = NULL; + } +} + +bool RAWINPUT_RegisterNotifications(HWND hWnd) +{ + int i; + RAWINPUTDEVICE rid[SDL_arraysize(subscribed_devices)]; + + if (!SDL_RAWINPUT_inited) { + return true; + } + + for (i = 0; i < SDL_arraysize(subscribed_devices); i++) { + rid[i].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP; + rid[i].usUsage = subscribed_devices[i]; + rid[i].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; // Receive messages when in background, including device add/remove + rid[i].hwndTarget = hWnd; + } + + if (!RegisterRawInputDevices(rid, SDL_arraysize(rid), sizeof(RAWINPUTDEVICE))) { + return SDL_SetError("Couldn't register for raw input events"); + } + return true; +} + +bool RAWINPUT_UnregisterNotifications(void) +{ + int i; + RAWINPUTDEVICE rid[SDL_arraysize(subscribed_devices)]; + + if (!SDL_RAWINPUT_inited) { + return true; + } + + for (i = 0; i < SDL_arraysize(subscribed_devices); i++) { + rid[i].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP; + rid[i].usUsage = subscribed_devices[i]; + rid[i].dwFlags = RIDEV_REMOVE; + rid[i].hwndTarget = NULL; + } + + if (!RegisterRawInputDevices(rid, SDL_arraysize(rid), sizeof(RAWINPUTDEVICE))) { + return SDL_SetError("Couldn't unregister for raw input events"); + } + return true; +} + +LRESULT CALLBACK +RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + LRESULT result = -1; + + if (SDL_RAWINPUT_inited) { + SDL_LockJoysticks(); + + switch (msg) { + case WM_INPUT_DEVICE_CHANGE: + { + HANDLE hDevice = (HANDLE)lParam; + switch (wParam) { + case GIDC_ARRIVAL: + RAWINPUT_AddDevice(hDevice); + break; + case GIDC_REMOVAL: + { + SDL_RAWINPUT_Device *device; + device = RAWINPUT_DeviceFromHandle(hDevice); + if (device) { + RAWINPUT_DelDevice(device, true); + } + break; + } + default: + break; + } + } + result = 0; + break; + + case WM_INPUT: + { + Uint8 data[sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + USB_PACKET_LENGTH]; + UINT buffer_size = SDL_arraysize(data); + + if ((int)GetRawInputData((HRAWINPUT)lParam, RID_INPUT, data, &buffer_size, sizeof(RAWINPUTHEADER)) > 0) { + PRAWINPUT raw_input = (PRAWINPUT)data; + SDL_RAWINPUT_Device *device = RAWINPUT_DeviceFromHandle(raw_input->header.hDevice); + if (device) { + SDL_Joystick *joystick = device->joystick; + if (joystick) { + RAWINPUT_HandleStatePacket(joystick, raw_input->data.hid.bRawData, raw_input->data.hid.dwSizeHid); + } + } + } + } + result = 0; + break; + } + + SDL_UnlockJoysticks(); + } + + if (result >= 0) { + return result; + } + return CallWindowProc(DefWindowProc, hWnd, msg, wParam, lParam); +} + +static void RAWINPUT_JoystickQuit(void) +{ + if (!SDL_RAWINPUT_inited) { + return; + } + + RAWINPUT_RemoveDevices(); + + WIN_UnloadHIDDLL(); + + SDL_RAWINPUT_inited = false; +} + +static bool RAWINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver = { + RAWINPUT_JoystickInit, + RAWINPUT_JoystickGetCount, + RAWINPUT_JoystickDetect, + RAWINPUT_JoystickIsDevicePresent, + RAWINPUT_JoystickGetDeviceName, + RAWINPUT_JoystickGetDevicePath, + RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot, + RAWINPUT_JoystickGetDevicePlayerIndex, + RAWINPUT_JoystickSetDevicePlayerIndex, + RAWINPUT_JoystickGetDeviceGUID, + RAWINPUT_JoystickGetDeviceInstanceID, + RAWINPUT_JoystickOpen, + RAWINPUT_JoystickRumble, + RAWINPUT_JoystickRumbleTriggers, + RAWINPUT_JoystickSetLED, + RAWINPUT_JoystickSendEffect, + RAWINPUT_JoystickSetSensorsEnabled, + RAWINPUT_JoystickUpdate, + RAWINPUT_JoystickClose, + RAWINPUT_JoystickQuit, + RAWINPUT_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_RAWINPUT diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h new file mode 100644 index 0000000..b67544b --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h @@ -0,0 +1,32 @@ +/* + 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" +#include "../../core/windows/SDL_windows.h" + +// Return true if the RawInput driver is enabled +extern bool RAWINPUT_IsEnabled(void); + +// Registers for input events +extern int RAWINPUT_RegisterNotifications(HWND hWnd); +extern int RAWINPUT_UnregisterNotifications(void); + +// Returns 0 if message was handled +extern LRESULT CALLBACK RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c new file mode 100644 index 0000000..dbc5658 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c @@ -0,0 +1,1039 @@ +/* + 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_JOYSTICK_WGI + +#include "../SDL_sysjoystick.h" +#include "../hidapi/SDL_hidapijoystick_c.h" +#include "SDL_rawinputjoystick_c.h" + +#include "../../core/windows/SDL_windows.h" +#define COBJMACROS +#include "windows.gaming.input.h" +#include +#include +#include +#include + +#ifdef ____FIReference_1_INT32_INTERFACE_DEFINED__ +// MinGW-64 uses __FIReference_1_INT32 instead of Microsoft's __FIReference_1_int +#define __FIReference_1_int __FIReference_1_INT32 +#define __FIReference_1_int_get_Value __FIReference_1_INT32_get_Value +#define __FIReference_1_int_Release __FIReference_1_INT32_Release +#endif + +struct joystick_hwdata +{ + __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller; + __x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller; + __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo *battery; + __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad; + __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; + UINT64 timestamp; +}; + +typedef struct WindowsGamingInputControllerState +{ + SDL_JoystickID instance_id; + __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller; + char *name; + SDL_GUID guid; + SDL_JoystickType type; + int steam_virtual_gamepad_slot; +} WindowsGamingInputControllerState; + +typedef HRESULT(WINAPI *CoIncrementMTAUsage_t)(CO_MTA_USAGE_COOKIE *pCookie); +typedef HRESULT(WINAPI *RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void **factory); +typedef HRESULT(WINAPI *WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING *string); +typedef HRESULT(WINAPI *WindowsDeleteString_t)(HSTRING string); +typedef PCWSTR(WINAPI *WindowsGetStringRawBuffer_t)(HSTRING string, UINT32 *length); + +static struct +{ + CoIncrementMTAUsage_t CoIncrementMTAUsage; + RoGetActivationFactory_t RoGetActivationFactory; + WindowsCreateStringReference_t WindowsCreateStringReference; + WindowsDeleteString_t WindowsDeleteString; + WindowsGetStringRawBuffer_t WindowsGetStringRawBuffer; + __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics *controller_statics; + __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics *arcade_stick_statics; + __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2 *arcade_stick_statics2; + __x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics *flight_stick_statics; + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics *gamepad_statics; + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2 *gamepad_statics2; + __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics *racing_wheel_statics; + __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2 *racing_wheel_statics2; + EventRegistrationToken controller_added_token; + EventRegistrationToken controller_removed_token; + int controller_count; + WindowsGamingInputControllerState *controllers; +} wgi; + +// WinRT headers in official Windows SDK contain only declarations, and we have to define these GUIDs ourselves. +// https://stackoverflow.com/a/55605485/1795050 +DEFINE_GUID(IID___FIEventHandler_1_Windows__CGaming__CInput__CRawGameController, 0x00621c22, 0x42e8, 0x529f, 0x92, 0x70, 0x83, 0x6b, 0x32, 0x93, 0x1d, 0x72); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics, 0x5c37b8c8, 0x37b1, 0x4ad8, 0x94, 0x58, 0x20, 0x0f, 0x1a, 0x30, 0x01, 0x8e); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2, 0x52b5d744, 0xbb86, 0x445a, 0xb5, 0x9c, 0x59, 0x6f, 0x0e, 0x2a, 0x49, 0xdf); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics, 0x5514924a, 0xfecc, 0x435e, 0x83, 0xdc, 0x5c, 0xec, 0x8a, 0x18, 0xa5, 0x20); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGameController, 0x1baf6522, 0x5f64, 0x42c5, 0x82, 0x67, 0xb9, 0xfe, 0x22, 0x15, 0xbf, 0xbd); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo, 0xdcecc681, 0x3963, 0x4da6, 0x95, 0x5d, 0x55, 0x3f, 0x3b, 0x6f, 0x61, 0x61); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics, 0x8bbce529, 0xd49c, 0x39e9, 0x95, 0x60, 0xe4, 0x7d, 0xde, 0x96, 0xb7, 0xc8); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2, 0x42676dc5, 0x0856, 0x47c4, 0x92, 0x13, 0xb3, 0x95, 0x50, 0x4c, 0x3a, 0x3c); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics, 0x3ac12cd5, 0x581b, 0x4936, 0x9f, 0x94, 0x69, 0xf1, 0xe6, 0x51, 0x4c, 0x7d); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2, 0xe666bcaa, 0xedfd, 0x4323, 0xa9, 0xf6, 0x3c, 0x38, 0x40, 0x48, 0xd1, 0xed); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, 0x7cad6d91, 0xa7e1, 0x4f71, 0x9a, 0x78, 0x33, 0xe9, 0xc5, 0xdf, 0xea, 0x62); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, 0x43c0c035, 0xbb73, 0x4756, 0xa7, 0x87, 0x3e, 0xd6, 0xbe, 0xa6, 0x17, 0xbd); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics, 0xeb8d0792, 0xe95a, 0x4b19, 0xaf, 0xc7, 0x0a, 0x59, 0xf8, 0xbf, 0x75, 0x9e); + +extern bool SDL_XINPUT_Enabled(void); + + +static bool SDL_IsXInputDevice(Uint16 vendor, Uint16 product, const char *name) +{ +#if defined(SDL_JOYSTICK_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT) + PRAWINPUTDEVICELIST raw_devices = NULL; + UINT i, raw_device_count = 0; + LONG vidpid = MAKELONG(vendor, product); + + // XInput and RawInput backends will pick up XInput-compatible devices + if (!SDL_XINPUT_Enabled() +#ifdef SDL_JOYSTICK_RAWINPUT + && !RAWINPUT_IsEnabled() +#endif + ) { + return false; + } + + // Sometimes we'll get a Windows.Gaming.Input callback before the raw input device is even in the list, + // so try to do some checks up front to catch these cases. + if (SDL_IsJoystickXboxOne(vendor, product) || + (name && SDL_strncmp(name, "Xbox ", 5) == 0)) { + return true; + } + + // Go through RAWINPUT (WinXP and later) to find HID devices. + if ((GetRawInputDeviceList(NULL, &raw_device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!raw_device_count)) { + return false; // oh well. + } + + raw_devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * raw_device_count); + if (!raw_devices) { + return false; + } + + raw_device_count = GetRawInputDeviceList(raw_devices, &raw_device_count, sizeof(RAWINPUTDEVICELIST)); + if (raw_device_count == (UINT)-1) { + SDL_free(raw_devices); + raw_devices = NULL; + return false; // oh well. + } + + for (i = 0; i < raw_device_count; i++) { + RID_DEVICE_INFO rdi; + char devName[MAX_PATH] = { 0 }; + UINT rdiSize = sizeof(rdi); + UINT nameSize = SDL_arraysize(devName); + DEVINST devNode; + char devVidPidString[32]; + int j; + + rdi.cbSize = sizeof(rdi); + + if ((raw_devices[i].dwType != RIM_TYPEHID) || + (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == ((UINT)-1)) || + (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) == ((UINT)-1)) || + (SDL_strstr(devName, "IG_") == NULL)) { + // Skip non-XInput devices + continue; + } + + // First check for a simple VID/PID match. This will work for Xbox 360 controllers. + if (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == vidpid) { + SDL_free(raw_devices); + return true; + } + + /* For Xbox One controllers, Microsoft doesn't propagate the VID/PID down to the HID stack. + * We'll have to walk the device tree upwards searching for a match for our VID/PID. */ + + // Make sure the device interface string is something we know how to parse + // Example: \\?\HID#VID_045E&PID_02FF&IG_00#9&2c203035&2&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} + if ((SDL_strstr(devName, "\\\\?\\") != devName) || (SDL_strstr(devName, "#{") == NULL)) { + continue; + } + + // Unescape the backslashes in the string and terminate before the GUID portion + for (j = 0; devName[j] != '\0'; j++) { + if (devName[j] == '#') { + if (devName[j + 1] == '{') { + devName[j] = '\0'; + break; + } else { + devName[j] = '\\'; + } + } + } + + /* We'll be left with a string like this: \\?\HID\VID_045E&PID_02FF&IG_00\9&2c203035&2&0000 + * Simply skip the \\?\ prefix and we'll have a properly formed device instance ID */ + if (CM_Locate_DevNodeA(&devNode, &devName[4], CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) { + continue; + } + + (void)SDL_snprintf(devVidPidString, sizeof(devVidPidString), "VID_%04X&PID_%04X", vendor, product); + + while (CM_Get_Parent(&devNode, devNode, 0) == CR_SUCCESS) { + char deviceId[MAX_DEVICE_ID_LEN]; + + if ((CM_Get_Device_IDA(devNode, deviceId, SDL_arraysize(deviceId), 0) == CR_SUCCESS) && + (SDL_strstr(deviceId, devVidPidString) != NULL)) { + // The VID/PID matched a parent device + SDL_free(raw_devices); + return true; + } + } + } + + SDL_free(raw_devices); +#endif // SDL_JOYSTICK_XINPUT || SDL_JOYSTICK_RAWINPUT + + return false; +} + +static void WGI_LoadRawGameControllerStatics(void) +{ + HRESULT hr; + HSTRING_HEADER class_name_header; + HSTRING class_name; + + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_RawGameController, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_RawGameController), &class_name_header, &class_name); + if (SUCCEEDED(hr)) { + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics, (void **)&wgi.controller_statics); + if (!SUCCEEDED(hr)) { + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IRawGameControllerStatics", hr); + } + } +} + +static void WGI_LoadOtherControllerStatics(void) +{ + HRESULT hr; + HSTRING_HEADER class_name_header; + HSTRING class_name; + + if (!wgi.arcade_stick_statics) { + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_ArcadeStick, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_ArcadeStick), &class_name_header, &class_name); + if (SUCCEEDED(hr)) { + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics, (void **)&wgi.arcade_stick_statics); + if (SUCCEEDED(hr)) { + __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_QueryInterface(wgi.arcade_stick_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2, (void **)&wgi.arcade_stick_statics2); + } else { + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IArcadeStickStatics", hr); + } + } + } + + if (!wgi.flight_stick_statics) { + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_FlightStick, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_FlightStick), &class_name_header, &class_name); + if (SUCCEEDED(hr)) { + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics, (void **)&wgi.flight_stick_statics); + if (!SUCCEEDED(hr)) { + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IFlightStickStatics", hr); + } + } + } + + if (!wgi.gamepad_statics) { + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_Gamepad, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_Gamepad), &class_name_header, &class_name); + if (SUCCEEDED(hr)) { + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics, (void **)&wgi.gamepad_statics); + if (SUCCEEDED(hr)) { + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_QueryInterface(wgi.gamepad_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2, (void **)&wgi.gamepad_statics2); + } else { + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IGamepadStatics", hr); + } + } + } + + if (!wgi.racing_wheel_statics) { + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_RacingWheel, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_RacingWheel), &class_name_header, &class_name); + if (SUCCEEDED(hr)) { + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics, (void **)&wgi.racing_wheel_statics); + if (SUCCEEDED(hr)) { + __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics_QueryInterface(wgi.racing_wheel_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2, (void **)&wgi.racing_wheel_statics2); + } else { + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IRacingWheelStatics", hr); + } + } + } +} + +static SDL_JoystickType GetGameControllerType(__x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller) +{ + __x_ABI_CWindows_CGaming_CInput_CIArcadeStick *arcade_stick = NULL; + __x_ABI_CWindows_CGaming_CInput_CIFlightStick *flight_stick = NULL; + __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad = NULL; + __x_ABI_CWindows_CGaming_CInput_CIRacingWheel *racing_wheel = NULL; + + /* Wait to initialize these interfaces until we need them. + * Initializing the gamepad interface will switch Bluetooth PS4 controllers into enhanced mode, breaking DirectInput + */ + WGI_LoadOtherControllerStatics(); + + if (wgi.gamepad_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_FromGameController(wgi.gamepad_statics2, game_controller, &gamepad)) && gamepad) { + __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad); + return SDL_JOYSTICK_TYPE_GAMEPAD; + } + + if (wgi.arcade_stick_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2_FromGameController(wgi.arcade_stick_statics2, game_controller, &arcade_stick)) && arcade_stick) { + __x_ABI_CWindows_CGaming_CInput_CIArcadeStick_Release(arcade_stick); + return SDL_JOYSTICK_TYPE_ARCADE_STICK; + } + + if (wgi.flight_stick_statics && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics_FromGameController(wgi.flight_stick_statics, game_controller, &flight_stick)) && flight_stick) { + __x_ABI_CWindows_CGaming_CInput_CIFlightStick_Release(flight_stick); + return SDL_JOYSTICK_TYPE_FLIGHT_STICK; + } + + if (wgi.racing_wheel_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2_FromGameController(wgi.racing_wheel_statics2, game_controller, &racing_wheel)) && racing_wheel) { + __x_ABI_CWindows_CGaming_CInput_CIRacingWheel_Release(racing_wheel); + return SDL_JOYSTICK_TYPE_WHEEL; + } + + return SDL_JOYSTICK_TYPE_UNKNOWN; +} + +typedef struct RawGameControllerDelegate +{ + __FIEventHandler_1_Windows__CGaming__CInput__CRawGameController iface; + SDL_AtomicInt refcount; +} RawGameControllerDelegate; + +static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_QueryInterface(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, REFIID riid, void **ppvObject) +{ + if (!ppvObject) { + return E_INVALIDARG; + } + + *ppvObject = NULL; + if (WIN_IsEqualIID(riid, &IID_IUnknown) || WIN_IsEqualIID(riid, &IID_IAgileObject) || WIN_IsEqualIID(riid, &IID___FIEventHandler_1_Windows__CGaming__CInput__CRawGameController)) { + *ppvObject = This; + __FIEventHandler_1_Windows__CGaming__CInput__CRawGameController_AddRef(This); + return S_OK; + } else if (WIN_IsEqualIID(riid, &IID_IMarshal)) { + // This seems complicated. Let's hope it doesn't happen. + return E_OUTOFMEMORY; + } else { + return E_NOINTERFACE; + } +} + +static ULONG STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_AddRef(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This) +{ + RawGameControllerDelegate *self = (RawGameControllerDelegate *)This; + return SDL_AddAtomicInt(&self->refcount, 1) + 1UL; +} + +static ULONG STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_Release(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This) +{ + RawGameControllerDelegate *self = (RawGameControllerDelegate *)This; + int rc = SDL_AddAtomicInt(&self->refcount, -1) - 1; + // Should never free the static delegate objects + SDL_assert(rc > 0); + return rc; +} + +static int GetSteamVirtualGamepadSlot(__x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller, Uint16 vendor_id, Uint16 product_id) +{ + int slot = -1; + + if (vendor_id == USB_VENDOR_VALVE && + product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + __x_ABI_CWindows_CGaming_CInput_CIRawGameController2 *controller2 = NULL; + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, (void **)&controller2); + if (SUCCEEDED(hr)) { + HSTRING hString; + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_NonRoamableId(controller2, &hString); + if (SUCCEEDED(hr)) { + PCWSTR string = wgi.WindowsGetStringRawBuffer(hString, NULL); + if (string) { + char *id = WIN_StringToUTF8W(string); + if (id) { + (void)SDL_sscanf(id, "{wgi/nrid/:steam-%*X&%*X&%*X#%d#%*u}", &slot); + SDL_free(id); + } + } + wgi.WindowsDeleteString(hString); + } + __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_Release(controller2); + } + } + return slot; +} + +static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeAdded(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIRawGameController *e) +{ + HRESULT hr; + __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller = NULL; + + SDL_LockJoysticks(); + + // We can get delayed calls to InvokeAdded() after WGI_JoystickQuit() + if (SDL_JoysticksQuitting() || !SDL_JoysticksInitialized()) { + SDL_UnlockJoysticks(); + return S_OK; + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(e, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, (void **)&controller); + if (SUCCEEDED(hr)) { + char *name = NULL; + Uint16 bus = SDL_HARDWARE_BUS_USB; + Uint16 vendor = 0; + Uint16 product = 0; + Uint16 version = 0; + SDL_JoystickType type = SDL_JOYSTICK_TYPE_UNKNOWN; + __x_ABI_CWindows_CGaming_CInput_CIRawGameController2 *controller2 = NULL; + __x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller = NULL; + bool ignore_joystick = false; + + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_HardwareVendorId(controller, &vendor); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_HardwareProductId(controller, &product); + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameController, (void **)&game_controller); + if (SUCCEEDED(hr)) { + boolean wireless = 0; + hr = __x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(game_controller, &wireless); + if (SUCCEEDED(hr) && wireless) { + bus = SDL_HARDWARE_BUS_BLUETOOTH; + + // Fixup for Wireless Xbox 360 Controller + if (product == 0) { + vendor = USB_VENDOR_MICROSOFT; + product = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; + } + } + + __x_ABI_CWindows_CGaming_CInput_CIGameController_Release(game_controller); + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, (void **)&controller2); + if (SUCCEEDED(hr)) { + HSTRING hString; + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_DisplayName(controller2, &hString); + if (SUCCEEDED(hr)) { + PCWSTR string = wgi.WindowsGetStringRawBuffer(hString, NULL); + if (string) { + name = WIN_StringToUTF8W(string); + } + wgi.WindowsDeleteString(hString); + } + __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_Release(controller2); + } + if (!name) { + name = SDL_strdup(""); + } + + if (!ignore_joystick && SDL_ShouldIgnoreJoystick(vendor, product, version, name)) { + ignore_joystick = true; + } + + if (!ignore_joystick && SDL_JoystickHandledByAnotherDriver(&SDL_WGI_JoystickDriver, vendor, product, version, name)) { + ignore_joystick = true; + } + + if (!ignore_joystick && SDL_IsXInputDevice(vendor, product, name)) { + // This hasn't been detected by the RAWINPUT driver yet, but it will be picked up later. + ignore_joystick = true; + } + + if (!ignore_joystick) { + // New device, add it + WindowsGamingInputControllerState *controllers = SDL_realloc(wgi.controllers, sizeof(wgi.controllers[0]) * (wgi.controller_count + 1)); + if (controllers) { + WindowsGamingInputControllerState *state = &controllers[wgi.controller_count]; + SDL_JoystickID joystickID = SDL_GetNextObjectID(); + + if (game_controller) { + type = GetGameControllerType(game_controller); + } + + SDL_zerop(state); + state->instance_id = joystickID; + state->controller = controller; + state->name = name; + state->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, NULL, name, 'w', (Uint8)type); + state->type = type; + state->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(controller, vendor, product); + + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_AddRef(controller); + + ++wgi.controller_count; + wgi.controllers = controllers; + + SDL_PrivateJoystickAdded(joystickID); + } else { + SDL_free(name); + } + } else { + SDL_free(name); + } + + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(controller); + } + + SDL_UnlockJoysticks(); + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeRemoved(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIRawGameController *e) +{ + HRESULT hr; + __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller = NULL; + + SDL_LockJoysticks(); + + // Can we get delayed calls to InvokeRemoved() after WGI_JoystickQuit()? + if (!SDL_JoysticksInitialized()) { + SDL_UnlockJoysticks(); + return S_OK; + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(e, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, (void **)&controller); + if (SUCCEEDED(hr)) { + int i; + + for (i = 0; i < wgi.controller_count; i++) { + if (wgi.controllers[i].controller == controller) { + WindowsGamingInputControllerState *state = &wgi.controllers[i]; + SDL_JoystickID joystickID = state->instance_id; + + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(state->controller); + + SDL_free(state->name); + + --wgi.controller_count; + if (i < wgi.controller_count) { + SDL_memmove(&wgi.controllers[i], &wgi.controllers[i + 1], (wgi.controller_count - i) * sizeof(wgi.controllers[i])); + } + + SDL_PrivateJoystickRemoved(joystickID); + break; + } + } + + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(controller); + } + + SDL_UnlockJoysticks(); + + return S_OK; +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4028) // formal parameter 3 different from declaration, when using older buggy WGI headers +#pragma warning(disable : 4113) // formal parameter 3 different from declaration (a more specific warning added in VS 2022), when using older buggy WGI headers +#endif + +static __FIEventHandler_1_Windows__CGaming__CInput__CRawGameControllerVtbl controller_added_vtbl = { + IEventHandler_CRawGameControllerVtbl_QueryInterface, + IEventHandler_CRawGameControllerVtbl_AddRef, + IEventHandler_CRawGameControllerVtbl_Release, + IEventHandler_CRawGameControllerVtbl_InvokeAdded +}; +static RawGameControllerDelegate controller_added = { + { &controller_added_vtbl }, + { 1 } +}; + +static __FIEventHandler_1_Windows__CGaming__CInput__CRawGameControllerVtbl controller_removed_vtbl = { + IEventHandler_CRawGameControllerVtbl_QueryInterface, + IEventHandler_CRawGameControllerVtbl_AddRef, + IEventHandler_CRawGameControllerVtbl_Release, + IEventHandler_CRawGameControllerVtbl_InvokeRemoved +}; +static RawGameControllerDelegate controller_removed = { + { &controller_removed_vtbl }, + { 1 } +}; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +static bool WGI_JoystickInit(void) +{ + HRESULT hr; + + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, true)) { + return true; + } + + if (FAILED(WIN_RoInitialize())) { + return SDL_SetError("RoInitialize() failed"); + } + +#define RESOLVE(x) wgi.x = (x##_t)WIN_LoadComBaseFunction(#x); if (!wgi.x) return WIN_SetError("GetProcAddress failed for " #x); + RESOLVE(CoIncrementMTAUsage); + RESOLVE(RoGetActivationFactory); + RESOLVE(WindowsCreateStringReference); + RESOLVE(WindowsDeleteString); + RESOLVE(WindowsGetStringRawBuffer); +#undef RESOLVE + + { + /* There seems to be a bug in Windows where a dependency of WGI can be unloaded from memory prior to WGI itself. + * This results in Windows_Gaming_Input!GameController::~GameController() invoking an unloaded DLL and crashing. + * As a workaround, we will keep a reference to the MTA to prevent COM from unloading DLLs later. + * See https://github.com/libsdl-org/SDL/issues/5552 for more details. + */ + static CO_MTA_USAGE_COOKIE cookie = NULL; + if (!cookie) { + hr = wgi.CoIncrementMTAUsage(&cookie); + if (FAILED(hr)) { + return WIN_SetErrorFromHRESULT("CoIncrementMTAUsage() failed", hr); + } + } + } + + WGI_LoadRawGameControllerStatics(); + + if (wgi.controller_statics) { + __FIVectorView_1_Windows__CGaming__CInput__CRawGameController *controllers; + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_add_RawGameControllerAdded(wgi.controller_statics, &controller_added.iface, &wgi.controller_added_token); + if (!SUCCEEDED(hr)) { + WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IRawGameControllerStatics.add_RawGameControllerAdded failed", hr); + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_add_RawGameControllerRemoved(wgi.controller_statics, &controller_removed.iface, &wgi.controller_removed_token); + if (!SUCCEEDED(hr)) { + WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IRawGameControllerStatics.add_RawGameControllerRemoved failed", hr); + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_get_RawGameControllers(wgi.controller_statics, &controllers); + if (SUCCEEDED(hr)) { + unsigned i, count = 0; + + hr = __FIVectorView_1_Windows__CGaming__CInput__CRawGameController_get_Size(controllers, &count); + if (SUCCEEDED(hr)) { + for (i = 0; i < count; ++i) { + __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller = NULL; + + hr = __FIVectorView_1_Windows__CGaming__CInput__CRawGameController_GetAt(controllers, i, &controller); + if (SUCCEEDED(hr) && controller) { + IEventHandler_CRawGameControllerVtbl_InvokeAdded(&controller_added.iface, NULL, controller); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(controller); + } + } + } + + __FIVectorView_1_Windows__CGaming__CInput__CRawGameController_Release(controllers); + } + } + + return true; +} + +static int WGI_JoystickGetCount(void) +{ + return wgi.controller_count; +} + +static void WGI_JoystickDetect(void) +{ +} + +static bool WGI_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers + return false; +} + +static const char *WGI_JoystickGetDeviceName(int device_index) +{ + return wgi.controllers[device_index].name; +} + +static const char *WGI_JoystickGetDevicePath(int device_index) +{ + return NULL; +} + +static int WGI_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return wgi.controllers[device_index].steam_virtual_gamepad_slot; +} + +static int WGI_JoystickGetDevicePlayerIndex(int device_index) +{ + return false; +} + +static void WGI_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static SDL_GUID WGI_JoystickGetDeviceGUID(int device_index) +{ + return wgi.controllers[device_index].guid; +} + +static SDL_JoystickID WGI_JoystickGetDeviceInstanceID(int device_index) +{ + return wgi.controllers[device_index].instance_id; +} + +static bool WGI_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + WindowsGamingInputControllerState *state = &wgi.controllers[device_index]; + struct joystick_hwdata *hwdata; + boolean wireless = false; + + hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata)); + if (!hwdata) { + return false; + } + joystick->hwdata = hwdata; + + hwdata->controller = state->controller; + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_AddRef(hwdata->controller); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(hwdata->controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameController, (void **)&hwdata->game_controller); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(hwdata->controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo, (void **)&hwdata->battery); + + if (wgi.gamepad_statics2) { + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_FromGameController(wgi.gamepad_statics2, hwdata->game_controller, &hwdata->gamepad); + } + + if (hwdata->game_controller) { + __x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(hwdata->game_controller, &wireless); + } + + // Initialize the joystick capabilities + if (wireless) { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + } else { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; + } + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_ButtonCount(hwdata->controller, &joystick->nbuttons); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_AxisCount(hwdata->controller, &joystick->naxes); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_SwitchCount(hwdata->controller, &joystick->nhats); + + if (hwdata->gamepad) { + // FIXME: Can WGI even tell us if trigger rumble is supported? + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); + } + return true; +} + +static bool WGI_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + + if (hwdata->gamepad) { + HRESULT hr; + + // Note: reusing partially filled vibration data struct + hwdata->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; + hwdata->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(hwdata->gamepad, hwdata->vibration); + if (SUCCEEDED(hr)) { + return true; + } else { + return WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IGamepad.put_Vibration failed", hr); + } + } else { + return SDL_Unsupported(); + } +} + +static bool WGI_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + + if (hwdata->gamepad) { + HRESULT hr; + + // Note: reusing partially filled vibration data struct + hwdata->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; + hwdata->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(hwdata->gamepad, hwdata->vibration); + if (SUCCEEDED(hr)) { + return true; + } else { + return WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IGamepad.put_Vibration failed", hr); + } + } else { + return SDL_Unsupported(); + } +} + +static bool WGI_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool WGI_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool WGI_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static Uint8 ConvertHatValue(__x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition value) +{ + switch (value) { + case GameControllerSwitchPosition_Up: + return SDL_HAT_UP; + case GameControllerSwitchPosition_UpRight: + return SDL_HAT_RIGHTUP; + case GameControllerSwitchPosition_Right: + return SDL_HAT_RIGHT; + case GameControllerSwitchPosition_DownRight: + return SDL_HAT_RIGHTDOWN; + case GameControllerSwitchPosition_Down: + return SDL_HAT_DOWN; + case GameControllerSwitchPosition_DownLeft: + return SDL_HAT_LEFTDOWN; + case GameControllerSwitchPosition_Left: + return SDL_HAT_LEFT; + case GameControllerSwitchPosition_UpLeft: + return SDL_HAT_LEFTUP; + default: + return SDL_HAT_CENTERED; + } +} + +static void WGI_JoystickUpdate(SDL_Joystick *joystick) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + HRESULT hr; + UINT32 nbuttons = SDL_min(joystick->nbuttons, SDL_MAX_UINT8); + boolean *buttons = NULL; + UINT32 nhats = SDL_min(joystick->nhats, SDL_MAX_UINT8); + __x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition *hats = NULL; + UINT32 naxes = SDL_min(joystick->naxes, SDL_MAX_UINT8); + DOUBLE *axes = NULL; + UINT64 timestamp; + + if (nbuttons > 0) { + buttons = SDL_stack_alloc(boolean, nbuttons); + } + if (nhats > 0) { + hats = SDL_stack_alloc(__x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition, nhats); + } + if (naxes > 0) { + axes = SDL_stack_alloc(DOUBLE, naxes); + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_GetCurrentReading(hwdata->controller, nbuttons, buttons, nhats, hats, naxes, axes, ×tamp); + if (SUCCEEDED(hr) && (!timestamp || timestamp != hwdata->timestamp)) { + UINT32 i; + bool all_zero = false; + + hwdata->timestamp = timestamp; + + // The axes are all zero when the application loses focus + if (naxes > 0) { + all_zero = true; + for (i = 0; i < naxes; ++i) { + if (axes[i] != 0.0f) { + all_zero = false; + break; + } + } + } + if (all_zero) { + SDL_PrivateJoystickForceRecentering(joystick); + } else { + // FIXME: What units are the timestamp we get from GetCurrentReading()? + timestamp = SDL_GetTicksNS(); + for (i = 0; i < nbuttons; ++i) { + SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, buttons[i]); + } + for (i = 0; i < nhats; ++i) { + SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, ConvertHatValue(hats[i])); + } + for (i = 0; i < naxes; ++i) { + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, (Sint16)((int)(axes[i] * 65535) - 32768)); + } + } + } + + SDL_stack_free(buttons); + SDL_stack_free(hats); + SDL_stack_free(axes); + + if (hwdata->battery) { + __x_ABI_CWindows_CDevices_CPower_CIBatteryReport *report = NULL; + + hr = __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo_TryGetBatteryReport(hwdata->battery, &report); + if (SUCCEEDED(hr) && report) { + SDL_PowerState state = SDL_POWERSTATE_UNKNOWN; + int percent = 0; + __x_ABI_CWindows_CSystem_CPower_CBatteryStatus status; + int full_capacity = 0, curr_capacity = 0; + __FIReference_1_int *full_capacityP, *curr_capacityP; + + hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_Status(report, &status); + if (SUCCEEDED(hr)) { + switch (status) { + case BatteryStatus_NotPresent: + state = SDL_POWERSTATE_NO_BATTERY; + break; + case BatteryStatus_Discharging: + state = SDL_POWERSTATE_ON_BATTERY; + break; + case BatteryStatus_Idle: + state = SDL_POWERSTATE_CHARGED; + break; + case BatteryStatus_Charging: + state = SDL_POWERSTATE_CHARGING; + break; + default: + state = SDL_POWERSTATE_UNKNOWN; + break; + } + } + + hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_FullChargeCapacityInMilliwattHours(report, &full_capacityP); + if (SUCCEEDED(hr)) { + __FIReference_1_int_get_Value(full_capacityP, &full_capacity); + __FIReference_1_int_Release(full_capacityP); + } + + hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_RemainingCapacityInMilliwattHours(report, &curr_capacityP); + if (SUCCEEDED(hr)) { + __FIReference_1_int_get_Value(curr_capacityP, &curr_capacity); + __FIReference_1_int_Release(curr_capacityP); + } + + if (full_capacity > 0) { + percent = (int)SDL_roundf(((float)curr_capacity / full_capacity) * 100.0f); + } + + SDL_SendJoystickPowerInfo(joystick, state, percent); + + __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_Release(report); + } + } +} + +static void WGI_JoystickClose(SDL_Joystick *joystick) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + + if (hwdata) { + if (hwdata->controller) { + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(hwdata->controller); + } + if (hwdata->game_controller) { + __x_ABI_CWindows_CGaming_CInput_CIGameController_Release(hwdata->game_controller); + } + if (hwdata->battery) { + __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo_Release(hwdata->battery); + } + if (hwdata->gamepad) { + __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(hwdata->gamepad); + } + SDL_free(hwdata); + } + joystick->hwdata = NULL; +} + +static void WGI_JoystickQuit(void) +{ + if (wgi.controller_statics) { + while (wgi.controller_count > 0) { + IEventHandler_CRawGameControllerVtbl_InvokeRemoved(&controller_removed.iface, NULL, wgi.controllers[wgi.controller_count - 1].controller); + } + if (wgi.controllers) { + SDL_free(wgi.controllers); + } + + if (wgi.arcade_stick_statics) { + __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_Release(wgi.arcade_stick_statics); + } + if (wgi.arcade_stick_statics2) { + __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2_Release(wgi.arcade_stick_statics2); + } + if (wgi.flight_stick_statics) { + __x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics_Release(wgi.flight_stick_statics); + } + if (wgi.gamepad_statics) { + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_Release(wgi.gamepad_statics); + } + if (wgi.gamepad_statics2) { + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_Release(wgi.gamepad_statics2); + } + if (wgi.racing_wheel_statics) { + __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics_Release(wgi.racing_wheel_statics); + } + if (wgi.racing_wheel_statics2) { + __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2_Release(wgi.racing_wheel_statics2); + } + + __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_remove_RawGameControllerAdded(wgi.controller_statics, wgi.controller_added_token); + __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_remove_RawGameControllerRemoved(wgi.controller_statics, wgi.controller_removed_token); + __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_Release(wgi.controller_statics); + } + + WIN_RoUninitialize(); + + SDL_zero(wgi); +} + +static bool WGI_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +SDL_JoystickDriver SDL_WGI_JoystickDriver = { + WGI_JoystickInit, + WGI_JoystickGetCount, + WGI_JoystickDetect, + WGI_JoystickIsDevicePresent, + WGI_JoystickGetDeviceName, + WGI_JoystickGetDevicePath, + WGI_JoystickGetDeviceSteamVirtualGamepadSlot, + WGI_JoystickGetDevicePlayerIndex, + WGI_JoystickSetDevicePlayerIndex, + WGI_JoystickGetDeviceGUID, + WGI_JoystickGetDeviceInstanceID, + WGI_JoystickOpen, + WGI_JoystickRumble, + WGI_JoystickRumbleTriggers, + WGI_JoystickSetLED, + WGI_JoystickSendEffect, + WGI_JoystickSetSensorsEnabled, + WGI_JoystickUpdate, + WGI_JoystickClose, + WGI_JoystickQuit, + WGI_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_WGI diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c new file mode 100644 index 0000000..e7fbfcb --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c @@ -0,0 +1,693 @@ +/* + 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" + +#if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT) + +/* DirectInput joystick driver; written by Glenn Maynard, based on Andrei de + * A. Formiga's WINMM driver. + * + * Hats and sliders are completely untested; the app I'm writing this for mostly + * doesn't use them and I don't own any joysticks with them. + * + * We don't bother to use event notification here. It doesn't seem to work + * with polled devices, and it's fine to call IDirectInputDevice8_GetDeviceData and + * let it return 0 events. */ + +#include "../SDL_sysjoystick.h" +#include "../../thread/SDL_systhread.h" +#include "../../core/windows/SDL_windows.h" +#include "../../core/windows/SDL_hid.h" +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +#include +#endif + +#define INITGUID // Only set here, if set twice will cause mingw32 to break. +#include "SDL_windowsjoystick_c.h" +#include "SDL_dinputjoystick_c.h" +#include "SDL_xinputjoystick_c.h" +#include "SDL_rawinputjoystick_c.h" + +#include "../../haptic/windows/SDL_dinputhaptic_c.h" // For haptic hot plugging + +#ifndef DEVICE_NOTIFY_WINDOW_HANDLE +#define DEVICE_NOTIFY_WINDOW_HANDLE 0x00000000 +#endif + +// local variables +static bool s_bJoystickThread = false; +static SDL_Condition *s_condJoystickThread = NULL; +static SDL_Mutex *s_mutexJoyStickEnum = NULL; +static SDL_Thread *s_joystickThread = NULL; +static bool s_bJoystickThreadQuit = false; +static Uint64 s_lastDeviceChange = 0; +static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2L, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } }; + +JoyStick_DeviceData *SYS_Joystick; // array to hold joystick ID values + + +static bool WindowsDeviceChanged(void) +{ + return (s_lastDeviceChange != WIN_GetLastDeviceNotification()); +} + +static void SetWindowsDeviceChanged(void) +{ + s_lastDeviceChange = 0; +} + +void WINDOWS_RAWINPUTEnabledChanged(void) +{ + SetWindowsDeviceChanged(); +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +typedef struct +{ + HRESULT coinitialized; + WNDCLASSEX wincl; + HWND messageWindow; + HDEVNOTIFY hNotify; +} SDL_DeviceNotificationData; + +#define IDT_SDL_DEVICE_CHANGE_TIMER_1 1200 +#define IDT_SDL_DEVICE_CHANGE_TIMER_2 1201 + +// windowproc for our joystick detect thread message only window, to detect any USB device addition/removal +static LRESULT CALLBACK SDL_PrivateJoystickDetectProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_DEVICECHANGE: + switch (wParam) { + case DBT_DEVICEARRIVAL: + case DBT_DEVICEREMOVECOMPLETE: + if (((DEV_BROADCAST_HDR *)lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { + // notify 300ms and 2 seconds later to ensure all APIs have updated status + SetTimer(hwnd, IDT_SDL_DEVICE_CHANGE_TIMER_1, 300, NULL); + SetTimer(hwnd, IDT_SDL_DEVICE_CHANGE_TIMER_2, 2000, NULL); + } + break; + } + return true; + case WM_TIMER: + if (wParam == IDT_SDL_DEVICE_CHANGE_TIMER_1 || + wParam == IDT_SDL_DEVICE_CHANGE_TIMER_2) { + KillTimer(hwnd, wParam); + SetWindowsDeviceChanged(); + return true; + } + break; + } + +#ifdef SDL_JOYSTICK_RAWINPUT + return CallWindowProc(RAWINPUT_WindowProc, hwnd, msg, wParam, lParam); +#else + return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam); +#endif +} + +static void SDL_CleanupDeviceNotification(SDL_DeviceNotificationData *data) +{ +#ifdef SDL_JOYSTICK_RAWINPUT + RAWINPUT_UnregisterNotifications(); +#endif + + if (data->hNotify) { + UnregisterDeviceNotification(data->hNotify); + } + + if (data->messageWindow) { + DestroyWindow(data->messageWindow); + } + + UnregisterClass(data->wincl.lpszClassName, data->wincl.hInstance); + + if (data->coinitialized == S_OK) { + WIN_CoUninitialize(); + } +} + +static bool SDL_CreateDeviceNotification(SDL_DeviceNotificationData *data) +{ + DEV_BROADCAST_DEVICEINTERFACE dbh; + + SDL_zerop(data); + + data->coinitialized = WIN_CoInitialize(); + + data->wincl.hInstance = GetModuleHandle(NULL); + data->wincl.lpszClassName = TEXT("Message"); + data->wincl.lpfnWndProc = SDL_PrivateJoystickDetectProc; // This function is called by windows + data->wincl.cbSize = sizeof(WNDCLASSEX); + + if (!RegisterClassEx(&data->wincl)) { + WIN_SetError("Failed to create register class for joystick autodetect"); + SDL_CleanupDeviceNotification(data); + return false; + } + + data->messageWindow = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); + if (!data->messageWindow) { + WIN_SetError("Failed to create message window for joystick autodetect"); + SDL_CleanupDeviceNotification(data); + return false; + } + + SDL_zero(dbh); + dbh.dbcc_size = sizeof(dbh); + dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + dbh.dbcc_classguid = GUID_DEVINTERFACE_HID; + + data->hNotify = RegisterDeviceNotification(data->messageWindow, &dbh, DEVICE_NOTIFY_WINDOW_HANDLE); + if (!data->hNotify) { + WIN_SetError("Failed to create notify device for joystick autodetect"); + SDL_CleanupDeviceNotification(data); + return false; + } + +#ifdef SDL_JOYSTICK_RAWINPUT + RAWINPUT_RegisterNotifications(data->messageWindow); +#endif + return true; +} + +static bool SDL_WaitForDeviceNotification(SDL_DeviceNotificationData *data, SDL_Mutex *mutex) +{ + MSG msg; + int lastret = 1; + + if (!data->messageWindow) { + return false; // device notifications require a window + } + + SDL_UnlockMutex(mutex); + while (lastret > 0 && !WindowsDeviceChanged()) { + lastret = GetMessage(&msg, NULL, 0, 0); // WM_QUIT causes return value of 0 + if (lastret > 0) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + SDL_LockMutex(mutex); + return (lastret != -1); +} + +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +static SDL_DeviceNotificationData s_notification_data; +#endif + +// Function/thread to scan the system for joysticks. +static int SDLCALL SDL_JoystickThread(void *_data) +{ +#ifdef SDL_JOYSTICK_XINPUT + bool bOpenedXInputDevices[XUSER_MAX_COUNT]; + SDL_zeroa(bOpenedXInputDevices); +#endif + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (!SDL_CreateDeviceNotification(&s_notification_data)) { + return 0; + } +#endif + + SDL_LockMutex(s_mutexJoyStickEnum); + while (s_bJoystickThreadQuit == false) { +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (SDL_WaitForDeviceNotification(&s_notification_data, s_mutexJoyStickEnum) == false) { +#else + { +#endif +#ifdef SDL_JOYSTICK_XINPUT + // WM_DEVICECHANGE not working, poll for new XINPUT controllers + SDL_WaitConditionTimeout(s_condJoystickThread, s_mutexJoyStickEnum, 1000); + if (SDL_XINPUT_Enabled()) { + // scan for any change in XInput devices + Uint8 userId; + for (userId = 0; userId < XUSER_MAX_COUNT; userId++) { + XINPUT_CAPABILITIES capabilities; + const DWORD result = XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities); + const bool available = (result == ERROR_SUCCESS); + if (bOpenedXInputDevices[userId] != available) { + SetWindowsDeviceChanged(); + bOpenedXInputDevices[userId] = available; + } + } + } +#else + // WM_DEVICECHANGE not working, no XINPUT, no point in keeping thread alive + break; +#endif // SDL_JOYSTICK_XINPUT + } + } + + SDL_UnlockMutex(s_mutexJoyStickEnum); + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + SDL_CleanupDeviceNotification(&s_notification_data); +#endif + + return 1; +} + +// spin up the thread to detect hotplug of devices +static bool SDL_StartJoystickThread(void) +{ + s_mutexJoyStickEnum = SDL_CreateMutex(); + if (!s_mutexJoyStickEnum) { + return false; + } + + s_condJoystickThread = SDL_CreateCondition(); + if (!s_condJoystickThread) { + return false; + } + + s_bJoystickThreadQuit = false; + s_joystickThread = SDL_CreateThread(SDL_JoystickThread, "SDL_joystick", NULL); + if (!s_joystickThread) { + return false; + } + return true; +} + +static void SDL_StopJoystickThread(void) +{ + if (!s_joystickThread) { + return; + } + + SDL_LockMutex(s_mutexJoyStickEnum); + s_bJoystickThreadQuit = true; + SDL_BroadcastCondition(s_condJoystickThread); // signal the joystick thread to quit + SDL_UnlockMutex(s_mutexJoyStickEnum); + PostThreadMessage((DWORD)SDL_GetThreadID(s_joystickThread), WM_QUIT, 0, 0); + + // Unlock joysticks while the joystick thread finishes processing messages + SDL_AssertJoysticksLocked(); + SDL_UnlockJoysticks(); + SDL_WaitThread(s_joystickThread, NULL); // wait for it to bugger off + SDL_LockJoysticks(); + + SDL_DestroyCondition(s_condJoystickThread); + s_condJoystickThread = NULL; + + SDL_DestroyMutex(s_mutexJoyStickEnum); + s_mutexJoyStickEnum = NULL; + + s_joystickThread = NULL; +} + +void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device) +{ + device->send_add_event = true; + device->nInstanceID = SDL_GetNextObjectID(); + device->pNext = SYS_Joystick; + SYS_Joystick = device; +} + +void WINDOWS_JoystickDetect(void); +void WINDOWS_JoystickQuit(void); + +static bool WINDOWS_JoystickInit(void) +{ + if (!SDL_XINPUT_JoystickInit()) { + WINDOWS_JoystickQuit(); + return false; + } + + if (!SDL_DINPUT_JoystickInit()) { + WINDOWS_JoystickQuit(); + return false; + } + + WIN_InitDeviceNotification(); + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + s_bJoystickThread = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_THREAD, true); + if (s_bJoystickThread) { + if (!SDL_StartJoystickThread()) { + return false; + } + } else { + if (!SDL_CreateDeviceNotification(&s_notification_data)) { + return false; + } + } +#endif + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + // On Xbox, force create the joystick thread for device detection (since other methods don't work + s_bJoystickThread = true; + if (!SDL_StartJoystickThread()) { + return false; + } +#endif + + SetWindowsDeviceChanged(); // force a scan of the system for joysticks this first time + + WINDOWS_JoystickDetect(); + + return true; +} + +// return the number of joysticks that are connected right now +static int WINDOWS_JoystickGetCount(void) +{ + int nJoysticks = 0; + JoyStick_DeviceData *device = SYS_Joystick; + while (device) { + nJoysticks++; + device = device->pNext; + } + + return nJoysticks; +} + +// detect any new joysticks being inserted into the system +void WINDOWS_JoystickDetect(void) +{ + JoyStick_DeviceData *pCurList = NULL; + + // only enum the devices if the joystick thread told us something changed + if (!WindowsDeviceChanged()) { + return; // thread hasn't signaled, nothing to do right now. + } + + if (s_mutexJoyStickEnum) { + SDL_LockMutex(s_mutexJoyStickEnum); + } + + s_lastDeviceChange = WIN_GetLastDeviceNotification(); + + pCurList = SYS_Joystick; + SYS_Joystick = NULL; + + // Look for DirectInput joysticks, wheels, head trackers, gamepads, etc.. + SDL_DINPUT_JoystickDetect(&pCurList); + + // Look for XInput devices. Do this last, so they're first in the final list. + SDL_XINPUT_JoystickDetect(&pCurList); + + if (s_mutexJoyStickEnum) { + SDL_UnlockMutex(s_mutexJoyStickEnum); + } + + while (pCurList) { + JoyStick_DeviceData *pListNext = NULL; + + if (!pCurList->bXInputDevice) { +#ifdef SDL_HAPTIC_DINPUT + SDL_DINPUT_HapticMaybeRemoveDevice(&pCurList->dxdevice); +#endif + } + + SDL_PrivateJoystickRemoved(pCurList->nInstanceID); + + pListNext = pCurList->pNext; + SDL_free(pCurList->joystickname); + SDL_free(pCurList); + pCurList = pListNext; + } + + for (pCurList = SYS_Joystick; pCurList; pCurList = pCurList->pNext) { + if (pCurList->send_add_event) { + if (!pCurList->bXInputDevice) { +#ifdef SDL_HAPTIC_DINPUT + SDL_DINPUT_HapticMaybeAddDevice(&pCurList->dxdevice); +#endif + } + + SDL_PrivateJoystickAdded(pCurList->nInstanceID); + + pCurList->send_add_event = false; + } + } +} + +static bool WINDOWS_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + if (SDL_DINPUT_JoystickPresent(vendor_id, product_id, version)) { + return true; + } + if (SDL_XINPUT_JoystickPresent(vendor_id, product_id, version)) { + return true; + } + return false; +} + +static const char *WINDOWS_JoystickGetDeviceName(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + return device->joystickname; +} + +static const char *WINDOWS_JoystickGetDevicePath(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + return device->path; +} + +static int WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + if (device->bXInputDevice) { + // The slot for XInput devices can change as controllers are seated + return SDL_XINPUT_GetSteamVirtualGamepadSlot(device->XInputUserId); + } else { + return device->steam_virtual_gamepad_slot; + } +} + +static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + return device->bXInputDevice ? (int)device->XInputUserId : -1; +} + +static void WINDOWS_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +// return the stable device guid for this device index +static SDL_GUID WINDOWS_JoystickGetDeviceGUID(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + return device->guid; +} + +// Function to perform the mapping between current device instance and this joysticks instance id +static SDL_JoystickID WINDOWS_JoystickGetDeviceInstanceID(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + return device->nInstanceID; +} + +/* Function to open a joystick for use. + The joystick to open is specified by the device index. + This should fill the nbuttons and naxes fields of the joystick structure. + It returns 0, or -1 if there is an error. + */ +static bool WINDOWS_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + // allocate memory for system specific hardware data + joystick->hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(struct joystick_hwdata)); + if (!joystick->hwdata) { + return false; + } + joystick->hwdata->guid = device->guid; + + if (device->bXInputDevice) { + return SDL_XINPUT_JoystickOpen(joystick, device); + } else { + return SDL_DINPUT_JoystickOpen(joystick, device); + } +} + +static bool WINDOWS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + if (joystick->hwdata->bXInputDevice) { + return SDL_XINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble); + } else { + return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble); + } +} + +static bool WINDOWS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static bool WINDOWS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool WINDOWS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool WINDOWS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void WINDOWS_JoystickUpdate(SDL_Joystick *joystick) +{ + if (!joystick->hwdata) { + return; + } + + if (joystick->hwdata->bXInputDevice) { + SDL_XINPUT_JoystickUpdate(joystick); + } else { + SDL_DINPUT_JoystickUpdate(joystick); + } +} + +// Function to close a joystick after use +static void WINDOWS_JoystickClose(SDL_Joystick *joystick) +{ + if (joystick->hwdata->bXInputDevice) { + SDL_XINPUT_JoystickClose(joystick); + } else { + SDL_DINPUT_JoystickClose(joystick); + } + + SDL_free(joystick->hwdata); +} + +// Function to perform any system-specific joystick related cleanup +void WINDOWS_JoystickQuit(void) +{ + JoyStick_DeviceData *device = SYS_Joystick; + + while (device) { + JoyStick_DeviceData *device_next = device->pNext; + SDL_free(device->joystickname); + SDL_free(device); + device = device_next; + } + SYS_Joystick = NULL; + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (s_bJoystickThread) { + SDL_StopJoystickThread(); + } else { + SDL_CleanupDeviceNotification(&s_notification_data); + } +#endif + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + if (s_bJoystickThread) { + SDL_StopJoystickThread(); + } +#endif + + SDL_DINPUT_JoystickQuit(); + SDL_XINPUT_JoystickQuit(); + + WIN_QuitDeviceNotification(); +} + +static bool WINDOWS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +SDL_JoystickDriver SDL_WINDOWS_JoystickDriver = { + WINDOWS_JoystickInit, + WINDOWS_JoystickGetCount, + WINDOWS_JoystickDetect, + WINDOWS_JoystickIsDevicePresent, + WINDOWS_JoystickGetDeviceName, + WINDOWS_JoystickGetDevicePath, + WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot, + WINDOWS_JoystickGetDevicePlayerIndex, + WINDOWS_JoystickSetDevicePlayerIndex, + WINDOWS_JoystickGetDeviceGUID, + WINDOWS_JoystickGetDeviceInstanceID, + WINDOWS_JoystickOpen, + WINDOWS_JoystickRumble, + WINDOWS_JoystickRumbleTriggers, + WINDOWS_JoystickSetLED, + WINDOWS_JoystickSendEffect, + WINDOWS_JoystickSetSensorsEnabled, + WINDOWS_JoystickUpdate, + WINDOWS_JoystickClose, + WINDOWS_JoystickQuit, + WINDOWS_JoystickGetGamepadMapping +}; + +#else + +#ifdef SDL_JOYSTICK_RAWINPUT +// The RAWINPUT driver needs the device notification setup above +#error SDL_JOYSTICK_RAWINPUT requires SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT +#endif + +#endif // SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h new file mode 100644 index 0000000..16b9184 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h @@ -0,0 +1,103 @@ +/* + 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" + +#include "../SDL_sysjoystick.h" +#include "../../core/windows/SDL_windows.h" +#include "../../core/windows/SDL_directx.h" + +#define MAX_INPUTS 256 // each joystick can have up to 256 inputs + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct JoyStick_DeviceData +{ + SDL_GUID guid; + char *joystickname; + Uint8 send_add_event; + SDL_JoystickID nInstanceID; + bool bXInputDevice; + BYTE SubType; + Uint8 XInputUserId; + DIDEVICEINSTANCE dxdevice; + char path[MAX_PATH]; + int steam_virtual_gamepad_slot; + struct JoyStick_DeviceData *pNext; +} JoyStick_DeviceData; + +extern JoyStick_DeviceData *SYS_Joystick; // array to hold joystick ID values + +typedef enum Type +{ + BUTTON, + AXIS, + HAT +} Type; + +typedef struct input_t +{ + // DirectInput offset for this input type: + DWORD ofs; + + // Button, axis or hat: + Type type; + + // SDL input offset: + Uint8 num; +} input_t; + +// The private structure used to keep track of a joystick +struct joystick_hwdata +{ + SDL_GUID guid; + +#ifdef SDL_JOYSTICK_DINPUT + LPDIRECTINPUTDEVICE8 InputDevice; + DIDEVCAPS Capabilities; + bool buffered; + bool first_update; + input_t Inputs[MAX_INPUTS]; + int NumInputs; + int NumSliders; + bool ff_initialized; + DIEFFECT *ffeffect; + LPDIRECTINPUTEFFECT ffeffect_ref; +#endif + + bool bXInputDevice; // true if this device supports using the xinput API rather than DirectInput + bool bXInputHaptic; // Supports force feedback via XInput. + Uint8 userid; // XInput userid index for this joystick + DWORD dwPacketNumber; +}; + +#ifdef SDL_JOYSTICK_DINPUT +extern const DIDATAFORMAT SDL_c_dfDIJoystick2; +#endif + +extern void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device); + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c new file mode 100644 index 0000000..9f6ce10 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c @@ -0,0 +1,473 @@ +/* + 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" + +#include "../SDL_sysjoystick.h" + +#ifdef SDL_JOYSTICK_XINPUT + +#include "SDL_windowsjoystick_c.h" +#include "SDL_xinputjoystick_c.h" +#include "SDL_rawinputjoystick_c.h" +#include "../hidapi/SDL_hidapijoystick_c.h" + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Internal stuff. + */ +static bool s_bXInputEnabled = false; + +bool SDL_XINPUT_Enabled(void) +{ + return s_bXInputEnabled; +} + +bool SDL_XINPUT_JoystickInit(void) +{ + bool enabled = SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, true); + + if (enabled && !WIN_LoadXInputDLL()) { + enabled = false; // oh well. + } + s_bXInputEnabled = enabled; + + return true; +} + +static const char *GetXInputName(const Uint8 userid, BYTE SubType) +{ + static char name[32]; + + switch (SubType) { + case XINPUT_DEVSUBTYPE_GAMEPAD: + (void)SDL_snprintf(name, sizeof(name), "XInput Controller #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_WHEEL: + (void)SDL_snprintf(name, sizeof(name), "XInput Wheel #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_ARCADE_STICK: + (void)SDL_snprintf(name, sizeof(name), "XInput ArcadeStick #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_FLIGHT_STICK: + (void)SDL_snprintf(name, sizeof(name), "XInput FlightStick #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_DANCE_PAD: + (void)SDL_snprintf(name, sizeof(name), "XInput DancePad #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_GUITAR: + case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE: + case XINPUT_DEVSUBTYPE_GUITAR_BASS: + (void)SDL_snprintf(name, sizeof(name), "XInput Guitar #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_DRUM_KIT: + (void)SDL_snprintf(name, sizeof(name), "XInput DrumKit #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_ARCADE_PAD: + (void)SDL_snprintf(name, sizeof(name), "XInput ArcadePad #%d", 1 + userid); + break; + default: + (void)SDL_snprintf(name, sizeof(name), "XInput Device #%d", 1 + userid); + break; + } + return name; +} + +static bool GetXInputDeviceInfo(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion) +{ + SDL_XINPUT_CAPABILITIES_EX capabilities; + + if (!XINPUTGETCAPABILITIESEX || XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) != ERROR_SUCCESS) { + // Use a generic VID/PID representing an XInput controller + if (pVID) { + *pVID = USB_VENDOR_MICROSOFT; + } + if (pPID) { + *pPID = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; + } + return false; + } + + // Fixup for Wireless Xbox 360 Controller + if (capabilities.ProductId == 0 && capabilities.Capabilities.Flags & XINPUT_CAPS_WIRELESS) { + capabilities.VendorId = USB_VENDOR_MICROSOFT; + capabilities.ProductId = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; + } + + if (pVID) { + *pVID = capabilities.VendorId; + } + if (pPID) { + *pPID = capabilities.ProductId; + } + if (pVersion) { + *pVersion = capabilities.ProductVersion; + } + return true; +} + +int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid) +{ + SDL_XINPUT_CAPABILITIES_EX capabilities; + + if (XINPUTGETCAPABILITIESEX && + XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) == ERROR_SUCCESS && + capabilities.VendorId == USB_VENDOR_VALVE && + capabilities.ProductId == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + return (int)capabilities.unk2; + } + return -1; +} + +static void AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext) +{ + const char *name = NULL; + Uint16 vendor = 0; + Uint16 product = 0; + Uint16 version = 0; + JoyStick_DeviceData *pPrevJoystick = NULL; + JoyStick_DeviceData *pNewJoystick = *pContext; + +#ifdef SDL_JOYSTICK_RAWINPUT + if (RAWINPUT_IsEnabled()) { + // The raw input driver handles more than 4 controllers, so prefer that when available + /* We do this check here rather than at the top of SDL_XINPUT_JoystickDetect() because + we need to check XInput state before RAWINPUT gets a hold of the device, otherwise + when a controller is connected via the wireless adapter, it will shut down at the + first subsequent XInput call. This seems like a driver stack bug? + + Reference: https://github.com/libsdl-org/SDL/issues/3468 + */ + return; + } +#endif + + if (SubType == XINPUT_DEVSUBTYPE_UNKNOWN) { + return; + } + + while (pNewJoystick) { + if (pNewJoystick->bXInputDevice && (pNewJoystick->XInputUserId == userid) && (pNewJoystick->SubType == SubType)) { + // if we are replacing the front of the list then update it + if (pNewJoystick == *pContext) { + *pContext = pNewJoystick->pNext; + } else if (pPrevJoystick) { + pPrevJoystick->pNext = pNewJoystick->pNext; + } + + pNewJoystick->pNext = SYS_Joystick; + SYS_Joystick = pNewJoystick; + return; // already in the list. + } + + pPrevJoystick = pNewJoystick; + pNewJoystick = pNewJoystick->pNext; + } + + name = GetXInputName(userid, SubType); + GetXInputDeviceInfo(userid, &vendor, &product, &version); + if (SDL_ShouldIgnoreJoystick(vendor, product, version, name) || + SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, name)) { + return; + } + + pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData)); + if (!pNewJoystick) { + return; // better luck next time? + } + + pNewJoystick->bXInputDevice = true; + pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, NULL, name); + if (!pNewJoystick->joystickname) { + SDL_free(pNewJoystick); + return; // better luck next time? + } + (void)SDL_snprintf(pNewJoystick->path, sizeof(pNewJoystick->path), "XInput#%u", userid); + pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, NULL, name, 'x', SubType); + pNewJoystick->SubType = SubType; + pNewJoystick->XInputUserId = userid; + + WINDOWS_AddJoystickDevice(pNewJoystick); +} + +void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext) +{ + int iuserid; + + if (!s_bXInputEnabled) { + return; + } + + // iterate in reverse, so these are in the final list in ascending numeric order. + for (iuserid = XUSER_MAX_COUNT - 1; iuserid >= 0; iuserid--) { + const Uint8 userid = (Uint8)iuserid; + XINPUT_CAPABILITIES capabilities; + if (XINPUTGETCAPABILITIES(userid, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) { + AddXInputDevice(userid, capabilities.SubType, pContext); + } + } +} + +bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version) +{ + int iuserid; + + if (!s_bXInputEnabled) { + return false; + } + + // iterate in reverse, so these are in the final list in ascending numeric order. + for (iuserid = 0; iuserid < XUSER_MAX_COUNT; ++iuserid) { + const Uint8 userid = (Uint8)iuserid; + Uint16 slot_vendor; + Uint16 slot_product; + Uint16 slot_version; + if (GetXInputDeviceInfo(userid, &slot_vendor, &slot_product, &slot_version)) { + if (vendor == slot_vendor && product == slot_product && version == slot_version) { + return true; + } + } + } + return false; +} + +bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice) +{ + const Uint8 userId = joystickdevice->XInputUserId; + XINPUT_CAPABILITIES capabilities; + XINPUT_VIBRATION state; + + SDL_assert(s_bXInputEnabled); + SDL_assert(XINPUTGETCAPABILITIES); + SDL_assert(XINPUTSETSTATE); + SDL_assert(userId < XUSER_MAX_COUNT); + + joystick->hwdata->bXInputDevice = true; + + if (XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities) != ERROR_SUCCESS) { + SDL_free(joystick->hwdata); + joystick->hwdata = NULL; + return SDL_SetError("Failed to obtain XInput device capabilities. Device disconnected?"); + } + SDL_zero(state); + joystick->hwdata->bXInputHaptic = (XINPUTSETSTATE(userId, &state) == ERROR_SUCCESS); + joystick->hwdata->userid = userId; + + // The XInput API has a hard coded button/axis mapping, so we just match it + joystick->naxes = 6; + joystick->nbuttons = 11; + joystick->nhats = 1; + + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + + return true; +} + +static void UpdateXInputJoystickBatteryInformation(SDL_Joystick *joystick, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) +{ + SDL_PowerState state; + int percent; + switch (pBatteryInformation->BatteryType) { + case BATTERY_TYPE_WIRED: + state = SDL_POWERSTATE_CHARGING; + break; + case BATTERY_TYPE_UNKNOWN: + case BATTERY_TYPE_DISCONNECTED: + state = SDL_POWERSTATE_UNKNOWN; + break; + default: + state = SDL_POWERSTATE_ON_BATTERY; + break; + } + switch (pBatteryInformation->BatteryLevel) { + case BATTERY_LEVEL_EMPTY: + percent = 10; + break; + case BATTERY_LEVEL_LOW: + percent = 40; + break; + case BATTERY_LEVEL_MEDIUM: + percent = 70; + break; + default: + case BATTERY_LEVEL_FULL: + percent = 100; + break; + } + SDL_SendJoystickPowerInfo(joystick, state, percent); +} + +static void UpdateXInputJoystickState(SDL_Joystick *joystick, XINPUT_STATE *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) +{ + static WORD s_XInputButtons[] = { + XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y, + XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_START, + XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB, + XINPUT_GAMEPAD_GUIDE + }; + WORD wButtons = pXInputState->Gamepad.wButtons; + Uint8 button; + Uint8 hat = SDL_HAT_CENTERED; + Uint64 timestamp = SDL_GetTicksNS(); + + SDL_SendJoystickAxis(timestamp, joystick, 0, pXInputState->Gamepad.sThumbLX); + SDL_SendJoystickAxis(timestamp, joystick, 1, ~pXInputState->Gamepad.sThumbLY); + SDL_SendJoystickAxis(timestamp, joystick, 2, ((int)pXInputState->Gamepad.bLeftTrigger * 257) - 32768); + SDL_SendJoystickAxis(timestamp, joystick, 3, pXInputState->Gamepad.sThumbRX); + SDL_SendJoystickAxis(timestamp, joystick, 4, ~pXInputState->Gamepad.sThumbRY); + SDL_SendJoystickAxis(timestamp, joystick, 5, ((int)pXInputState->Gamepad.bRightTrigger * 257) - 32768); + + for (button = 0; button < (Uint8)SDL_arraysize(s_XInputButtons); ++button) { + bool down = ((wButtons & s_XInputButtons[button]) != 0); + SDL_SendJoystickButton(timestamp, joystick, button, down); + } + + if (wButtons & XINPUT_GAMEPAD_DPAD_UP) { + hat |= SDL_HAT_UP; + } + if (wButtons & XINPUT_GAMEPAD_DPAD_DOWN) { + hat |= SDL_HAT_DOWN; + } + if (wButtons & XINPUT_GAMEPAD_DPAD_LEFT) { + hat |= SDL_HAT_LEFT; + } + if (wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation); +} + +bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + XINPUT_VIBRATION XVibration; + + if (!XINPUTSETSTATE) { + return SDL_Unsupported(); + } + + XVibration.wLeftMotorSpeed = low_frequency_rumble; + XVibration.wRightMotorSpeed = high_frequency_rumble; + if (XINPUTSETSTATE(joystick->hwdata->userid, &XVibration) != ERROR_SUCCESS) { + return SDL_SetError("XInputSetState() failed"); + } + return true; +} + +void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick) +{ + DWORD result; + XINPUT_STATE XInputState; + XINPUT_BATTERY_INFORMATION_EX XBatteryInformation; + + if (!XINPUTGETSTATE) { + return; + } + + result = XINPUTGETSTATE(joystick->hwdata->userid, &XInputState); + if (result == ERROR_DEVICE_NOT_CONNECTED) { + return; + } + + SDL_zero(XBatteryInformation); + if (XINPUTGETBATTERYINFORMATION) { + result = XINPUTGETBATTERYINFORMATION(joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation); + } + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + // XInputOnGameInput doesn't ever change dwPacketNumber, so have to just update every frame + UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation); +#else + // only fire events if the data changed from last time + if (XInputState.dwPacketNumber && XInputState.dwPacketNumber != joystick->hwdata->dwPacketNumber) { + UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation); + joystick->hwdata->dwPacketNumber = XInputState.dwPacketNumber; + } +#endif +} + +void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick) +{ +} + +void SDL_XINPUT_JoystickQuit(void) +{ + if (s_bXInputEnabled) { + s_bXInputEnabled = false; + WIN_UnloadXInputDLL(); + } +} + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif + +#else // !SDL_JOYSTICK_XINPUT + +typedef struct JoyStick_DeviceData JoyStick_DeviceData; + +bool SDL_XINPUT_Enabled(void) +{ + return false; +} + +bool SDL_XINPUT_JoystickInit(void) +{ + return true; +} + +void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext) +{ +} + +bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version) +{ + return false; +} + +bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice) +{ + return SDL_Unsupported(); +} + +bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick) +{ +} + +void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick) +{ +} + +void SDL_XINPUT_JoystickQuit(void) +{ +} + +#endif // SDL_JOYSTICK_XINPUT diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h new file mode 100644 index 0000000..305b090 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h @@ -0,0 +1,44 @@ +/* + 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" + +#include "../../core/windows/SDL_xinput.h" + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +extern bool SDL_XINPUT_Enabled(void); +extern bool SDL_XINPUT_JoystickInit(void); +extern void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext); +extern bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version); +extern bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice); +extern bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); +extern void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick); +extern void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick); +extern void SDL_XINPUT_JoystickQuit(void); +extern int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid); + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif -- cgit v1.2.3