diff options
Diffstat (limited to 'contrib/SDL-3.2.8/src/joystick/hidapi')
23 files changed, 19217 insertions, 0 deletions
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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | // This driver supports the Nintendo Switch Joy-Cons pair controllers | ||
| 22 | #include "SDL_internal.h" | ||
| 23 | |||
| 24 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 25 | |||
| 26 | #include "SDL_hidapijoystick_c.h" | ||
| 27 | #include "../SDL_sysjoystick.h" | ||
| 28 | |||
| 29 | static void HIDAPI_DriverCombined_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 30 | { | ||
| 31 | } | ||
| 32 | |||
| 33 | static void HIDAPI_DriverCombined_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 34 | { | ||
| 35 | } | ||
| 36 | |||
| 37 | static bool HIDAPI_DriverCombined_IsEnabled(void) | ||
| 38 | { | ||
| 39 | return true; | ||
| 40 | } | ||
| 41 | |||
| 42 | 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) | ||
| 43 | { | ||
| 44 | // This is always explicitly created for combined devices | ||
| 45 | return false; | ||
| 46 | } | ||
| 47 | |||
| 48 | static bool HIDAPI_DriverCombined_InitDevice(SDL_HIDAPI_Device *device) | ||
| 49 | { | ||
| 50 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 51 | } | ||
| 52 | |||
| 53 | static int HIDAPI_DriverCombined_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 54 | { | ||
| 55 | return -1; | ||
| 56 | } | ||
| 57 | |||
| 58 | static void HIDAPI_DriverCombined_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 59 | { | ||
| 60 | } | ||
| 61 | |||
| 62 | static bool HIDAPI_DriverCombined_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 63 | { | ||
| 64 | int i; | ||
| 65 | char *serial = NULL, *new_serial; | ||
| 66 | size_t serial_length = 0, new_length; | ||
| 67 | |||
| 68 | SDL_AssertJoysticksLocked(); | ||
| 69 | |||
| 70 | for (i = 0; i < device->num_children; ++i) { | ||
| 71 | SDL_HIDAPI_Device *child = device->children[i]; | ||
| 72 | if (!child->driver->OpenJoystick(child, joystick)) { | ||
| 73 | child->broken = true; | ||
| 74 | |||
| 75 | while (i-- > 0) { | ||
| 76 | child = device->children[i]; | ||
| 77 | child->driver->CloseJoystick(child, joystick); | ||
| 78 | } | ||
| 79 | if (serial) { | ||
| 80 | SDL_free(serial); | ||
| 81 | } | ||
| 82 | return false; | ||
| 83 | } | ||
| 84 | |||
| 85 | // Extend the serial number with the child serial number | ||
| 86 | if (joystick->serial) { | ||
| 87 | new_length = serial_length + 1 + SDL_strlen(joystick->serial); | ||
| 88 | new_serial = (char *)SDL_realloc(serial, new_length); | ||
| 89 | if (new_serial) { | ||
| 90 | if (serial) { | ||
| 91 | SDL_strlcat(new_serial, ",", new_length); | ||
| 92 | SDL_strlcat(new_serial, joystick->serial, new_length); | ||
| 93 | } else { | ||
| 94 | SDL_strlcpy(new_serial, joystick->serial, new_length); | ||
| 95 | } | ||
| 96 | serial = new_serial; | ||
| 97 | serial_length = new_length; | ||
| 98 | } | ||
| 99 | SDL_free(joystick->serial); | ||
| 100 | joystick->serial = NULL; | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | // Update the joystick with the combined serial numbers | ||
| 105 | if (joystick->serial) { | ||
| 106 | SDL_free(joystick->serial); | ||
| 107 | } | ||
| 108 | joystick->serial = serial; | ||
| 109 | |||
| 110 | return true; | ||
| 111 | } | ||
| 112 | |||
| 113 | static bool HIDAPI_DriverCombined_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 114 | { | ||
| 115 | int i; | ||
| 116 | bool result = false; | ||
| 117 | |||
| 118 | for (i = 0; i < device->num_children; ++i) { | ||
| 119 | SDL_HIDAPI_Device *child = device->children[i]; | ||
| 120 | if (child->driver->RumbleJoystick(child, joystick, low_frequency_rumble, high_frequency_rumble)) { | ||
| 121 | result = true; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | return result; | ||
| 125 | } | ||
| 126 | |||
| 127 | static bool HIDAPI_DriverCombined_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 128 | { | ||
| 129 | int i; | ||
| 130 | bool result = false; | ||
| 131 | |||
| 132 | for (i = 0; i < device->num_children; ++i) { | ||
| 133 | SDL_HIDAPI_Device *child = device->children[i]; | ||
| 134 | if (child->driver->RumbleJoystickTriggers(child, joystick, left_rumble, right_rumble)) { | ||
| 135 | result = true; | ||
| 136 | } | ||
| 137 | } | ||
| 138 | return result; | ||
| 139 | } | ||
| 140 | |||
| 141 | static Uint32 HIDAPI_DriverCombined_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 142 | { | ||
| 143 | int i; | ||
| 144 | Uint32 caps = 0; | ||
| 145 | |||
| 146 | for (i = 0; i < device->num_children; ++i) { | ||
| 147 | SDL_HIDAPI_Device *child = device->children[i]; | ||
| 148 | caps |= child->driver->GetJoystickCapabilities(child, joystick); | ||
| 149 | } | ||
| 150 | return caps; | ||
| 151 | } | ||
| 152 | |||
| 153 | static bool HIDAPI_DriverCombined_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 154 | { | ||
| 155 | int i; | ||
| 156 | bool result = false; | ||
| 157 | |||
| 158 | for (i = 0; i < device->num_children; ++i) { | ||
| 159 | SDL_HIDAPI_Device *child = device->children[i]; | ||
| 160 | if (child->driver->SetJoystickLED(child, joystick, red, green, blue)) { | ||
| 161 | result = true; | ||
| 162 | } | ||
| 163 | } | ||
| 164 | return result; | ||
| 165 | } | ||
| 166 | |||
| 167 | static bool HIDAPI_DriverCombined_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 168 | { | ||
| 169 | return SDL_Unsupported(); | ||
| 170 | } | ||
| 171 | |||
| 172 | static bool HIDAPI_DriverCombined_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 173 | { | ||
| 174 | int i; | ||
| 175 | bool result = false; | ||
| 176 | |||
| 177 | for (i = 0; i < device->num_children; ++i) { | ||
| 178 | SDL_HIDAPI_Device *child = device->children[i]; | ||
| 179 | if (child->driver->SetJoystickSensorsEnabled(child, joystick, enabled)) { | ||
| 180 | result = true; | ||
| 181 | } | ||
| 182 | } | ||
| 183 | return result; | ||
| 184 | } | ||
| 185 | |||
| 186 | static bool HIDAPI_DriverCombined_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 187 | { | ||
| 188 | int i; | ||
| 189 | int result = true; | ||
| 190 | |||
| 191 | for (i = 0; i < device->num_children; ++i) { | ||
| 192 | SDL_HIDAPI_Device *child = device->children[i]; | ||
| 193 | if (!child->driver->UpdateDevice(child)) { | ||
| 194 | result = false; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | return result; | ||
| 198 | } | ||
| 199 | |||
| 200 | static void HIDAPI_DriverCombined_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 201 | { | ||
| 202 | int i; | ||
| 203 | |||
| 204 | for (i = 0; i < device->num_children; ++i) { | ||
| 205 | SDL_HIDAPI_Device *child = device->children[i]; | ||
| 206 | child->driver->CloseJoystick(child, joystick); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | static void HIDAPI_DriverCombined_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 211 | { | ||
| 212 | } | ||
| 213 | |||
| 214 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined = { | ||
| 215 | "SDL_JOYSTICK_HIDAPI_COMBINED", | ||
| 216 | true, | ||
| 217 | HIDAPI_DriverCombined_RegisterHints, | ||
| 218 | HIDAPI_DriverCombined_UnregisterHints, | ||
| 219 | HIDAPI_DriverCombined_IsEnabled, | ||
| 220 | HIDAPI_DriverCombined_IsSupportedDevice, | ||
| 221 | HIDAPI_DriverCombined_InitDevice, | ||
| 222 | HIDAPI_DriverCombined_GetDevicePlayerIndex, | ||
| 223 | HIDAPI_DriverCombined_SetDevicePlayerIndex, | ||
| 224 | HIDAPI_DriverCombined_UpdateDevice, | ||
| 225 | HIDAPI_DriverCombined_OpenJoystick, | ||
| 226 | HIDAPI_DriverCombined_RumbleJoystick, | ||
| 227 | HIDAPI_DriverCombined_RumbleJoystickTriggers, | ||
| 228 | HIDAPI_DriverCombined_GetJoystickCapabilities, | ||
| 229 | HIDAPI_DriverCombined_SetJoystickLED, | ||
| 230 | HIDAPI_DriverCombined_SendJoystickEffect, | ||
| 231 | HIDAPI_DriverCombined_SetJoystickSensorsEnabled, | ||
| 232 | HIDAPI_DriverCombined_CloseJoystick, | ||
| 233 | HIDAPI_DriverCombined_FreeDevice, | ||
| 234 | }; | ||
| 235 | |||
| 236 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../../SDL_hints_c.h" | ||
| 26 | #include "../SDL_sysjoystick.h" | ||
| 27 | #include "SDL_hidapijoystick_c.h" | ||
| 28 | #include "SDL_hidapi_rumble.h" | ||
| 29 | #include "../../hidapi/SDL_hidapi_c.h" | ||
| 30 | |||
| 31 | #ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE | ||
| 32 | |||
| 33 | // Define this if you want to log all packets from the controller | ||
| 34 | // #define DEBUG_GAMECUBE_PROTOCOL | ||
| 35 | |||
| 36 | #define MAX_CONTROLLERS 4 | ||
| 37 | |||
| 38 | typedef struct | ||
| 39 | { | ||
| 40 | bool pc_mode; | ||
| 41 | SDL_JoystickID joysticks[MAX_CONTROLLERS]; | ||
| 42 | Uint8 wireless[MAX_CONTROLLERS]; | ||
| 43 | Uint8 min_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_COUNT]; | ||
| 44 | Uint8 max_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_COUNT]; | ||
| 45 | Uint8 rumbleAllowed[MAX_CONTROLLERS]; | ||
| 46 | Uint8 rumble[1 + MAX_CONTROLLERS]; | ||
| 47 | // Without this variable, hid_write starts to lag a TON | ||
| 48 | bool rumbleUpdate; | ||
| 49 | bool useRumbleBrake; | ||
| 50 | } SDL_DriverGameCube_Context; | ||
| 51 | |||
| 52 | static void HIDAPI_DriverGameCube_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 53 | { | ||
| 54 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata); | ||
| 55 | } | ||
| 56 | |||
| 57 | static void HIDAPI_DriverGameCube_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 58 | { | ||
| 59 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata); | ||
| 60 | } | ||
| 61 | |||
| 62 | static bool HIDAPI_DriverGameCube_IsEnabled(void) | ||
| 63 | { | ||
| 64 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, | ||
| 65 | SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, | ||
| 66 | SDL_HIDAPI_DEFAULT)); | ||
| 67 | } | ||
| 68 | |||
| 69 | 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) | ||
| 70 | { | ||
| 71 | if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) { | ||
| 72 | // Nintendo Co., Ltd. Wii U GameCube Controller Adapter | ||
| 73 | return true; | ||
| 74 | } | ||
| 75 | if (vendor_id == USB_VENDOR_DRAGONRISE && | ||
| 76 | (product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 || | ||
| 77 | product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2)) { | ||
| 78 | // EVORETRO GameCube Controller Adapter | ||
| 79 | return true; | ||
| 80 | } | ||
| 81 | return false; | ||
| 82 | } | ||
| 83 | |||
| 84 | static void ResetAxisRange(SDL_DriverGameCube_Context *ctx, int joystick_index) | ||
| 85 | { | ||
| 86 | SDL_memset(&ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT], 128 - 88, SDL_GAMEPAD_AXIS_COUNT); | ||
| 87 | SDL_memset(&ctx->max_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT], 128 + 88, SDL_GAMEPAD_AXIS_COUNT); | ||
| 88 | |||
| 89 | // Trigger axes may have a higher resting value | ||
| 90 | ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT + SDL_GAMEPAD_AXIS_LEFT_TRIGGER] = 40; | ||
| 91 | ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER] = 40; | ||
| 92 | } | ||
| 93 | |||
| 94 | static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 95 | { | ||
| 96 | if (hint) { | ||
| 97 | SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata; | ||
| 98 | ctx->useRumbleBrake = SDL_GetStringBoolean(hint, false); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) | ||
| 103 | { | ||
| 104 | SDL_DriverGameCube_Context *ctx; | ||
| 105 | Uint8 packet[37]; | ||
| 106 | Uint8 *curSlot; | ||
| 107 | Uint8 i; | ||
| 108 | int size; | ||
| 109 | Uint8 initMagic = 0x13; | ||
| 110 | Uint8 rumbleMagic = 0x11; | ||
| 111 | |||
| 112 | #ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS | ||
| 113 | SDL_EnableGameCubeAdaptors(); | ||
| 114 | #endif | ||
| 115 | |||
| 116 | ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 117 | if (!ctx) { | ||
| 118 | return false; | ||
| 119 | } | ||
| 120 | device->context = ctx; | ||
| 121 | |||
| 122 | ctx->joysticks[0] = 0; | ||
| 123 | ctx->joysticks[1] = 0; | ||
| 124 | ctx->joysticks[2] = 0; | ||
| 125 | ctx->joysticks[3] = 0; | ||
| 126 | ctx->rumble[0] = rumbleMagic; | ||
| 127 | ctx->useRumbleBrake = false; | ||
| 128 | |||
| 129 | if (device->vendor_id != USB_VENDOR_NINTENDO) { | ||
| 130 | ctx->pc_mode = true; | ||
| 131 | } | ||
| 132 | |||
| 133 | if (ctx->pc_mode) { | ||
| 134 | for (i = 0; i < MAX_CONTROLLERS; ++i) { | ||
| 135 | ResetAxisRange(ctx, i); | ||
| 136 | HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); | ||
| 137 | } | ||
| 138 | } else { | ||
| 139 | // This is all that's needed to initialize the device. Really! | ||
| 140 | if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) { | ||
| 141 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, | ||
| 142 | "HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028"); | ||
| 143 | return false; | ||
| 144 | } | ||
| 145 | |||
| 146 | // Wait for the adapter to initialize | ||
| 147 | SDL_Delay(10); | ||
| 148 | |||
| 149 | // Add all the applicable joysticks | ||
| 150 | while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) { | ||
| 151 | #ifdef DEBUG_GAMECUBE_PROTOCOL | ||
| 152 | HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size); | ||
| 153 | #endif | ||
| 154 | if (size < 37 || packet[0] != 0x21) { | ||
| 155 | continue; // Nothing to do yet...? | ||
| 156 | } | ||
| 157 | |||
| 158 | // Go through all 4 slots | ||
| 159 | curSlot = packet + 1; | ||
| 160 | for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) { | ||
| 161 | ctx->wireless[i] = (curSlot[0] & 0x20) != 0; | ||
| 162 | |||
| 163 | // Only allow rumble if the adapter's second USB cable is connected | ||
| 164 | ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i]; | ||
| 165 | |||
| 166 | if (curSlot[0] & 0x30) { // 0x10 - Wired, 0x20 - Wireless | ||
| 167 | if (ctx->joysticks[i] == 0) { | ||
| 168 | ResetAxisRange(ctx, i); | ||
| 169 | HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); | ||
| 170 | } | ||
| 171 | } else { | ||
| 172 | if (ctx->joysticks[i] != 0) { | ||
| 173 | HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]); | ||
| 174 | ctx->joysticks[i] = 0; | ||
| 175 | } | ||
| 176 | continue; | ||
| 177 | } | ||
| 178 | } | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE, | ||
| 183 | SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx); | ||
| 184 | |||
| 185 | HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller"); | ||
| 186 | |||
| 187 | return true; | ||
| 188 | } | ||
| 189 | |||
| 190 | static int HIDAPI_DriverGameCube_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 191 | { | ||
| 192 | SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; | ||
| 193 | Uint8 i; | ||
| 194 | |||
| 195 | for (i = 0; i < 4; ++i) { | ||
| 196 | if (instance_id == ctx->joysticks[i]) { | ||
| 197 | return i; | ||
| 198 | } | ||
| 199 | } | ||
| 200 | return -1; | ||
| 201 | } | ||
| 202 | |||
| 203 | static void HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 204 | { | ||
| 205 | } | ||
| 206 | |||
| 207 | static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, int size) | ||
| 208 | { | ||
| 209 | SDL_Joystick *joystick; | ||
| 210 | Uint8 i, v; | ||
| 211 | Sint16 axis_value; | ||
| 212 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 213 | |||
| 214 | if (size != 10) { | ||
| 215 | return; // How do we handle this packet? | ||
| 216 | } | ||
| 217 | |||
| 218 | i = packet[0] - 1; | ||
| 219 | if (i >= MAX_CONTROLLERS) { | ||
| 220 | return; // How do we handle this packet? | ||
| 221 | } | ||
| 222 | |||
| 223 | joystick = SDL_GetJoystickFromID(ctx->joysticks[i]); | ||
| 224 | if (!joystick) { | ||
| 225 | // Hasn't been opened yet, skip | ||
| 226 | return; | ||
| 227 | } | ||
| 228 | |||
| 229 | #define READ_BUTTON(off, flag, button) \ | ||
| 230 | SDL_SendJoystickButton( \ | ||
| 231 | timestamp, \ | ||
| 232 | joystick, \ | ||
| 233 | button, \ | ||
| 234 | ((packet[off] & flag) != 0)); | ||
| 235 | READ_BUTTON(1, 0x02, 0) // A | ||
| 236 | READ_BUTTON(1, 0x04, 1) // B | ||
| 237 | READ_BUTTON(1, 0x08, 3) // Y | ||
| 238 | READ_BUTTON(1, 0x01, 2) // X | ||
| 239 | READ_BUTTON(2, 0x80, 4) // DPAD_LEFT | ||
| 240 | READ_BUTTON(2, 0x20, 5) // DPAD_RIGHT | ||
| 241 | READ_BUTTON(2, 0x40, 6) // DPAD_DOWN | ||
| 242 | READ_BUTTON(2, 0x10, 7) // DPAD_UP | ||
| 243 | READ_BUTTON(2, 0x02, 8) // START | ||
| 244 | READ_BUTTON(1, 0x80, 9) // RIGHTSHOULDER | ||
| 245 | /* These two buttons are for the bottoms of the analog triggers. | ||
| 246 | * More than likely, you're going to want to read the axes instead! | ||
| 247 | * -flibit | ||
| 248 | */ | ||
| 249 | READ_BUTTON(1, 0x20, 10) // TRIGGERRIGHT | ||
| 250 | READ_BUTTON(1, 0x10, 11) // TRIGGERLEFT | ||
| 251 | #undef READ_BUTTON | ||
| 252 | |||
| 253 | #define READ_AXIS(off, axis, invert) \ | ||
| 254 | v = invert ? (0xff - packet[off]) : packet[off]; \ | ||
| 255 | if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ | ||
| 256 | ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ | ||
| 257 | if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ | ||
| 258 | ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ | ||
| 259 | 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); \ | ||
| 260 | SDL_SendJoystickAxis( \ | ||
| 261 | timestamp, \ | ||
| 262 | joystick, \ | ||
| 263 | axis, axis_value); | ||
| 264 | READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX, 0) | ||
| 265 | READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY, 1) | ||
| 266 | READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTX, 0) | ||
| 267 | READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTY, 1) | ||
| 268 | READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0) | ||
| 269 | READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0) | ||
| 270 | #undef READ_AXIS | ||
| 271 | } | ||
| 272 | |||
| 273 | static void HIDAPI_DriverGameCube_HandleNintendoPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, Uint8 *packet, int size) | ||
| 274 | { | ||
| 275 | SDL_Joystick *joystick; | ||
| 276 | Uint8 *curSlot; | ||
| 277 | Uint8 i; | ||
| 278 | Sint16 axis_value; | ||
| 279 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 280 | |||
| 281 | if (size < 37 || packet[0] != 0x21) { | ||
| 282 | return; // Nothing to do right now...? | ||
| 283 | } | ||
| 284 | |||
| 285 | // Go through all 4 slots | ||
| 286 | curSlot = packet + 1; | ||
| 287 | for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) { | ||
| 288 | ctx->wireless[i] = (curSlot[0] & 0x20) != 0; | ||
| 289 | |||
| 290 | // Only allow rumble if the adapter's second USB cable is connected | ||
| 291 | ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i]; | ||
| 292 | |||
| 293 | if (curSlot[0] & 0x30) { // 0x10 - Wired, 0x20 - Wireless | ||
| 294 | if (ctx->joysticks[i] == 0) { | ||
| 295 | ResetAxisRange(ctx, i); | ||
| 296 | HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); | ||
| 297 | } | ||
| 298 | joystick = SDL_GetJoystickFromID(ctx->joysticks[i]); | ||
| 299 | |||
| 300 | // Hasn't been opened yet, skip | ||
| 301 | if (!joystick) { | ||
| 302 | continue; | ||
| 303 | } | ||
| 304 | } else { | ||
| 305 | if (ctx->joysticks[i] != 0) { | ||
| 306 | HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]); | ||
| 307 | ctx->joysticks[i] = 0; | ||
| 308 | } | ||
| 309 | continue; | ||
| 310 | } | ||
| 311 | |||
| 312 | #define READ_BUTTON(off, flag, button) \ | ||
| 313 | SDL_SendJoystickButton( \ | ||
| 314 | timestamp, \ | ||
| 315 | joystick, \ | ||
| 316 | button, \ | ||
| 317 | ((curSlot[off] & flag) != 0)); | ||
| 318 | READ_BUTTON(1, 0x01, 0) // A | ||
| 319 | READ_BUTTON(1, 0x02, 1) // B | ||
| 320 | READ_BUTTON(1, 0x04, 2) // X | ||
| 321 | READ_BUTTON(1, 0x08, 3) // Y | ||
| 322 | READ_BUTTON(1, 0x10, 4) // DPAD_LEFT | ||
| 323 | READ_BUTTON(1, 0x20, 5) // DPAD_RIGHT | ||
| 324 | READ_BUTTON(1, 0x40, 6) // DPAD_DOWN | ||
| 325 | READ_BUTTON(1, 0x80, 7) // DPAD_UP | ||
| 326 | READ_BUTTON(2, 0x01, 8) // START | ||
| 327 | READ_BUTTON(2, 0x02, 9) // RIGHTSHOULDER | ||
| 328 | /* These two buttons are for the bottoms of the analog triggers. | ||
| 329 | * More than likely, you're going to want to read the axes instead! | ||
| 330 | * -flibit | ||
| 331 | */ | ||
| 332 | READ_BUTTON(2, 0x04, 10) // TRIGGERRIGHT | ||
| 333 | READ_BUTTON(2, 0x08, 11) // TRIGGERLEFT | ||
| 334 | #undef READ_BUTTON | ||
| 335 | |||
| 336 | #define READ_AXIS(off, axis) \ | ||
| 337 | if (curSlot[off] < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ | ||
| 338 | ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = curSlot[off]; \ | ||
| 339 | if (curSlot[off] > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ | ||
| 340 | ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = curSlot[off]; \ | ||
| 341 | 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); \ | ||
| 342 | SDL_SendJoystickAxis( \ | ||
| 343 | timestamp, \ | ||
| 344 | joystick, \ | ||
| 345 | axis, axis_value); | ||
| 346 | READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX) | ||
| 347 | READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY) | ||
| 348 | READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTX) | ||
| 349 | READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTY) | ||
| 350 | READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | ||
| 351 | READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) | ||
| 352 | #undef READ_AXIS | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 357 | { | ||
| 358 | SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; | ||
| 359 | Uint8 packet[USB_PACKET_LENGTH]; | ||
| 360 | int size; | ||
| 361 | |||
| 362 | // Read input packet | ||
| 363 | while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) { | ||
| 364 | #ifdef DEBUG_GAMECUBE_PROTOCOL | ||
| 365 | HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size); | ||
| 366 | #endif | ||
| 367 | if (ctx->pc_mode) { | ||
| 368 | HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, size); | ||
| 369 | } else { | ||
| 370 | HIDAPI_DriverGameCube_HandleNintendoPacket(device, ctx, packet, size); | ||
| 371 | } | ||
| 372 | } | ||
| 373 | |||
| 374 | // Write rumble packet | ||
| 375 | if (ctx->rumbleUpdate) { | ||
| 376 | SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble)); | ||
| 377 | ctx->rumbleUpdate = false; | ||
| 378 | } | ||
| 379 | |||
| 380 | // If we got here, nothing bad happened! | ||
| 381 | return true; | ||
| 382 | } | ||
| 383 | |||
| 384 | static bool HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 385 | { | ||
| 386 | SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; | ||
| 387 | Uint8 i; | ||
| 388 | |||
| 389 | SDL_AssertJoysticksLocked(); | ||
| 390 | |||
| 391 | for (i = 0; i < MAX_CONTROLLERS; i += 1) { | ||
| 392 | if (joystick->instance_id == ctx->joysticks[i]) { | ||
| 393 | joystick->nbuttons = 12; | ||
| 394 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 395 | if (ctx->wireless[i]) { | ||
| 396 | joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; | ||
| 397 | } else { | ||
| 398 | joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; | ||
| 399 | } | ||
| 400 | return true; | ||
| 401 | } | ||
| 402 | } | ||
| 403 | return false; // Should never get here! | ||
| 404 | } | ||
| 405 | |||
| 406 | static bool HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 407 | { | ||
| 408 | SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; | ||
| 409 | Uint8 i, val; | ||
| 410 | |||
| 411 | SDL_AssertJoysticksLocked(); | ||
| 412 | |||
| 413 | if (ctx->pc_mode) { | ||
| 414 | return SDL_Unsupported(); | ||
| 415 | } | ||
| 416 | |||
| 417 | for (i = 0; i < MAX_CONTROLLERS; i += 1) { | ||
| 418 | if (joystick->instance_id == ctx->joysticks[i]) { | ||
| 419 | if (ctx->wireless[i]) { | ||
| 420 | return SDL_SetError("Nintendo GameCube WaveBird controllers do not support rumble"); | ||
| 421 | } | ||
| 422 | if (!ctx->rumbleAllowed[i]) { | ||
| 423 | return SDL_SetError("Second USB cable for WUP-028 not connected"); | ||
| 424 | } | ||
| 425 | if (ctx->useRumbleBrake) { | ||
| 426 | if (low_frequency_rumble == 0 && high_frequency_rumble > 0) { | ||
| 427 | val = 0; // if only low is 0 we want to do a regular stop | ||
| 428 | } else if (low_frequency_rumble == 0 && high_frequency_rumble == 0) { | ||
| 429 | val = 2; // if both frequencies are 0 we want to do a hard stop | ||
| 430 | } else { | ||
| 431 | val = 1; // normal rumble | ||
| 432 | } | ||
| 433 | } else { | ||
| 434 | val = (low_frequency_rumble > 0 || high_frequency_rumble > 0); | ||
| 435 | } | ||
| 436 | if (val != ctx->rumble[i + 1]) { | ||
| 437 | ctx->rumble[i + 1] = val; | ||
| 438 | ctx->rumbleUpdate = true; | ||
| 439 | } | ||
| 440 | return true; | ||
| 441 | } | ||
| 442 | } | ||
| 443 | |||
| 444 | // Should never get here! | ||
| 445 | return SDL_SetError("Couldn't find joystick"); | ||
| 446 | } | ||
| 447 | |||
| 448 | static bool HIDAPI_DriverGameCube_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 449 | { | ||
| 450 | return SDL_Unsupported(); | ||
| 451 | } | ||
| 452 | |||
| 453 | static Uint32 HIDAPI_DriverGameCube_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 454 | { | ||
| 455 | SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; | ||
| 456 | Uint32 result = 0; | ||
| 457 | |||
| 458 | SDL_AssertJoysticksLocked(); | ||
| 459 | |||
| 460 | if (!ctx->pc_mode) { | ||
| 461 | Uint8 i; | ||
| 462 | |||
| 463 | for (i = 0; i < MAX_CONTROLLERS; i += 1) { | ||
| 464 | if (joystick->instance_id == ctx->joysticks[i]) { | ||
| 465 | if (!ctx->wireless[i] && ctx->rumbleAllowed[i]) { | ||
| 466 | result |= SDL_JOYSTICK_CAP_RUMBLE; | ||
| 467 | break; | ||
| 468 | } | ||
| 469 | } | ||
| 470 | } | ||
| 471 | } | ||
| 472 | |||
| 473 | return result; | ||
| 474 | } | ||
| 475 | |||
| 476 | static bool HIDAPI_DriverGameCube_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 477 | { | ||
| 478 | return SDL_Unsupported(); | ||
| 479 | } | ||
| 480 | |||
| 481 | static bool HIDAPI_DriverGameCube_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 482 | { | ||
| 483 | return SDL_Unsupported(); | ||
| 484 | } | ||
| 485 | |||
| 486 | static bool HIDAPI_DriverGameCube_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 487 | { | ||
| 488 | return SDL_Unsupported(); | ||
| 489 | } | ||
| 490 | |||
| 491 | static void HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 492 | { | ||
| 493 | SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; | ||
| 494 | |||
| 495 | // Stop rumble activity | ||
| 496 | if (ctx->rumbleUpdate) { | ||
| 497 | SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble)); | ||
| 498 | ctx->rumbleUpdate = false; | ||
| 499 | } | ||
| 500 | } | ||
| 501 | |||
| 502 | static void HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 503 | { | ||
| 504 | SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context; | ||
| 505 | |||
| 506 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE, | ||
| 507 | SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx); | ||
| 508 | } | ||
| 509 | |||
| 510 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube = { | ||
| 511 | SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, | ||
| 512 | true, | ||
| 513 | HIDAPI_DriverGameCube_RegisterHints, | ||
| 514 | HIDAPI_DriverGameCube_UnregisterHints, | ||
| 515 | HIDAPI_DriverGameCube_IsEnabled, | ||
| 516 | HIDAPI_DriverGameCube_IsSupportedDevice, | ||
| 517 | HIDAPI_DriverGameCube_InitDevice, | ||
| 518 | HIDAPI_DriverGameCube_GetDevicePlayerIndex, | ||
| 519 | HIDAPI_DriverGameCube_SetDevicePlayerIndex, | ||
| 520 | HIDAPI_DriverGameCube_UpdateDevice, | ||
| 521 | HIDAPI_DriverGameCube_OpenJoystick, | ||
| 522 | HIDAPI_DriverGameCube_RumbleJoystick, | ||
| 523 | HIDAPI_DriverGameCube_RumbleJoystickTriggers, | ||
| 524 | HIDAPI_DriverGameCube_GetJoystickCapabilities, | ||
| 525 | HIDAPI_DriverGameCube_SetJoystickLED, | ||
| 526 | HIDAPI_DriverGameCube_SendJoystickEffect, | ||
| 527 | HIDAPI_DriverGameCube_SetJoystickSensorsEnabled, | ||
| 528 | HIDAPI_DriverGameCube_CloseJoystick, | ||
| 529 | HIDAPI_DriverGameCube_FreeDevice, | ||
| 530 | }; | ||
| 531 | |||
| 532 | #endif // SDL_JOYSTICK_HIDAPI_GAMECUBE | ||
| 533 | |||
| 534 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../SDL_sysjoystick.h" | ||
| 26 | #include "SDL_hidapijoystick_c.h" | ||
| 27 | #include "SDL_hidapi_rumble.h" | ||
| 28 | |||
| 29 | #ifdef SDL_JOYSTICK_HIDAPI_LUNA | ||
| 30 | |||
| 31 | // Define this if you want to log all packets from the controller | ||
| 32 | // #define DEBUG_LUNA_PROTOCOL | ||
| 33 | |||
| 34 | // Sending rumble on macOS blocks for a long time and eventually fails | ||
| 35 | #ifndef SDL_PLATFORM_MACOS | ||
| 36 | #define ENABLE_LUNA_BLUETOOTH_RUMBLE | ||
| 37 | #endif | ||
| 38 | |||
| 39 | enum | ||
| 40 | { | ||
| 41 | SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE = 11, | ||
| 42 | SDL_GAMEPAD_NUM_LUNA_BUTTONS, | ||
| 43 | }; | ||
| 44 | |||
| 45 | typedef struct | ||
| 46 | { | ||
| 47 | Uint8 last_state[USB_PACKET_LENGTH]; | ||
| 48 | } SDL_DriverLuna_Context; | ||
| 49 | |||
| 50 | static void HIDAPI_DriverLuna_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 51 | { | ||
| 52 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata); | ||
| 53 | } | ||
| 54 | |||
| 55 | static void HIDAPI_DriverLuna_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 56 | { | ||
| 57 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata); | ||
| 58 | } | ||
| 59 | |||
| 60 | static bool HIDAPI_DriverLuna_IsEnabled(void) | ||
| 61 | { | ||
| 62 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LUNA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); | ||
| 63 | } | ||
| 64 | |||
| 65 | 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) | ||
| 66 | { | ||
| 67 | return SDL_IsJoystickAmazonLunaController(vendor_id, product_id); | ||
| 68 | } | ||
| 69 | |||
| 70 | static bool HIDAPI_DriverLuna_InitDevice(SDL_HIDAPI_Device *device) | ||
| 71 | { | ||
| 72 | SDL_DriverLuna_Context *ctx; | ||
| 73 | |||
| 74 | ctx = (SDL_DriverLuna_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 75 | if (!ctx) { | ||
| 76 | return false; | ||
| 77 | } | ||
| 78 | device->context = ctx; | ||
| 79 | |||
| 80 | HIDAPI_SetDeviceName(device, "Amazon Luna Controller"); | ||
| 81 | |||
| 82 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 83 | } | ||
| 84 | |||
| 85 | static int HIDAPI_DriverLuna_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 86 | { | ||
| 87 | return -1; | ||
| 88 | } | ||
| 89 | |||
| 90 | static void HIDAPI_DriverLuna_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 91 | { | ||
| 92 | } | ||
| 93 | |||
| 94 | static bool HIDAPI_DriverLuna_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 95 | { | ||
| 96 | SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context; | ||
| 97 | |||
| 98 | SDL_AssertJoysticksLocked(); | ||
| 99 | |||
| 100 | SDL_zeroa(ctx->last_state); | ||
| 101 | |||
| 102 | // Initialize the joystick capabilities | ||
| 103 | joystick->nbuttons = SDL_GAMEPAD_NUM_LUNA_BUTTONS; | ||
| 104 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 105 | joystick->nhats = 1; | ||
| 106 | |||
| 107 | return true; | ||
| 108 | } | ||
| 109 | |||
| 110 | static bool HIDAPI_DriverLuna_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 111 | { | ||
| 112 | #ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE | ||
| 113 | if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) { | ||
| 114 | // Same packet as on Xbox One controllers connected via Bluetooth | ||
| 115 | Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB }; | ||
| 116 | |||
| 117 | // Magnitude is 1..100 so scale the 16-bit input here | ||
| 118 | rumble_packet[4] = (Uint8)(low_frequency_rumble / 655); | ||
| 119 | rumble_packet[5] = (Uint8)(high_frequency_rumble / 655); | ||
| 120 | |||
| 121 | if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { | ||
| 122 | return SDL_SetError("Couldn't send rumble packet"); | ||
| 123 | } | ||
| 124 | |||
| 125 | return true; | ||
| 126 | } | ||
| 127 | #endif // ENABLE_LUNA_BLUETOOTH_RUMBLE | ||
| 128 | |||
| 129 | // There is currently no rumble packet over USB | ||
| 130 | return SDL_Unsupported(); | ||
| 131 | } | ||
| 132 | |||
| 133 | static bool HIDAPI_DriverLuna_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 134 | { | ||
| 135 | return SDL_Unsupported(); | ||
| 136 | } | ||
| 137 | |||
| 138 | static Uint32 HIDAPI_DriverLuna_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 139 | { | ||
| 140 | Uint32 result = 0; | ||
| 141 | |||
| 142 | #ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE | ||
| 143 | if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) { | ||
| 144 | result |= SDL_JOYSTICK_CAP_RUMBLE; | ||
| 145 | } | ||
| 146 | #endif | ||
| 147 | |||
| 148 | return result; | ||
| 149 | } | ||
| 150 | |||
| 151 | static bool HIDAPI_DriverLuna_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 152 | { | ||
| 153 | return SDL_Unsupported(); | ||
| 154 | } | ||
| 155 | |||
| 156 | static bool HIDAPI_DriverLuna_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 157 | { | ||
| 158 | return SDL_Unsupported(); | ||
| 159 | } | ||
| 160 | |||
| 161 | static bool HIDAPI_DriverLuna_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 162 | { | ||
| 163 | return SDL_Unsupported(); | ||
| 164 | } | ||
| 165 | |||
| 166 | static void HIDAPI_DriverLuna_HandleUSBStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size) | ||
| 167 | { | ||
| 168 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 169 | |||
| 170 | if (ctx->last_state[1] != data[1]) { | ||
| 171 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[1] & 0x01) != 0)); | ||
| 172 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[1] & 0x02) != 0)); | ||
| 173 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[1] & 0x04) != 0)); | ||
| 174 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[1] & 0x08) != 0)); | ||
| 175 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0)); | ||
| 176 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0)); | ||
| 177 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x40) != 0)); | ||
| 178 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x80) != 0)); | ||
| 179 | } | ||
| 180 | if (ctx->last_state[2] != data[2]) { | ||
| 181 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x01) != 0)); | ||
| 182 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, ((data[2] & 0x02) != 0)); | ||
| 183 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x04) != 0)); | ||
| 184 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x08) != 0)); | ||
| 185 | } | ||
| 186 | |||
| 187 | if (ctx->last_state[3] != data[3]) { | ||
| 188 | Uint8 hat; | ||
| 189 | |||
| 190 | switch (data[3] & 0x0f) { | ||
| 191 | case 0: | ||
| 192 | hat = SDL_HAT_UP; | ||
| 193 | break; | ||
| 194 | case 1: | ||
| 195 | hat = SDL_HAT_RIGHTUP; | ||
| 196 | break; | ||
| 197 | case 2: | ||
| 198 | hat = SDL_HAT_RIGHT; | ||
| 199 | break; | ||
| 200 | case 3: | ||
| 201 | hat = SDL_HAT_RIGHTDOWN; | ||
| 202 | break; | ||
| 203 | case 4: | ||
| 204 | hat = SDL_HAT_DOWN; | ||
| 205 | break; | ||
| 206 | case 5: | ||
| 207 | hat = SDL_HAT_LEFTDOWN; | ||
| 208 | break; | ||
| 209 | case 6: | ||
| 210 | hat = SDL_HAT_LEFT; | ||
| 211 | break; | ||
| 212 | case 7: | ||
| 213 | hat = SDL_HAT_LEFTUP; | ||
| 214 | break; | ||
| 215 | default: | ||
| 216 | hat = SDL_HAT_CENTERED; | ||
| 217 | break; | ||
| 218 | } | ||
| 219 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 220 | } | ||
| 221 | |||
| 222 | #define READ_STICK_AXIS(offset) \ | ||
| 223 | (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16)) | ||
| 224 | { | ||
| 225 | Sint16 axis = READ_STICK_AXIS(4); | ||
| 226 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 227 | axis = READ_STICK_AXIS(5); | ||
| 228 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 229 | axis = READ_STICK_AXIS(6); | ||
| 230 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 231 | axis = READ_STICK_AXIS(7); | ||
| 232 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 233 | } | ||
| 234 | #undef READ_STICK_AXIS | ||
| 235 | |||
| 236 | #define READ_TRIGGER_AXIS(offset) \ | ||
| 237 | (Sint16) HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16) | ||
| 238 | { | ||
| 239 | Sint16 axis = READ_TRIGGER_AXIS(8); | ||
| 240 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 241 | axis = READ_TRIGGER_AXIS(9); | ||
| 242 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 243 | } | ||
| 244 | #undef READ_TRIGGER_AXIS | ||
| 245 | |||
| 246 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 247 | } | ||
| 248 | |||
| 249 | static void HIDAPI_DriverLuna_HandleBluetoothStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size) | ||
| 250 | { | ||
| 251 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 252 | |||
| 253 | if (size >= 2 && data[0] == 0x02) { | ||
| 254 | // Home button has dedicated report | ||
| 255 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x1) != 0)); | ||
| 256 | return; | ||
| 257 | } | ||
| 258 | |||
| 259 | if (size >= 2 && data[0] == 0x04) { | ||
| 260 | // Battery level report | ||
| 261 | int percent = (int)SDL_roundf((data[1] / 255.0f) * 100.0f); | ||
| 262 | SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent); | ||
| 263 | return; | ||
| 264 | } | ||
| 265 | |||
| 266 | if (size < 17 || data[0] != 0x01) { | ||
| 267 | // We don't know how to handle this report | ||
| 268 | return; | ||
| 269 | } | ||
| 270 | |||
| 271 | if (ctx->last_state[13] != data[13]) { | ||
| 272 | Uint8 hat; | ||
| 273 | |||
| 274 | switch (data[13] & 0x0f) { | ||
| 275 | case 1: | ||
| 276 | hat = SDL_HAT_UP; | ||
| 277 | break; | ||
| 278 | case 2: | ||
| 279 | hat = SDL_HAT_RIGHTUP; | ||
| 280 | break; | ||
| 281 | case 3: | ||
| 282 | hat = SDL_HAT_RIGHT; | ||
| 283 | break; | ||
| 284 | case 4: | ||
| 285 | hat = SDL_HAT_RIGHTDOWN; | ||
| 286 | break; | ||
| 287 | case 5: | ||
| 288 | hat = SDL_HAT_DOWN; | ||
| 289 | break; | ||
| 290 | case 6: | ||
| 291 | hat = SDL_HAT_LEFTDOWN; | ||
| 292 | break; | ||
| 293 | case 7: | ||
| 294 | hat = SDL_HAT_LEFT; | ||
| 295 | break; | ||
| 296 | case 8: | ||
| 297 | hat = SDL_HAT_LEFTUP; | ||
| 298 | break; | ||
| 299 | default: | ||
| 300 | hat = SDL_HAT_CENTERED; | ||
| 301 | break; | ||
| 302 | } | ||
| 303 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 304 | } | ||
| 305 | |||
| 306 | if (ctx->last_state[14] != data[14]) { | ||
| 307 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0)); | ||
| 308 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0)); | ||
| 309 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x08) != 0)); | ||
| 310 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x10) != 0)); | ||
| 311 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x40) != 0)); | ||
| 312 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x80) != 0)); | ||
| 313 | } | ||
| 314 | if (ctx->last_state[15] != data[15]) { | ||
| 315 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[15] & 0x08) != 0)); | ||
| 316 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x20) != 0)); | ||
| 317 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x40) != 0)); | ||
| 318 | } | ||
| 319 | if (ctx->last_state[16] != data[16]) { | ||
| 320 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[16] & 0x01) != 0)); | ||
| 321 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, ((data[16] & 0x02) != 0)); | ||
| 322 | } | ||
| 323 | |||
| 324 | #define READ_STICK_AXIS(offset) \ | ||
| 325 | (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16)) | ||
| 326 | { | ||
| 327 | Sint16 axis = READ_STICK_AXIS(2); | ||
| 328 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 329 | axis = READ_STICK_AXIS(4); | ||
| 330 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 331 | axis = READ_STICK_AXIS(6); | ||
| 332 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 333 | axis = READ_STICK_AXIS(8); | ||
| 334 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 335 | } | ||
| 336 | #undef READ_STICK_AXIS | ||
| 337 | |||
| 338 | #define READ_TRIGGER_AXIS(offset) \ | ||
| 339 | (Sint16) HIDAPI_RemapVal((float)((int)(((data[offset] | (data[offset + 1] << 8)) & 0x3ff) - 0x200)), 0x00 - 0x200, 0x3ff - 0x200, SDL_MIN_SINT16, SDL_MAX_SINT16) | ||
| 340 | { | ||
| 341 | Sint16 axis = READ_TRIGGER_AXIS(9); | ||
| 342 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 343 | axis = READ_TRIGGER_AXIS(11); | ||
| 344 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 345 | } | ||
| 346 | #undef READ_TRIGGER_AXIS | ||
| 347 | |||
| 348 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 349 | } | ||
| 350 | |||
| 351 | static bool HIDAPI_DriverLuna_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 352 | { | ||
| 353 | SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context; | ||
| 354 | SDL_Joystick *joystick = NULL; | ||
| 355 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 356 | int size = 0; | ||
| 357 | |||
| 358 | if (device->num_joysticks > 0) { | ||
| 359 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 360 | } else { | ||
| 361 | return false; | ||
| 362 | } | ||
| 363 | |||
| 364 | while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { | ||
| 365 | #ifdef DEBUG_LUNA_PROTOCOL | ||
| 366 | HIDAPI_DumpPacket("Amazon Luna packet: size = %d", data, size); | ||
| 367 | #endif | ||
| 368 | if (!joystick) { | ||
| 369 | continue; | ||
| 370 | } | ||
| 371 | |||
| 372 | switch (size) { | ||
| 373 | case 10: | ||
| 374 | HIDAPI_DriverLuna_HandleUSBStatePacket(joystick, ctx, data, size); | ||
| 375 | break; | ||
| 376 | default: | ||
| 377 | HIDAPI_DriverLuna_HandleBluetoothStatePacket(joystick, ctx, data, size); | ||
| 378 | break; | ||
| 379 | } | ||
| 380 | } | ||
| 381 | |||
| 382 | if (size < 0) { | ||
| 383 | // Read error, device is disconnected | ||
| 384 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 385 | } | ||
| 386 | return (size >= 0); | ||
| 387 | } | ||
| 388 | |||
| 389 | static void HIDAPI_DriverLuna_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 390 | { | ||
| 391 | } | ||
| 392 | |||
| 393 | static void HIDAPI_DriverLuna_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 394 | { | ||
| 395 | } | ||
| 396 | |||
| 397 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna = { | ||
| 398 | SDL_HINT_JOYSTICK_HIDAPI_LUNA, | ||
| 399 | true, | ||
| 400 | HIDAPI_DriverLuna_RegisterHints, | ||
| 401 | HIDAPI_DriverLuna_UnregisterHints, | ||
| 402 | HIDAPI_DriverLuna_IsEnabled, | ||
| 403 | HIDAPI_DriverLuna_IsSupportedDevice, | ||
| 404 | HIDAPI_DriverLuna_InitDevice, | ||
| 405 | HIDAPI_DriverLuna_GetDevicePlayerIndex, | ||
| 406 | HIDAPI_DriverLuna_SetDevicePlayerIndex, | ||
| 407 | HIDAPI_DriverLuna_UpdateDevice, | ||
| 408 | HIDAPI_DriverLuna_OpenJoystick, | ||
| 409 | HIDAPI_DriverLuna_RumbleJoystick, | ||
| 410 | HIDAPI_DriverLuna_RumbleJoystickTriggers, | ||
| 411 | HIDAPI_DriverLuna_GetJoystickCapabilities, | ||
| 412 | HIDAPI_DriverLuna_SetJoystickLED, | ||
| 413 | HIDAPI_DriverLuna_SendJoystickEffect, | ||
| 414 | HIDAPI_DriverLuna_SetJoystickSensorsEnabled, | ||
| 415 | HIDAPI_DriverLuna_CloseJoystick, | ||
| 416 | HIDAPI_DriverLuna_FreeDevice, | ||
| 417 | }; | ||
| 418 | |||
| 419 | #endif // SDL_JOYSTICK_HIDAPI_LUNA | ||
| 420 | |||
| 421 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | // These are values used in the controller type byte of the controller GUID | ||
| 23 | |||
| 24 | // These values come directly out of the hardware, so don't change them | ||
| 25 | typedef enum | ||
| 26 | { | ||
| 27 | k_eSwitchDeviceInfoControllerType_Unknown = 0, | ||
| 28 | k_eSwitchDeviceInfoControllerType_JoyConLeft = 1, | ||
| 29 | k_eSwitchDeviceInfoControllerType_JoyConRight = 2, | ||
| 30 | k_eSwitchDeviceInfoControllerType_ProController = 3, | ||
| 31 | k_eSwitchDeviceInfoControllerType_LicProController = 6, | ||
| 32 | k_eSwitchDeviceInfoControllerType_HVCLeft = 7, | ||
| 33 | k_eSwitchDeviceInfoControllerType_HVCRight = 8, | ||
| 34 | k_eSwitchDeviceInfoControllerType_NESLeft = 9, | ||
| 35 | k_eSwitchDeviceInfoControllerType_NESRight = 10, | ||
| 36 | k_eSwitchDeviceInfoControllerType_SNES = 11, | ||
| 37 | k_eSwitchDeviceInfoControllerType_N64 = 12, | ||
| 38 | k_eSwitchDeviceInfoControllerType_SEGA_Genesis = 13, | ||
| 39 | } ESwitchDeviceInfoControllerType; | ||
| 40 | |||
| 41 | // These values are used internally but can be updated as needed | ||
| 42 | typedef enum | ||
| 43 | { | ||
| 44 | k_eWiiExtensionControllerType_Unknown = 0, | ||
| 45 | k_eWiiExtensionControllerType_None = 128, | ||
| 46 | k_eWiiExtensionControllerType_Nunchuk = 129, | ||
| 47 | k_eWiiExtensionControllerType_Gamepad = 130, | ||
| 48 | k_eWiiExtensionControllerType_WiiUPro = 131, | ||
| 49 | } 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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../../SDL_hints_c.h" | ||
| 26 | #include "../SDL_sysjoystick.h" | ||
| 27 | #include "SDL_hidapijoystick_c.h" | ||
| 28 | #include "SDL_hidapi_rumble.h" | ||
| 29 | |||
| 30 | #ifdef SDL_JOYSTICK_HIDAPI_PS3 | ||
| 31 | |||
| 32 | // Define this if you want to log all packets from the controller | ||
| 33 | // #define DEBUG_PS3_PROTOCOL | ||
| 34 | |||
| 35 | #define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) | ||
| 36 | |||
| 37 | typedef enum | ||
| 38 | { | ||
| 39 | k_EPS3ReportIdState = 1, | ||
| 40 | k_EPS3ReportIdEffects = 1, | ||
| 41 | } EPS3ReportId; | ||
| 42 | |||
| 43 | typedef enum | ||
| 44 | { | ||
| 45 | k_EPS3SonySixaxisReportIdState = 0, | ||
| 46 | k_EPS3SonySixaxisReportIdEffects = 0, | ||
| 47 | } EPS3SonySixaxisReportId; | ||
| 48 | |||
| 49 | // Commands for Sony's sixaxis.sys Windows driver | ||
| 50 | // All commands must be sent using 49-byte buffer containing output report | ||
| 51 | // Byte 0 indicates reportId and must always be 0 | ||
| 52 | // Byte 1 indicates a command, supported values are specified below: | ||
| 53 | typedef enum | ||
| 54 | { | ||
| 55 | // This command allows to set user LEDs. | ||
| 56 | // Bytes 5,6.7.8 contain mode for corresponding LED: 0 - LED is off, 1 - LED in on, 2 - LED is flashing. | ||
| 57 | // Bytes 9-16 specify 64-bit LED flash period in 100 ns units if some LED is flashing, otherwise not used. | ||
| 58 | k_EPS3SixaxisCommandSetLEDs = 1, | ||
| 59 | |||
| 60 | // This command allows to set left and right motors. | ||
| 61 | // Byte 5 is right motor duration (0-255) and byte 6, if not zero, activates right motor. Zero value disables right motor. | ||
| 62 | // Byte 7 is left motor duration (0-255) and byte 8 is left motor amplitude (0-255). | ||
| 63 | k_EPS3SixaxisCommandSetMotors = 2, | ||
| 64 | |||
| 65 | // This command allows to block/unblock setting device LEDs by applications. | ||
| 66 | // Byte 5 is used as parameter - any non-zero value blocks LEDs, zero value will unblock LEDs. | ||
| 67 | k_EPS3SixaxisCommandBlockLEDs = 3, | ||
| 68 | |||
| 69 | // This command refreshes driver settings. No parameters used. | ||
| 70 | // When sixaxis driver loads it reads 'CurrentDriverSetting' binary value from 'HKLM\System\CurrentControlSet\Services\sixaxis\Parameters' registry key. | ||
| 71 | // 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. | ||
| 72 | k_EPS3SixaxisCommandRefreshDriverSetting = 9, | ||
| 73 | |||
| 74 | // This command clears current bluetooth pairing. No parameters used. | ||
| 75 | k_EPS3SixaxisCommandClearPairing = 10 | ||
| 76 | } EPS3SixaxisDriverCommands; | ||
| 77 | |||
| 78 | typedef struct | ||
| 79 | { | ||
| 80 | SDL_HIDAPI_Device *device; | ||
| 81 | SDL_Joystick *joystick; | ||
| 82 | bool is_shanwan; | ||
| 83 | bool has_analog_buttons; | ||
| 84 | bool report_sensors; | ||
| 85 | bool effects_updated; | ||
| 86 | int player_index; | ||
| 87 | Uint8 rumble_left; | ||
| 88 | Uint8 rumble_right; | ||
| 89 | Uint8 last_state[USB_PACKET_LENGTH]; | ||
| 90 | } SDL_DriverPS3_Context; | ||
| 91 | |||
| 92 | static bool HIDAPI_DriverPS3_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size); | ||
| 93 | |||
| 94 | static void HIDAPI_DriverPS3_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 95 | { | ||
| 96 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3, callback, userdata); | ||
| 97 | } | ||
| 98 | |||
| 99 | static void HIDAPI_DriverPS3_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 100 | { | ||
| 101 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3, callback, userdata); | ||
| 102 | } | ||
| 103 | |||
| 104 | static bool HIDAPI_DriverPS3_IsEnabled(void) | ||
| 105 | { | ||
| 106 | bool default_value; | ||
| 107 | |||
| 108 | #ifdef SDL_PLATFORM_MACOS | ||
| 109 | // This works well on macOS | ||
| 110 | default_value = true; | ||
| 111 | #elif defined(SDL_PLATFORM_WIN32) | ||
| 112 | /* For official Sony driver (sixaxis.sys) use SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER. | ||
| 113 | * | ||
| 114 | * See https://github.com/ViGEm/DsHidMini as an alternative driver | ||
| 115 | */ | ||
| 116 | default_value = false; | ||
| 117 | #elif defined(SDL_PLATFORM_LINUX) | ||
| 118 | /* Linux drivers do a better job of managing the transition between | ||
| 119 | * USB and Bluetooth. There are also some quirks in communicating | ||
| 120 | * with PS3 controllers that have been implemented in SDL's hidapi | ||
| 121 | * for libusb, but are not possible to support using hidraw if the | ||
| 122 | * kernel doesn't already know about them. | ||
| 123 | */ | ||
| 124 | default_value = false; | ||
| 125 | #else | ||
| 126 | // Untested, default off | ||
| 127 | default_value = false; | ||
| 128 | #endif | ||
| 129 | |||
| 130 | if (default_value) { | ||
| 131 | default_value = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT); | ||
| 132 | } | ||
| 133 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3, default_value); | ||
| 134 | } | ||
| 135 | |||
| 136 | 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) | ||
| 137 | { | ||
| 138 | if (vendor_id == USB_VENDOR_SONY && product_id == USB_PRODUCT_SONY_DS3) { | ||
| 139 | return true; | ||
| 140 | } | ||
| 141 | if (vendor_id == USB_VENDOR_SHANWAN && product_id == USB_PRODUCT_SHANWAN_DS3) { | ||
| 142 | return true; | ||
| 143 | } | ||
| 144 | return false; | ||
| 145 | } | ||
| 146 | |||
| 147 | static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length) | ||
| 148 | { | ||
| 149 | SDL_memset(report, 0, length); | ||
| 150 | report[0] = report_id; | ||
| 151 | return SDL_hid_get_feature_report(dev, report, length); | ||
| 152 | } | ||
| 153 | |||
| 154 | static int SendFeatureReport(SDL_hid_device *dev, Uint8 *report, size_t length) | ||
| 155 | { | ||
| 156 | return SDL_hid_send_feature_report(dev, report, length); | ||
| 157 | } | ||
| 158 | |||
| 159 | static bool HIDAPI_DriverPS3_InitDevice(SDL_HIDAPI_Device *device) | ||
| 160 | { | ||
| 161 | SDL_DriverPS3_Context *ctx; | ||
| 162 | bool is_shanwan = false; | ||
| 163 | |||
| 164 | if (device->vendor_id == USB_VENDOR_SONY && | ||
| 165 | SDL_strncasecmp(device->name, "ShanWan", 7) == 0) { | ||
| 166 | is_shanwan = true; | ||
| 167 | } | ||
| 168 | if (device->vendor_id == USB_VENDOR_SHANWAN || | ||
| 169 | device->vendor_id == USB_VENDOR_SHANWAN_ALT) { | ||
| 170 | is_shanwan = true; | ||
| 171 | } | ||
| 172 | |||
| 173 | ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 174 | if (!ctx) { | ||
| 175 | return false; | ||
| 176 | } | ||
| 177 | ctx->device = device; | ||
| 178 | ctx->is_shanwan = is_shanwan; | ||
| 179 | ctx->has_analog_buttons = true; | ||
| 180 | |||
| 181 | device->context = ctx; | ||
| 182 | |||
| 183 | // Set the controller into report mode over Bluetooth | ||
| 184 | if (device->is_bluetooth) { | ||
| 185 | Uint8 data[] = { 0xf4, 0x42, 0x03, 0x00, 0x00 }; | ||
| 186 | |||
| 187 | SendFeatureReport(device->dev, data, sizeof(data)); | ||
| 188 | } | ||
| 189 | |||
| 190 | // Set the controller into report mode over USB | ||
| 191 | if (!device->is_bluetooth) { | ||
| 192 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 193 | |||
| 194 | int size = ReadFeatureReport(device->dev, 0xf2, data, 17); | ||
| 195 | if (size < 0) { | ||
| 196 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, | ||
| 197 | "HIDAPI_DriverPS3_InitDevice(): Couldn't read feature report 0xf2"); | ||
| 198 | return false; | ||
| 199 | } | ||
| 200 | #ifdef DEBUG_PS3_PROTOCOL | ||
| 201 | HIDAPI_DumpPacket("PS3 0xF2 packet: size = %d", data, size); | ||
| 202 | #endif | ||
| 203 | size = ReadFeatureReport(device->dev, 0xf5, data, 8); | ||
| 204 | if (size < 0) { | ||
| 205 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, | ||
| 206 | "HIDAPI_DriverPS3_InitDevice(): Couldn't read feature report 0xf5"); | ||
| 207 | return false; | ||
| 208 | } | ||
| 209 | #ifdef DEBUG_PS3_PROTOCOL | ||
| 210 | HIDAPI_DumpPacket("PS3 0xF5 packet: size = %d", data, size); | ||
| 211 | #endif | ||
| 212 | if (!ctx->is_shanwan) { | ||
| 213 | // An output report could cause ShanWan controllers to rumble non-stop | ||
| 214 | SDL_hid_write(device->dev, data, 1); | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | device->type = SDL_GAMEPAD_TYPE_PS3; | ||
| 219 | HIDAPI_SetDeviceName(device, "PS3 Controller"); | ||
| 220 | |||
| 221 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 222 | } | ||
| 223 | |||
| 224 | static int HIDAPI_DriverPS3_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 225 | { | ||
| 226 | return -1; | ||
| 227 | } | ||
| 228 | |||
| 229 | static bool HIDAPI_DriverPS3_UpdateEffects(SDL_HIDAPI_Device *device) | ||
| 230 | { | ||
| 231 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 232 | |||
| 233 | Uint8 effects[] = { | ||
| 234 | 0x01, 0xff, 0x00, 0xff, 0x00, | ||
| 235 | 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| 236 | 0xff, 0x27, 0x10, 0x00, 0x32, | ||
| 237 | 0xff, 0x27, 0x10, 0x00, 0x32, | ||
| 238 | 0xff, 0x27, 0x10, 0x00, 0x32, | ||
| 239 | 0xff, 0x27, 0x10, 0x00, 0x32, | ||
| 240 | 0x00, 0x00, 0x00, 0x00, 0x00 | ||
| 241 | }; | ||
| 242 | |||
| 243 | effects[2] = ctx->rumble_right ? 1 : 0; | ||
| 244 | effects[4] = ctx->rumble_left; | ||
| 245 | |||
| 246 | effects[9] = (0x01 << (1 + (ctx->player_index % 4))); | ||
| 247 | |||
| 248 | return HIDAPI_DriverPS3_SendJoystickEffect(device, ctx->joystick, effects, sizeof(effects)); | ||
| 249 | } | ||
| 250 | |||
| 251 | static void HIDAPI_DriverPS3_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 252 | { | ||
| 253 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 254 | |||
| 255 | if (!ctx) { | ||
| 256 | return; | ||
| 257 | } | ||
| 258 | |||
| 259 | ctx->player_index = player_index; | ||
| 260 | |||
| 261 | // This will set the new LED state based on the new player index | ||
| 262 | HIDAPI_DriverPS3_UpdateEffects(device); | ||
| 263 | } | ||
| 264 | |||
| 265 | static bool HIDAPI_DriverPS3_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 266 | { | ||
| 267 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 268 | |||
| 269 | SDL_AssertJoysticksLocked(); | ||
| 270 | |||
| 271 | ctx->joystick = joystick; | ||
| 272 | ctx->effects_updated = false; | ||
| 273 | ctx->rumble_left = 0; | ||
| 274 | ctx->rumble_right = 0; | ||
| 275 | SDL_zeroa(ctx->last_state); | ||
| 276 | |||
| 277 | // Initialize player index (needed for setting LEDs) | ||
| 278 | ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); | ||
| 279 | |||
| 280 | // Initialize the joystick capabilities | ||
| 281 | joystick->nbuttons = 11; | ||
| 282 | joystick->naxes = 6; | ||
| 283 | if (ctx->has_analog_buttons) { | ||
| 284 | joystick->naxes += 10; | ||
| 285 | } | ||
| 286 | joystick->nhats = 1; | ||
| 287 | |||
| 288 | SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f); | ||
| 289 | |||
| 290 | return true; | ||
| 291 | } | ||
| 292 | |||
| 293 | static bool HIDAPI_DriverPS3_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 294 | { | ||
| 295 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 296 | |||
| 297 | ctx->rumble_left = (low_frequency_rumble >> 8); | ||
| 298 | ctx->rumble_right = (high_frequency_rumble >> 8); | ||
| 299 | |||
| 300 | return HIDAPI_DriverPS3_UpdateEffects(device); | ||
| 301 | } | ||
| 302 | |||
| 303 | static bool HIDAPI_DriverPS3_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 304 | { | ||
| 305 | return SDL_Unsupported(); | ||
| 306 | } | ||
| 307 | |||
| 308 | static Uint32 HIDAPI_DriverPS3_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 309 | { | ||
| 310 | return SDL_JOYSTICK_CAP_RUMBLE; | ||
| 311 | } | ||
| 312 | |||
| 313 | static bool HIDAPI_DriverPS3_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 314 | { | ||
| 315 | return SDL_Unsupported(); | ||
| 316 | } | ||
| 317 | |||
| 318 | static bool HIDAPI_DriverPS3_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) | ||
| 319 | { | ||
| 320 | Uint8 data[49]; | ||
| 321 | int report_size, offset; | ||
| 322 | |||
| 323 | SDL_zeroa(data); | ||
| 324 | |||
| 325 | data[0] = k_EPS3ReportIdEffects; | ||
| 326 | report_size = sizeof(data); | ||
| 327 | offset = 1; | ||
| 328 | SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size)); | ||
| 329 | |||
| 330 | if (SDL_HIDAPI_SendRumble(device, data, report_size) != report_size) { | ||
| 331 | return SDL_SetError("Couldn't send rumble packet"); | ||
| 332 | } | ||
| 333 | return true; | ||
| 334 | } | ||
| 335 | |||
| 336 | static bool HIDAPI_DriverPS3_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 337 | { | ||
| 338 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 339 | |||
| 340 | ctx->report_sensors = enabled; | ||
| 341 | |||
| 342 | return true; | ||
| 343 | } | ||
| 344 | |||
| 345 | static float HIDAPI_DriverPS3_ScaleAccel(Sint16 value) | ||
| 346 | { | ||
| 347 | // Accelerometer values are in big endian order | ||
| 348 | value = SDL_Swap16BE(value); | ||
| 349 | return ((float)(value - 511) / 113.0f) * SDL_STANDARD_GRAVITY; | ||
| 350 | } | ||
| 351 | |||
| 352 | static void HIDAPI_DriverPS3_HandleMiniStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size) | ||
| 353 | { | ||
| 354 | Sint16 axis; | ||
| 355 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 356 | |||
| 357 | if (ctx->last_state[4] != data[4]) { | ||
| 358 | Uint8 hat; | ||
| 359 | |||
| 360 | switch (data[4] & 0x0f) { | ||
| 361 | case 0: | ||
| 362 | hat = SDL_HAT_UP; | ||
| 363 | break; | ||
| 364 | case 1: | ||
| 365 | hat = SDL_HAT_RIGHTUP; | ||
| 366 | break; | ||
| 367 | case 2: | ||
| 368 | hat = SDL_HAT_RIGHT; | ||
| 369 | break; | ||
| 370 | case 3: | ||
| 371 | hat = SDL_HAT_RIGHTDOWN; | ||
| 372 | break; | ||
| 373 | case 4: | ||
| 374 | hat = SDL_HAT_DOWN; | ||
| 375 | break; | ||
| 376 | case 5: | ||
| 377 | hat = SDL_HAT_LEFTDOWN; | ||
| 378 | break; | ||
| 379 | case 6: | ||
| 380 | hat = SDL_HAT_LEFT; | ||
| 381 | break; | ||
| 382 | case 7: | ||
| 383 | hat = SDL_HAT_LEFTUP; | ||
| 384 | break; | ||
| 385 | default: | ||
| 386 | hat = SDL_HAT_CENTERED; | ||
| 387 | break; | ||
| 388 | } | ||
| 389 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 390 | |||
| 391 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[4] & 0x10) != 0)); | ||
| 392 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[4] & 0x20) != 0)); | ||
| 393 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[4] & 0x40) != 0)); | ||
| 394 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[4] & 0x80) != 0)); | ||
| 395 | } | ||
| 396 | |||
| 397 | if (ctx->last_state[5] != data[5]) { | ||
| 398 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[5] & 0x01) != 0)); | ||
| 399 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[5] & 0x02) != 0)); | ||
| 400 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[5] & 0x04) ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN); | ||
| 401 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[5] & 0x08) ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN); | ||
| 402 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[5] & 0x10) != 0)); | ||
| 403 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[5] & 0x20) != 0)); | ||
| 404 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[5] & 0x40) != 0)); | ||
| 405 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[5] & 0x80) != 0)); | ||
| 406 | } | ||
| 407 | |||
| 408 | axis = ((int)data[2] * 257) - 32768; | ||
| 409 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 410 | axis = ((int)data[3] * 257) - 32768; | ||
| 411 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 412 | axis = ((int)data[0] * 257) - 32768; | ||
| 413 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 414 | axis = ((int)data[1] * 257) - 32768; | ||
| 415 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 416 | |||
| 417 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 418 | } | ||
| 419 | |||
| 420 | static void HIDAPI_DriverPS3_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size) | ||
| 421 | { | ||
| 422 | Sint16 axis; | ||
| 423 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 424 | |||
| 425 | if (ctx->last_state[2] != data[2]) { | ||
| 426 | Uint8 hat = 0; | ||
| 427 | |||
| 428 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x01) != 0)); | ||
| 429 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x02) != 0)); | ||
| 430 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x04) != 0)); | ||
| 431 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x08) != 0)); | ||
| 432 | |||
| 433 | if (data[2] & 0x10) { | ||
| 434 | hat |= SDL_HAT_UP; | ||
| 435 | } | ||
| 436 | if (data[2] & 0x20) { | ||
| 437 | hat |= SDL_HAT_RIGHT; | ||
| 438 | } | ||
| 439 | if (data[2] & 0x40) { | ||
| 440 | hat |= SDL_HAT_DOWN; | ||
| 441 | } | ||
| 442 | if (data[2] & 0x80) { | ||
| 443 | hat |= SDL_HAT_LEFT; | ||
| 444 | } | ||
| 445 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 446 | } | ||
| 447 | |||
| 448 | if (ctx->last_state[3] != data[3]) { | ||
| 449 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x04) != 0)); | ||
| 450 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x08) != 0)); | ||
| 451 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x10) != 0)); | ||
| 452 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0)); | ||
| 453 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x40) != 0)); | ||
| 454 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x80) != 0)); | ||
| 455 | } | ||
| 456 | |||
| 457 | if (ctx->last_state[4] != data[4]) { | ||
| 458 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[4] & 0x01) != 0)); | ||
| 459 | } | ||
| 460 | |||
| 461 | axis = ((int)data[18] * 257) - 32768; | ||
| 462 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 463 | axis = ((int)data[19] * 257) - 32768; | ||
| 464 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 465 | axis = ((int)data[6] * 257) - 32768; | ||
| 466 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 467 | axis = ((int)data[7] * 257) - 32768; | ||
| 468 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 469 | axis = ((int)data[8] * 257) - 32768; | ||
| 470 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 471 | axis = ((int)data[9] * 257) - 32768; | ||
| 472 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 473 | |||
| 474 | // Buttons are mapped as axes in the order they appear in the button enumeration | ||
| 475 | if (ctx->has_analog_buttons) { | ||
| 476 | static int button_axis_offsets[] = { | ||
| 477 | 24, // SDL_GAMEPAD_BUTTON_SOUTH | ||
| 478 | 23, // SDL_GAMEPAD_BUTTON_EAST | ||
| 479 | 25, // SDL_GAMEPAD_BUTTON_WEST | ||
| 480 | 22, // SDL_GAMEPAD_BUTTON_NORTH | ||
| 481 | 0, // SDL_GAMEPAD_BUTTON_BACK | ||
| 482 | 0, // SDL_GAMEPAD_BUTTON_GUIDE | ||
| 483 | 0, // SDL_GAMEPAD_BUTTON_START | ||
| 484 | 0, // SDL_GAMEPAD_BUTTON_LEFT_STICK | ||
| 485 | 0, // SDL_GAMEPAD_BUTTON_RIGHT_STICK | ||
| 486 | 20, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER | ||
| 487 | 21, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER | ||
| 488 | 14, // SDL_GAMEPAD_BUTTON_DPAD_UP | ||
| 489 | 16, // SDL_GAMEPAD_BUTTON_DPAD_DOWN | ||
| 490 | 17, // SDL_GAMEPAD_BUTTON_DPAD_LEFT | ||
| 491 | 15, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT | ||
| 492 | }; | ||
| 493 | Uint8 i, axis_index = 6; | ||
| 494 | |||
| 495 | for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) { | ||
| 496 | int offset = button_axis_offsets[i]; | ||
| 497 | if (!offset) { | ||
| 498 | // This button doesn't report as an axis | ||
| 499 | continue; | ||
| 500 | } | ||
| 501 | |||
| 502 | axis = ((int)data[offset] * 257) - 32768; | ||
| 503 | SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis); | ||
| 504 | ++axis_index; | ||
| 505 | } | ||
| 506 | } | ||
| 507 | |||
| 508 | if (ctx->report_sensors) { | ||
| 509 | float sensor_data[3]; | ||
| 510 | |||
| 511 | sensor_data[0] = HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[41], data[42])); | ||
| 512 | sensor_data[1] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[45], data[46])); | ||
| 513 | sensor_data[2] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[43], data[44])); | ||
| 514 | SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, sensor_data, SDL_arraysize(sensor_data)); | ||
| 515 | } | ||
| 516 | |||
| 517 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 518 | } | ||
| 519 | |||
| 520 | static bool HIDAPI_DriverPS3_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 521 | { | ||
| 522 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 523 | SDL_Joystick *joystick = NULL; | ||
| 524 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 525 | int size; | ||
| 526 | |||
| 527 | if (device->num_joysticks > 0) { | ||
| 528 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 529 | } else { | ||
| 530 | return false; | ||
| 531 | } | ||
| 532 | |||
| 533 | while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { | ||
| 534 | #ifdef DEBUG_PS3_PROTOCOL | ||
| 535 | HIDAPI_DumpPacket("PS3 packet: size = %d", data, size); | ||
| 536 | #endif | ||
| 537 | if (!joystick) { | ||
| 538 | continue; | ||
| 539 | } | ||
| 540 | |||
| 541 | if (size == 7) { | ||
| 542 | // Seen on a ShanWan PS2 -> PS3 USB converter | ||
| 543 | HIDAPI_DriverPS3_HandleMiniStatePacket(joystick, ctx, data, size); | ||
| 544 | |||
| 545 | // Wait for the first report to set the LED state after the controller stops blinking | ||
| 546 | if (!ctx->effects_updated) { | ||
| 547 | HIDAPI_DriverPS3_UpdateEffects(device); | ||
| 548 | ctx->effects_updated = true; | ||
| 549 | } | ||
| 550 | continue; | ||
| 551 | } | ||
| 552 | |||
| 553 | switch (data[0]) { | ||
| 554 | case k_EPS3ReportIdState: | ||
| 555 | if (data[1] == 0xFF) { | ||
| 556 | // Invalid data packet, ignore | ||
| 557 | break; | ||
| 558 | } | ||
| 559 | HIDAPI_DriverPS3_HandleStatePacket(joystick, ctx, data, size); | ||
| 560 | |||
| 561 | // Wait for the first report to set the LED state after the controller stops blinking | ||
| 562 | if (!ctx->effects_updated) { | ||
| 563 | HIDAPI_DriverPS3_UpdateEffects(device); | ||
| 564 | ctx->effects_updated = true; | ||
| 565 | } | ||
| 566 | break; | ||
| 567 | default: | ||
| 568 | #ifdef DEBUG_JOYSTICK | ||
| 569 | SDL_Log("Unknown PS3 packet: 0x%.2x", data[0]); | ||
| 570 | #endif | ||
| 571 | break; | ||
| 572 | } | ||
| 573 | } | ||
| 574 | |||
| 575 | if (size < 0) { | ||
| 576 | // Read error, device is disconnected | ||
| 577 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 578 | } | ||
| 579 | return (size >= 0); | ||
| 580 | } | ||
| 581 | |||
| 582 | static void HIDAPI_DriverPS3_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 583 | { | ||
| 584 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 585 | |||
| 586 | ctx->joystick = NULL; | ||
| 587 | } | ||
| 588 | |||
| 589 | static void HIDAPI_DriverPS3_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 590 | { | ||
| 591 | } | ||
| 592 | |||
| 593 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3 = { | ||
| 594 | SDL_HINT_JOYSTICK_HIDAPI_PS3, | ||
| 595 | true, | ||
| 596 | HIDAPI_DriverPS3_RegisterHints, | ||
| 597 | HIDAPI_DriverPS3_UnregisterHints, | ||
| 598 | HIDAPI_DriverPS3_IsEnabled, | ||
| 599 | HIDAPI_DriverPS3_IsSupportedDevice, | ||
| 600 | HIDAPI_DriverPS3_InitDevice, | ||
| 601 | HIDAPI_DriverPS3_GetDevicePlayerIndex, | ||
| 602 | HIDAPI_DriverPS3_SetDevicePlayerIndex, | ||
| 603 | HIDAPI_DriverPS3_UpdateDevice, | ||
| 604 | HIDAPI_DriverPS3_OpenJoystick, | ||
| 605 | HIDAPI_DriverPS3_RumbleJoystick, | ||
| 606 | HIDAPI_DriverPS3_RumbleJoystickTriggers, | ||
| 607 | HIDAPI_DriverPS3_GetJoystickCapabilities, | ||
| 608 | HIDAPI_DriverPS3_SetJoystickLED, | ||
| 609 | HIDAPI_DriverPS3_SendJoystickEffect, | ||
| 610 | HIDAPI_DriverPS3_SetJoystickSensorsEnabled, | ||
| 611 | HIDAPI_DriverPS3_CloseJoystick, | ||
| 612 | HIDAPI_DriverPS3_FreeDevice, | ||
| 613 | }; | ||
| 614 | |||
| 615 | static bool HIDAPI_DriverPS3ThirdParty_IsEnabled(void) | ||
| 616 | { | ||
| 617 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3, | ||
| 618 | SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, | ||
| 619 | SDL_HIDAPI_DEFAULT)); | ||
| 620 | } | ||
| 621 | |||
| 622 | 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) | ||
| 623 | { | ||
| 624 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 625 | int size; | ||
| 626 | |||
| 627 | if (vendor_id == USB_VENDOR_LOGITECH && | ||
| 628 | product_id == USB_PRODUCT_LOGITECH_CHILLSTREAM) { | ||
| 629 | return true; | ||
| 630 | } | ||
| 631 | |||
| 632 | if ((type == SDL_GAMEPAD_TYPE_PS3 && vendor_id != USB_VENDOR_SONY) || | ||
| 633 | HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) { | ||
| 634 | if (device && device->dev) { | ||
| 635 | size = ReadFeatureReport(device->dev, 0x03, data, sizeof(data)); | ||
| 636 | if (size == 8 && data[2] == 0x26) { | ||
| 637 | // Supported third party controller | ||
| 638 | return true; | ||
| 639 | } else { | ||
| 640 | return false; | ||
| 641 | } | ||
| 642 | } else { | ||
| 643 | // Might be supported by this driver, enumerate and find out | ||
| 644 | return true; | ||
| 645 | } | ||
| 646 | } | ||
| 647 | return false; | ||
| 648 | } | ||
| 649 | |||
| 650 | static bool HIDAPI_DriverPS3ThirdParty_InitDevice(SDL_HIDAPI_Device *device) | ||
| 651 | { | ||
| 652 | SDL_DriverPS3_Context *ctx; | ||
| 653 | |||
| 654 | ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 655 | if (!ctx) { | ||
| 656 | return false; | ||
| 657 | } | ||
| 658 | ctx->device = device; | ||
| 659 | if (device->vendor_id == USB_VENDOR_SWITCH && device->product_id == USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER) { | ||
| 660 | ctx->has_analog_buttons = false; | ||
| 661 | } else { | ||
| 662 | ctx->has_analog_buttons = true; | ||
| 663 | } | ||
| 664 | |||
| 665 | device->context = ctx; | ||
| 666 | |||
| 667 | device->type = SDL_GAMEPAD_TYPE_PS3; | ||
| 668 | |||
| 669 | if (device->vendor_id == USB_VENDOR_LOGITECH && | ||
| 670 | device->product_id == USB_PRODUCT_LOGITECH_CHILLSTREAM) { | ||
| 671 | HIDAPI_SetDeviceName(device, "Logitech ChillStream"); | ||
| 672 | } | ||
| 673 | |||
| 674 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 675 | } | ||
| 676 | |||
| 677 | static int HIDAPI_DriverPS3ThirdParty_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 678 | { | ||
| 679 | return -1; | ||
| 680 | } | ||
| 681 | |||
| 682 | static void HIDAPI_DriverPS3ThirdParty_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 683 | { | ||
| 684 | } | ||
| 685 | |||
| 686 | static bool HIDAPI_DriverPS3ThirdParty_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 687 | { | ||
| 688 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 689 | |||
| 690 | SDL_AssertJoysticksLocked(); | ||
| 691 | |||
| 692 | ctx->joystick = joystick; | ||
| 693 | SDL_zeroa(ctx->last_state); | ||
| 694 | |||
| 695 | // Initialize the joystick capabilities | ||
| 696 | joystick->nbuttons = 11; | ||
| 697 | joystick->naxes = 6; | ||
| 698 | if (ctx->has_analog_buttons) { | ||
| 699 | joystick->naxes += 10; | ||
| 700 | } | ||
| 701 | joystick->nhats = 1; | ||
| 702 | |||
| 703 | if (device->vendor_id == USB_VENDOR_SWITCH && device->product_id == USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER) { | ||
| 704 | // This is a wireless controller using a USB dongle | ||
| 705 | joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; | ||
| 706 | } | ||
| 707 | |||
| 708 | return true; | ||
| 709 | } | ||
| 710 | |||
| 711 | static bool HIDAPI_DriverPS3ThirdParty_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 712 | { | ||
| 713 | return SDL_Unsupported(); | ||
| 714 | } | ||
| 715 | |||
| 716 | static bool HIDAPI_DriverPS3ThirdParty_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 717 | { | ||
| 718 | return SDL_Unsupported(); | ||
| 719 | } | ||
| 720 | |||
| 721 | static Uint32 HIDAPI_DriverPS3ThirdParty_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 722 | { | ||
| 723 | return 0; | ||
| 724 | } | ||
| 725 | |||
| 726 | static bool HIDAPI_DriverPS3ThirdParty_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 727 | { | ||
| 728 | return SDL_Unsupported(); | ||
| 729 | } | ||
| 730 | |||
| 731 | static bool HIDAPI_DriverPS3ThirdParty_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) | ||
| 732 | { | ||
| 733 | return SDL_Unsupported(); | ||
| 734 | } | ||
| 735 | |||
| 736 | static bool HIDAPI_DriverPS3ThirdParty_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 737 | { | ||
| 738 | return SDL_Unsupported(); | ||
| 739 | } | ||
| 740 | |||
| 741 | static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket18(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size) | ||
| 742 | { | ||
| 743 | Sint16 axis; | ||
| 744 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 745 | |||
| 746 | if (ctx->last_state[0] != data[0]) { | ||
| 747 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x01) != 0)); | ||
| 748 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x02) != 0)); | ||
| 749 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x04) != 0)); | ||
| 750 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x08) != 0)); | ||
| 751 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x10) != 0)); | ||
| 752 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x20) != 0)); | ||
| 753 | } | ||
| 754 | |||
| 755 | if (ctx->last_state[1] != data[1]) { | ||
| 756 | Uint8 hat; | ||
| 757 | |||
| 758 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x01) != 0)); | ||
| 759 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x02) != 0)); | ||
| 760 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x04) != 0)); | ||
| 761 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x08) != 0)); | ||
| 762 | |||
| 763 | switch (data[1] >> 4) { | ||
| 764 | case 0: | ||
| 765 | hat = SDL_HAT_UP; | ||
| 766 | break; | ||
| 767 | case 1: | ||
| 768 | hat = SDL_HAT_RIGHTUP; | ||
| 769 | break; | ||
| 770 | case 2: | ||
| 771 | hat = SDL_HAT_RIGHT; | ||
| 772 | break; | ||
| 773 | case 3: | ||
| 774 | hat = SDL_HAT_RIGHTDOWN; | ||
| 775 | break; | ||
| 776 | case 4: | ||
| 777 | hat = SDL_HAT_DOWN; | ||
| 778 | break; | ||
| 779 | case 5: | ||
| 780 | hat = SDL_HAT_LEFTDOWN; | ||
| 781 | break; | ||
| 782 | case 6: | ||
| 783 | hat = SDL_HAT_LEFT; | ||
| 784 | break; | ||
| 785 | case 7: | ||
| 786 | hat = SDL_HAT_LEFTUP; | ||
| 787 | break; | ||
| 788 | default: | ||
| 789 | hat = SDL_HAT_CENTERED; | ||
| 790 | break; | ||
| 791 | } | ||
| 792 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 793 | } | ||
| 794 | |||
| 795 | axis = ((int)data[16] * 257) - 32768; | ||
| 796 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 797 | axis = ((int)data[17] * 257) - 32768; | ||
| 798 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 799 | axis = ((int)data[2] * 257) - 32768; | ||
| 800 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 801 | axis = ((int)data[3] * 257) - 32768; | ||
| 802 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 803 | axis = ((int)data[4] * 257) - 32768; | ||
| 804 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 805 | axis = ((int)data[5] * 257) - 32768; | ||
| 806 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 807 | |||
| 808 | // Buttons are mapped as axes in the order they appear in the button enumeration | ||
| 809 | if (ctx->has_analog_buttons) { | ||
| 810 | static int button_axis_offsets[] = { | ||
| 811 | 12, // SDL_GAMEPAD_BUTTON_SOUTH | ||
| 812 | 11, // SDL_GAMEPAD_BUTTON_EAST | ||
| 813 | 13, // SDL_GAMEPAD_BUTTON_WEST | ||
| 814 | 10, // SDL_GAMEPAD_BUTTON_NORTH | ||
| 815 | 0, // SDL_GAMEPAD_BUTTON_BACK | ||
| 816 | 0, // SDL_GAMEPAD_BUTTON_GUIDE | ||
| 817 | 0, // SDL_GAMEPAD_BUTTON_START | ||
| 818 | 0, // SDL_GAMEPAD_BUTTON_LEFT_STICK | ||
| 819 | 0, // SDL_GAMEPAD_BUTTON_RIGHT_STICK | ||
| 820 | 14, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER | ||
| 821 | 15, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER | ||
| 822 | 8, // SDL_GAMEPAD_BUTTON_DPAD_UP | ||
| 823 | 9, // SDL_GAMEPAD_BUTTON_DPAD_DOWN | ||
| 824 | 7, // SDL_GAMEPAD_BUTTON_DPAD_LEFT | ||
| 825 | 6, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT | ||
| 826 | }; | ||
| 827 | Uint8 i, axis_index = 6; | ||
| 828 | |||
| 829 | for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) { | ||
| 830 | int offset = button_axis_offsets[i]; | ||
| 831 | if (!offset) { | ||
| 832 | // This button doesn't report as an axis | ||
| 833 | continue; | ||
| 834 | } | ||
| 835 | |||
| 836 | axis = ((int)data[offset] * 257) - 32768; | ||
| 837 | SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis); | ||
| 838 | ++axis_index; | ||
| 839 | } | ||
| 840 | } | ||
| 841 | |||
| 842 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 843 | } | ||
| 844 | |||
| 845 | static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket19(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size) | ||
| 846 | { | ||
| 847 | Sint16 axis; | ||
| 848 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 849 | |||
| 850 | if (ctx->last_state[0] != data[0]) { | ||
| 851 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x01) != 0)); | ||
| 852 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x02) != 0)); | ||
| 853 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x04) != 0)); | ||
| 854 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x08) != 0)); | ||
| 855 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x10) != 0)); | ||
| 856 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x20) != 0)); | ||
| 857 | } | ||
| 858 | |||
| 859 | if (ctx->last_state[1] != data[1]) { | ||
| 860 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x01) != 0)); | ||
| 861 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x02) != 0)); | ||
| 862 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x04) != 0)); | ||
| 863 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x08) != 0)); | ||
| 864 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0)); | ||
| 865 | } | ||
| 866 | |||
| 867 | if (ctx->device->vendor_id == USB_VENDOR_SAITEK && ctx->device->product_id == USB_PRODUCT_SAITEK_CYBORG_V3) { | ||
| 868 | // Cyborg V.3 Rumble Pad doesn't set the dpad bits as expected, so use the axes instead | ||
| 869 | Uint8 hat = 0; | ||
| 870 | |||
| 871 | if (data[7]) { | ||
| 872 | hat |= SDL_HAT_RIGHT; | ||
| 873 | } | ||
| 874 | if (data[8]) { | ||
| 875 | hat |= SDL_HAT_LEFT; | ||
| 876 | } | ||
| 877 | if (data[9]) { | ||
| 878 | hat |= SDL_HAT_UP; | ||
| 879 | } | ||
| 880 | if (data[10]) { | ||
| 881 | hat |= SDL_HAT_DOWN; | ||
| 882 | } | ||
| 883 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 884 | } else { | ||
| 885 | if (ctx->last_state[2] != data[2]) { | ||
| 886 | Uint8 hat; | ||
| 887 | |||
| 888 | switch (data[2] & 0x0f) { | ||
| 889 | case 0: | ||
| 890 | hat = SDL_HAT_UP; | ||
| 891 | break; | ||
| 892 | case 1: | ||
| 893 | hat = SDL_HAT_RIGHTUP; | ||
| 894 | break; | ||
| 895 | case 2: | ||
| 896 | hat = SDL_HAT_RIGHT; | ||
| 897 | break; | ||
| 898 | case 3: | ||
| 899 | hat = SDL_HAT_RIGHTDOWN; | ||
| 900 | break; | ||
| 901 | case 4: | ||
| 902 | hat = SDL_HAT_DOWN; | ||
| 903 | break; | ||
| 904 | case 5: | ||
| 905 | hat = SDL_HAT_LEFTDOWN; | ||
| 906 | break; | ||
| 907 | case 6: | ||
| 908 | hat = SDL_HAT_LEFT; | ||
| 909 | break; | ||
| 910 | case 7: | ||
| 911 | hat = SDL_HAT_LEFTUP; | ||
| 912 | break; | ||
| 913 | default: | ||
| 914 | hat = SDL_HAT_CENTERED; | ||
| 915 | break; | ||
| 916 | } | ||
| 917 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 918 | } | ||
| 919 | } | ||
| 920 | |||
| 921 | if (data[0] & 0x40) { | ||
| 922 | axis = 32767; | ||
| 923 | } else { | ||
| 924 | axis = ((int)data[17] * 257) - 32768; | ||
| 925 | } | ||
| 926 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 927 | if (data[0] & 0x80) { | ||
| 928 | axis = 32767; | ||
| 929 | } else { | ||
| 930 | axis = ((int)data[18] * 257) - 32768; | ||
| 931 | } | ||
| 932 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 933 | axis = ((int)data[3] * 257) - 32768; | ||
| 934 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 935 | axis = ((int)data[4] * 257) - 32768; | ||
| 936 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 937 | axis = ((int)data[5] * 257) - 32768; | ||
| 938 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 939 | axis = ((int)data[6] * 257) - 32768; | ||
| 940 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 941 | |||
| 942 | // Buttons are mapped as axes in the order they appear in the button enumeration | ||
| 943 | if (ctx->has_analog_buttons) { | ||
| 944 | static int button_axis_offsets[] = { | ||
| 945 | 13, // SDL_GAMEPAD_BUTTON_SOUTH | ||
| 946 | 12, // SDL_GAMEPAD_BUTTON_EAST | ||
| 947 | 14, // SDL_GAMEPAD_BUTTON_WEST | ||
| 948 | 11, // SDL_GAMEPAD_BUTTON_NORTH | ||
| 949 | 0, // SDL_GAMEPAD_BUTTON_BACK | ||
| 950 | 0, // SDL_GAMEPAD_BUTTON_GUIDE | ||
| 951 | 0, // SDL_GAMEPAD_BUTTON_START | ||
| 952 | 0, // SDL_GAMEPAD_BUTTON_LEFT_STICK | ||
| 953 | 0, // SDL_GAMEPAD_BUTTON_RIGHT_STICK | ||
| 954 | 15, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER | ||
| 955 | 16, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER | ||
| 956 | 9, // SDL_GAMEPAD_BUTTON_DPAD_UP | ||
| 957 | 10, // SDL_GAMEPAD_BUTTON_DPAD_DOWN | ||
| 958 | 8, // SDL_GAMEPAD_BUTTON_DPAD_LEFT | ||
| 959 | 7, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT | ||
| 960 | }; | ||
| 961 | Uint8 i, axis_index = 6; | ||
| 962 | |||
| 963 | for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) { | ||
| 964 | int offset = button_axis_offsets[i]; | ||
| 965 | if (!offset) { | ||
| 966 | // This button doesn't report as an axis | ||
| 967 | continue; | ||
| 968 | } | ||
| 969 | |||
| 970 | axis = ((int)data[offset] * 257) - 32768; | ||
| 971 | SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis); | ||
| 972 | ++axis_index; | ||
| 973 | } | ||
| 974 | } | ||
| 975 | |||
| 976 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 977 | } | ||
| 978 | |||
| 979 | static bool HIDAPI_DriverPS3ThirdParty_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 980 | { | ||
| 981 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 982 | SDL_Joystick *joystick = NULL; | ||
| 983 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 984 | int size; | ||
| 985 | |||
| 986 | if (device->num_joysticks > 0) { | ||
| 987 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 988 | } else { | ||
| 989 | return false; | ||
| 990 | } | ||
| 991 | |||
| 992 | while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { | ||
| 993 | #ifdef DEBUG_PS3_PROTOCOL | ||
| 994 | HIDAPI_DumpPacket("PS3 packet: size = %d", data, size); | ||
| 995 | #endif | ||
| 996 | if (!joystick) { | ||
| 997 | continue; | ||
| 998 | } | ||
| 999 | |||
| 1000 | if (size >= 19) { | ||
| 1001 | HIDAPI_DriverPS3ThirdParty_HandleStatePacket19(joystick, ctx, data, size); | ||
| 1002 | } else if (size == 18) { | ||
| 1003 | // This packet format was seen with the Logitech ChillStream | ||
| 1004 | HIDAPI_DriverPS3ThirdParty_HandleStatePacket18(joystick, ctx, data, size); | ||
| 1005 | } else { | ||
| 1006 | #ifdef DEBUG_JOYSTICK | ||
| 1007 | SDL_Log("Unknown PS3 packet, size %d", size); | ||
| 1008 | #endif | ||
| 1009 | } | ||
| 1010 | } | ||
| 1011 | |||
| 1012 | if (size < 0) { | ||
| 1013 | // Read error, device is disconnected | ||
| 1014 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 1015 | } | ||
| 1016 | return (size >= 0); | ||
| 1017 | } | ||
| 1018 | |||
| 1019 | static void HIDAPI_DriverPS3ThirdParty_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1020 | { | ||
| 1021 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 1022 | |||
| 1023 | ctx->joystick = NULL; | ||
| 1024 | } | ||
| 1025 | |||
| 1026 | static void HIDAPI_DriverPS3ThirdParty_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 1027 | { | ||
| 1028 | } | ||
| 1029 | |||
| 1030 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3ThirdParty = { | ||
| 1031 | SDL_HINT_JOYSTICK_HIDAPI_PS3, | ||
| 1032 | true, | ||
| 1033 | HIDAPI_DriverPS3_RegisterHints, | ||
| 1034 | HIDAPI_DriverPS3_UnregisterHints, | ||
| 1035 | HIDAPI_DriverPS3ThirdParty_IsEnabled, | ||
| 1036 | HIDAPI_DriverPS3ThirdParty_IsSupportedDevice, | ||
| 1037 | HIDAPI_DriverPS3ThirdParty_InitDevice, | ||
| 1038 | HIDAPI_DriverPS3ThirdParty_GetDevicePlayerIndex, | ||
| 1039 | HIDAPI_DriverPS3ThirdParty_SetDevicePlayerIndex, | ||
| 1040 | HIDAPI_DriverPS3ThirdParty_UpdateDevice, | ||
| 1041 | HIDAPI_DriverPS3ThirdParty_OpenJoystick, | ||
| 1042 | HIDAPI_DriverPS3ThirdParty_RumbleJoystick, | ||
| 1043 | HIDAPI_DriverPS3ThirdParty_RumbleJoystickTriggers, | ||
| 1044 | HIDAPI_DriverPS3ThirdParty_GetJoystickCapabilities, | ||
| 1045 | HIDAPI_DriverPS3ThirdParty_SetJoystickLED, | ||
| 1046 | HIDAPI_DriverPS3ThirdParty_SendJoystickEffect, | ||
| 1047 | HIDAPI_DriverPS3ThirdParty_SetJoystickSensorsEnabled, | ||
| 1048 | HIDAPI_DriverPS3ThirdParty_CloseJoystick, | ||
| 1049 | HIDAPI_DriverPS3ThirdParty_FreeDevice, | ||
| 1050 | }; | ||
| 1051 | |||
| 1052 | static bool HIDAPI_DriverPS3_UpdateRumbleSonySixaxis(SDL_HIDAPI_Device *device); | ||
| 1053 | static bool HIDAPI_DriverPS3_UpdateLEDsSonySixaxis(SDL_HIDAPI_Device *device); | ||
| 1054 | |||
| 1055 | static void HIDAPI_DriverPS3SonySixaxis_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 1056 | { | ||
| 1057 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER, callback, userdata); | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | static void HIDAPI_DriverPS3SonySixaxis_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 1061 | { | ||
| 1062 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER, callback, userdata); | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | static bool HIDAPI_DriverPS3SonySixaxis_IsEnabled(void) | ||
| 1066 | { | ||
| 1067 | #ifdef SDL_PLATFORM_WIN32 | ||
| 1068 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER, false); | ||
| 1069 | #else | ||
| 1070 | return false; | ||
| 1071 | #endif | ||
| 1072 | } | ||
| 1073 | |||
| 1074 | 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) | ||
| 1075 | { | ||
| 1076 | if (vendor_id == USB_VENDOR_SONY && product_id == USB_PRODUCT_SONY_DS3) { | ||
| 1077 | return true; | ||
| 1078 | } | ||
| 1079 | return false; | ||
| 1080 | } | ||
| 1081 | |||
| 1082 | static bool HIDAPI_DriverPS3SonySixaxis_InitDevice(SDL_HIDAPI_Device *device) | ||
| 1083 | { | ||
| 1084 | SDL_DriverPS3_Context *ctx; | ||
| 1085 | |||
| 1086 | ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 1087 | if (!ctx) { | ||
| 1088 | return false; | ||
| 1089 | } | ||
| 1090 | ctx->device = device; | ||
| 1091 | ctx->has_analog_buttons = true; | ||
| 1092 | |||
| 1093 | device->context = ctx; | ||
| 1094 | |||
| 1095 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 1096 | |||
| 1097 | int size = ReadFeatureReport(device->dev, 0xf2, data, sizeof(data)); | ||
| 1098 | if (size < 0) { | ||
| 1099 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, | ||
| 1100 | "HIDAPI_DriverPS3SonySixaxis_InitDevice(): Couldn't read feature report 0xf2. Trying again with 0x0."); | ||
| 1101 | SDL_zeroa(data); | ||
| 1102 | size = ReadFeatureReport(device->dev, 0x00, data, sizeof(data)); | ||
| 1103 | if (size < 0) { | ||
| 1104 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, | ||
| 1105 | "HIDAPI_DriverPS3SonySixaxis_InitDevice(): Couldn't read feature report 0x00."); | ||
| 1106 | return false; | ||
| 1107 | } | ||
| 1108 | #ifdef DEBUG_PS3_PROTOCOL | ||
| 1109 | HIDAPI_DumpPacket("PS3 0x0 packet: size = %d", data, size); | ||
| 1110 | #endif | ||
| 1111 | } | ||
| 1112 | #ifdef DEBUG_PS3_PROTOCOL | ||
| 1113 | HIDAPI_DumpPacket("PS3 0xF2 packet: size = %d", data, size); | ||
| 1114 | #endif | ||
| 1115 | |||
| 1116 | device->type = SDL_GAMEPAD_TYPE_PS3; | ||
| 1117 | HIDAPI_SetDeviceName(device, "PS3 Controller"); | ||
| 1118 | |||
| 1119 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 1120 | } | ||
| 1121 | |||
| 1122 | static int HIDAPI_DriverPS3SonySixaxis_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 1123 | { | ||
| 1124 | return -1; | ||
| 1125 | } | ||
| 1126 | |||
| 1127 | static void HIDAPI_DriverPS3SonySixaxis_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 1128 | { | ||
| 1129 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 1130 | |||
| 1131 | if (!ctx) { | ||
| 1132 | return; | ||
| 1133 | } | ||
| 1134 | |||
| 1135 | ctx->player_index = player_index; | ||
| 1136 | |||
| 1137 | // This will set the new LED state based on the new player index | ||
| 1138 | HIDAPI_DriverPS3_UpdateLEDsSonySixaxis(device); | ||
| 1139 | } | ||
| 1140 | |||
| 1141 | static bool HIDAPI_DriverPS3SonySixaxis_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1142 | { | ||
| 1143 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 1144 | |||
| 1145 | SDL_AssertJoysticksLocked(); | ||
| 1146 | |||
| 1147 | ctx->joystick = joystick; | ||
| 1148 | ctx->effects_updated = false; | ||
| 1149 | ctx->rumble_left = 0; | ||
| 1150 | ctx->rumble_right = 0; | ||
| 1151 | SDL_zeroa(ctx->last_state); | ||
| 1152 | |||
| 1153 | // Initialize player index (needed for setting LEDs) | ||
| 1154 | ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); | ||
| 1155 | |||
| 1156 | // Initialize the joystick capabilities | ||
| 1157 | joystick->nbuttons = 11; | ||
| 1158 | joystick->naxes = 6; | ||
| 1159 | if (ctx->has_analog_buttons) { | ||
| 1160 | joystick->naxes += 10; | ||
| 1161 | } | ||
| 1162 | joystick->nhats = 1; | ||
| 1163 | |||
| 1164 | SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f); | ||
| 1165 | |||
| 1166 | return true; | ||
| 1167 | } | ||
| 1168 | |||
| 1169 | static bool HIDAPI_DriverPS3SonySixaxis_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 1170 | { | ||
| 1171 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 1172 | |||
| 1173 | ctx->rumble_left = (low_frequency_rumble >> 8); | ||
| 1174 | ctx->rumble_right = (high_frequency_rumble >> 8); | ||
| 1175 | |||
| 1176 | return HIDAPI_DriverPS3_UpdateRumbleSonySixaxis(device); | ||
| 1177 | } | ||
| 1178 | |||
| 1179 | static bool HIDAPI_DriverPS3SonySixaxis_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 1180 | { | ||
| 1181 | return SDL_Unsupported(); | ||
| 1182 | } | ||
| 1183 | |||
| 1184 | static Uint32 HIDAPI_DriverPS3SonySixaxis_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1185 | { | ||
| 1186 | return 0; | ||
| 1187 | } | ||
| 1188 | |||
| 1189 | static bool HIDAPI_DriverPS3SonySixaxis_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 1190 | { | ||
| 1191 | return SDL_Unsupported(); | ||
| 1192 | } | ||
| 1193 | |||
| 1194 | static bool HIDAPI_DriverPS3SonySixaxis_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) | ||
| 1195 | { | ||
| 1196 | Uint8 data[49]; | ||
| 1197 | int report_size; | ||
| 1198 | |||
| 1199 | SDL_zeroa(data); | ||
| 1200 | |||
| 1201 | data[0] = k_EPS3SonySixaxisReportIdEffects; | ||
| 1202 | report_size = sizeof(data); | ||
| 1203 | |||
| 1204 | // No offset with Sony sixaxis.sys driver | ||
| 1205 | SDL_memcpy(&data, effect, SDL_min(sizeof(data), (size_t)size)); | ||
| 1206 | |||
| 1207 | if (SDL_HIDAPI_SendRumble(device, data, report_size) != report_size) { | ||
| 1208 | return SDL_SetError("Couldn't send rumble packet"); | ||
| 1209 | } | ||
| 1210 | return true; | ||
| 1211 | } | ||
| 1212 | |||
| 1213 | static bool HIDAPI_DriverPS3SonySixaxis_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 1214 | { | ||
| 1215 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 1216 | |||
| 1217 | ctx->report_sensors = enabled; | ||
| 1218 | |||
| 1219 | return true; | ||
| 1220 | } | ||
| 1221 | |||
| 1222 | static void HIDAPI_DriverPS3SonySixaxis_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size) | ||
| 1223 | { | ||
| 1224 | Sint16 axis; | ||
| 1225 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 1226 | |||
| 1227 | if (ctx->last_state[2] != data[2]) { | ||
| 1228 | Uint8 hat = 0; | ||
| 1229 | |||
| 1230 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x01) != 0)); | ||
| 1231 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x02) != 0)); | ||
| 1232 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x04) != 0)); | ||
| 1233 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x08) != 0)); | ||
| 1234 | |||
| 1235 | if (data[2] & 0x10) { | ||
| 1236 | hat |= SDL_HAT_UP; | ||
| 1237 | } | ||
| 1238 | if (data[2] & 0x20) { | ||
| 1239 | hat |= SDL_HAT_RIGHT; | ||
| 1240 | } | ||
| 1241 | if (data[2] & 0x40) { | ||
| 1242 | hat |= SDL_HAT_DOWN; | ||
| 1243 | } | ||
| 1244 | if (data[2] & 0x80) { | ||
| 1245 | hat |= SDL_HAT_LEFT; | ||
| 1246 | } | ||
| 1247 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 1248 | } | ||
| 1249 | |||
| 1250 | if (ctx->last_state[3] != data[3]) { | ||
| 1251 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x04) != 0)); | ||
| 1252 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x08) != 0)); | ||
| 1253 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x10) != 0)); | ||
| 1254 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0)); | ||
| 1255 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x40) != 0)); | ||
| 1256 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x80) != 0)); | ||
| 1257 | } | ||
| 1258 | |||
| 1259 | if (ctx->last_state[4] != data[4]) { | ||
| 1260 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[4] & 0x01) != 0)); | ||
| 1261 | } | ||
| 1262 | |||
| 1263 | axis = ((int)data[18] * 257) - 32768; | ||
| 1264 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 1265 | axis = ((int)data[19] * 257) - 32768; | ||
| 1266 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 1267 | axis = ((int)data[6] * 257) - 32768; | ||
| 1268 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 1269 | axis = ((int)data[7] * 257) - 32768; | ||
| 1270 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 1271 | axis = ((int)data[8] * 257) - 32768; | ||
| 1272 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 1273 | axis = ((int)data[9] * 257) - 32768; | ||
| 1274 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 1275 | |||
| 1276 | // Buttons are mapped as axes in the order they appear in the button enumeration | ||
| 1277 | if (ctx->has_analog_buttons) { | ||
| 1278 | static int button_axis_offsets[] = { | ||
| 1279 | 24, // SDL_GAMEPAD_BUTTON_SOUTH | ||
| 1280 | 23, // SDL_GAMEPAD_BUTTON_EAST | ||
| 1281 | 25, // SDL_GAMEPAD_BUTTON_WEST | ||
| 1282 | 22, // SDL_GAMEPAD_BUTTON_NORTH | ||
| 1283 | 0, // SDL_GAMEPAD_BUTTON_BACK | ||
| 1284 | 0, // SDL_GAMEPAD_BUTTON_GUIDE | ||
| 1285 | 0, // SDL_GAMEPAD_BUTTON_START | ||
| 1286 | 0, // SDL_GAMEPAD_BUTTON_LEFT_STICK | ||
| 1287 | 0, // SDL_GAMEPAD_BUTTON_RIGHT_STICK | ||
| 1288 | 20, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER | ||
| 1289 | 21, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER | ||
| 1290 | 14, // SDL_GAMEPAD_BUTTON_DPAD_UP | ||
| 1291 | 16, // SDL_GAMEPAD_BUTTON_DPAD_DOWN | ||
| 1292 | 17, // SDL_GAMEPAD_BUTTON_DPAD_LEFT | ||
| 1293 | 15, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT | ||
| 1294 | }; | ||
| 1295 | Uint8 i, axis_index = 6; | ||
| 1296 | |||
| 1297 | for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) { | ||
| 1298 | int offset = button_axis_offsets[i]; | ||
| 1299 | if (!offset) { | ||
| 1300 | // This button doesn't report as an axis | ||
| 1301 | continue; | ||
| 1302 | } | ||
| 1303 | |||
| 1304 | axis = ((int)data[offset] * 257) - 32768; | ||
| 1305 | SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis); | ||
| 1306 | ++axis_index; | ||
| 1307 | } | ||
| 1308 | } | ||
| 1309 | |||
| 1310 | if (ctx->report_sensors) { | ||
| 1311 | float sensor_data[3]; | ||
| 1312 | |||
| 1313 | sensor_data[0] = HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[41], data[42])); | ||
| 1314 | sensor_data[1] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[45], data[46])); | ||
| 1315 | sensor_data[2] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[43], data[44])); | ||
| 1316 | SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, sensor_data, SDL_arraysize(sensor_data)); | ||
| 1317 | } | ||
| 1318 | |||
| 1319 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 1320 | } | ||
| 1321 | |||
| 1322 | static bool HIDAPI_DriverPS3SonySixaxis_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 1323 | { | ||
| 1324 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 1325 | SDL_Joystick *joystick = NULL; | ||
| 1326 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 1327 | int size; | ||
| 1328 | |||
| 1329 | if (device->num_joysticks > 0) { | ||
| 1330 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 1331 | } else { | ||
| 1332 | return false; | ||
| 1333 | } | ||
| 1334 | |||
| 1335 | if (!joystick) { | ||
| 1336 | return false; | ||
| 1337 | } | ||
| 1338 | |||
| 1339 | // With sixaxis.sys driver we need to use hid_get_feature_report instead of hid_read | ||
| 1340 | size = ReadFeatureReport(device->dev, 0x0, data, sizeof(data)); | ||
| 1341 | if (size < 0) { | ||
| 1342 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, | ||
| 1343 | "HIDAPI_DriverPS3SonySixaxis_UpdateDevice(): Couldn't read feature report 0x00"); | ||
| 1344 | return false; | ||
| 1345 | } | ||
| 1346 | |||
| 1347 | switch (data[0]) { | ||
| 1348 | case k_EPS3SonySixaxisReportIdState: | ||
| 1349 | HIDAPI_DriverPS3SonySixaxis_HandleStatePacket(joystick, ctx, &data[1], size - 1); // report data starts in data[1] | ||
| 1350 | |||
| 1351 | // Wait for the first report to set the LED state after the controller stops blinking | ||
| 1352 | if (!ctx->effects_updated) { | ||
| 1353 | HIDAPI_DriverPS3_UpdateLEDsSonySixaxis(device); | ||
| 1354 | ctx->effects_updated = true; | ||
| 1355 | } | ||
| 1356 | |||
| 1357 | break; | ||
| 1358 | default: | ||
| 1359 | #ifdef DEBUG_JOYSTICK | ||
| 1360 | SDL_Log("Unknown PS3 packet: 0x%.2x", data[0]); | ||
| 1361 | #endif | ||
| 1362 | break; | ||
| 1363 | } | ||
| 1364 | |||
| 1365 | if (size < 0) { | ||
| 1366 | // Read error, device is disconnected | ||
| 1367 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 1368 | } | ||
| 1369 | return (size >= 0); | ||
| 1370 | } | ||
| 1371 | |||
| 1372 | static void HIDAPI_DriverPS3SonySixaxis_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1373 | { | ||
| 1374 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 1375 | |||
| 1376 | ctx->joystick = NULL; | ||
| 1377 | } | ||
| 1378 | |||
| 1379 | static void HIDAPI_DriverPS3SonySixaxis_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 1380 | { | ||
| 1381 | } | ||
| 1382 | |||
| 1383 | static bool HIDAPI_DriverPS3_UpdateRumbleSonySixaxis(SDL_HIDAPI_Device *device) | ||
| 1384 | { | ||
| 1385 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 1386 | |||
| 1387 | Uint8 effects[] = { | ||
| 1388 | 0x0, // Report Id | ||
| 1389 | k_EPS3SixaxisCommandSetMotors, // 2 = Set Motors | ||
| 1390 | 0x00, 0x00, 0x00, // padding | ||
| 1391 | 0xff, // Small Motor duration - 0xff is forever | ||
| 1392 | 0x00, // Small Motor off/on (0 or 1) | ||
| 1393 | 0xff, // Large Motor duration - 0xff is forever | ||
| 1394 | 0x00 // Large Motor force (0 to 255) | ||
| 1395 | }; | ||
| 1396 | |||
| 1397 | effects[6] = ctx->rumble_right ? 1 : 0; // Small motor | ||
| 1398 | effects[8] = ctx->rumble_left; // Large motor | ||
| 1399 | |||
| 1400 | return HIDAPI_DriverPS3SonySixaxis_SendJoystickEffect(device, ctx->joystick, effects, sizeof(effects)); | ||
| 1401 | } | ||
| 1402 | |||
| 1403 | static bool HIDAPI_DriverPS3_UpdateLEDsSonySixaxis(SDL_HIDAPI_Device *device) | ||
| 1404 | { | ||
| 1405 | SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context; | ||
| 1406 | |||
| 1407 | Uint8 effects[] = { | ||
| 1408 | 0x0, // Report Id | ||
| 1409 | k_EPS3SixaxisCommandSetLEDs, // 1 = Set LEDs | ||
| 1410 | 0x00, 0x00, 0x00, // padding | ||
| 1411 | 0x00, 0x00, 0x00, 0x00 // LED #4, LED #3, LED #2, LED #1 (0 = Off, 1 = On, 2 = Flashing) | ||
| 1412 | }; | ||
| 1413 | |||
| 1414 | // 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) | ||
| 1415 | if (ctx->player_index < 4) { | ||
| 1416 | effects[8 - ctx->player_index] = 1; | ||
| 1417 | } | ||
| 1418 | |||
| 1419 | return HIDAPI_DriverPS3SonySixaxis_SendJoystickEffect(device, ctx->joystick, effects, sizeof(effects)); | ||
| 1420 | } | ||
| 1421 | |||
| 1422 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3SonySixaxis = { | ||
| 1423 | SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER, | ||
| 1424 | true, | ||
| 1425 | HIDAPI_DriverPS3SonySixaxis_RegisterHints, | ||
| 1426 | HIDAPI_DriverPS3SonySixaxis_UnregisterHints, | ||
| 1427 | HIDAPI_DriverPS3SonySixaxis_IsEnabled, | ||
| 1428 | HIDAPI_DriverPS3SonySixaxis_IsSupportedDevice, | ||
| 1429 | HIDAPI_DriverPS3SonySixaxis_InitDevice, | ||
| 1430 | HIDAPI_DriverPS3SonySixaxis_GetDevicePlayerIndex, | ||
| 1431 | HIDAPI_DriverPS3SonySixaxis_SetDevicePlayerIndex, | ||
| 1432 | HIDAPI_DriverPS3SonySixaxis_UpdateDevice, | ||
| 1433 | HIDAPI_DriverPS3SonySixaxis_OpenJoystick, | ||
| 1434 | HIDAPI_DriverPS3SonySixaxis_RumbleJoystick, | ||
| 1435 | HIDAPI_DriverPS3SonySixaxis_RumbleJoystickTriggers, | ||
| 1436 | HIDAPI_DriverPS3SonySixaxis_GetJoystickCapabilities, | ||
| 1437 | HIDAPI_DriverPS3SonySixaxis_SetJoystickLED, | ||
| 1438 | HIDAPI_DriverPS3SonySixaxis_SendJoystickEffect, | ||
| 1439 | HIDAPI_DriverPS3SonySixaxis_SetJoystickSensorsEnabled, | ||
| 1440 | HIDAPI_DriverPS3SonySixaxis_CloseJoystick, | ||
| 1441 | HIDAPI_DriverPS3SonySixaxis_FreeDevice, | ||
| 1442 | }; | ||
| 1443 | |||
| 1444 | #endif // SDL_JOYSTICK_HIDAPI_PS3 | ||
| 1445 | |||
| 1446 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | /* This driver supports both simplified reports and the extended input reports enabled by Steam. | ||
| 22 | Code and logic contributed by Valve Corporation under the SDL zlib license. | ||
| 23 | */ | ||
| 24 | #include "SDL_internal.h" | ||
| 25 | |||
| 26 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 27 | |||
| 28 | #include "../../SDL_hints_c.h" | ||
| 29 | #include "../SDL_sysjoystick.h" | ||
| 30 | #include "SDL_hidapijoystick_c.h" | ||
| 31 | #include "SDL_hidapi_rumble.h" | ||
| 32 | |||
| 33 | #ifdef SDL_JOYSTICK_HIDAPI_PS4 | ||
| 34 | |||
| 35 | // Define this if you want to log all packets from the controller | ||
| 36 | #if 0 | ||
| 37 | #define DEBUG_PS4_PROTOCOL | ||
| 38 | #endif | ||
| 39 | |||
| 40 | // Define this if you want to log calibration data | ||
| 41 | #if 0 | ||
| 42 | #define DEBUG_PS4_CALIBRATION | ||
| 43 | #endif | ||
| 44 | |||
| 45 | #define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500 | ||
| 46 | |||
| 47 | #define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) | ||
| 48 | #define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ | ||
| 49 | (((Uint32)(B)) << 8) | \ | ||
| 50 | (((Uint32)(C)) << 16) | \ | ||
| 51 | (((Uint32)(D)) << 24)) | ||
| 52 | |||
| 53 | enum | ||
| 54 | { | ||
| 55 | SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD = 11 | ||
| 56 | }; | ||
| 57 | |||
| 58 | typedef enum | ||
| 59 | { | ||
| 60 | k_EPS4ReportIdUsbState = 1, | ||
| 61 | k_EPS4ReportIdUsbEffects = 5, | ||
| 62 | k_EPS4ReportIdBluetoothState1 = 17, | ||
| 63 | k_EPS4ReportIdBluetoothState2 = 18, | ||
| 64 | k_EPS4ReportIdBluetoothState3 = 19, | ||
| 65 | k_EPS4ReportIdBluetoothState4 = 20, | ||
| 66 | k_EPS4ReportIdBluetoothState5 = 21, | ||
| 67 | k_EPS4ReportIdBluetoothState6 = 22, | ||
| 68 | k_EPS4ReportIdBluetoothState7 = 23, | ||
| 69 | k_EPS4ReportIdBluetoothState8 = 24, | ||
| 70 | k_EPS4ReportIdBluetoothState9 = 25, | ||
| 71 | k_EPS4ReportIdBluetoothEffects = 17, | ||
| 72 | k_EPS4ReportIdDisconnectMessage = 226, | ||
| 73 | } EPS4ReportId; | ||
| 74 | |||
| 75 | typedef enum | ||
| 76 | { | ||
| 77 | k_ePS4FeatureReportIdGyroCalibration_USB = 0x02, | ||
| 78 | k_ePS4FeatureReportIdCapabilities = 0x03, | ||
| 79 | k_ePS4FeatureReportIdGyroCalibration_BT = 0x05, | ||
| 80 | k_ePS4FeatureReportIdSerialNumber = 0x12, | ||
| 81 | } EPS4FeatureReportID; | ||
| 82 | |||
| 83 | typedef struct | ||
| 84 | { | ||
| 85 | Uint8 ucLeftJoystickX; | ||
| 86 | Uint8 ucLeftJoystickY; | ||
| 87 | Uint8 ucRightJoystickX; | ||
| 88 | Uint8 ucRightJoystickY; | ||
| 89 | Uint8 rgucButtonsHatAndCounter[3]; | ||
| 90 | Uint8 ucTriggerLeft; | ||
| 91 | Uint8 ucTriggerRight; | ||
| 92 | Uint8 rgucTimestamp[2]; | ||
| 93 | Uint8 _rgucPad0[1]; | ||
| 94 | Uint8 rgucGyroX[2]; | ||
| 95 | Uint8 rgucGyroY[2]; | ||
| 96 | Uint8 rgucGyroZ[2]; | ||
| 97 | Uint8 rgucAccelX[2]; | ||
| 98 | Uint8 rgucAccelY[2]; | ||
| 99 | Uint8 rgucAccelZ[2]; | ||
| 100 | Uint8 _rgucPad1[5]; | ||
| 101 | Uint8 ucBatteryLevel; | ||
| 102 | Uint8 _rgucPad2[4]; | ||
| 103 | Uint8 ucTouchpadCounter1; | ||
| 104 | Uint8 rgucTouchpadData1[3]; | ||
| 105 | Uint8 ucTouchpadCounter2; | ||
| 106 | Uint8 rgucTouchpadData2[3]; | ||
| 107 | } PS4StatePacket_t; | ||
| 108 | |||
| 109 | typedef struct | ||
| 110 | { | ||
| 111 | Uint8 ucRumbleRight; | ||
| 112 | Uint8 ucRumbleLeft; | ||
| 113 | Uint8 ucLedRed; | ||
| 114 | Uint8 ucLedGreen; | ||
| 115 | Uint8 ucLedBlue; | ||
| 116 | Uint8 ucLedDelayOn; | ||
| 117 | Uint8 ucLedDelayOff; | ||
| 118 | Uint8 _rgucPad0[8]; | ||
| 119 | Uint8 ucVolumeLeft; | ||
| 120 | Uint8 ucVolumeRight; | ||
| 121 | Uint8 ucVolumeMic; | ||
| 122 | Uint8 ucVolumeSpeaker; | ||
| 123 | } DS4EffectsState_t; | ||
| 124 | |||
| 125 | typedef struct | ||
| 126 | { | ||
| 127 | Sint16 bias; | ||
| 128 | float scale; | ||
| 129 | } IMUCalibrationData; | ||
| 130 | |||
| 131 | /* Rumble hint mode: | ||
| 132 | * "0": enhanced features are never used | ||
| 133 | * "1": enhanced features are always used | ||
| 134 | * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it. | ||
| 135 | */ | ||
| 136 | typedef enum | ||
| 137 | { | ||
| 138 | PS4_ENHANCED_REPORT_HINT_OFF, | ||
| 139 | PS4_ENHANCED_REPORT_HINT_ON, | ||
| 140 | PS4_ENHANCED_REPORT_HINT_AUTO | ||
| 141 | } HIDAPI_PS4_EnhancedReportHint; | ||
| 142 | |||
| 143 | typedef struct | ||
| 144 | { | ||
| 145 | SDL_HIDAPI_Device *device; | ||
| 146 | SDL_Joystick *joystick; | ||
| 147 | bool is_dongle; | ||
| 148 | bool is_nacon_dongle; | ||
| 149 | bool official_controller; | ||
| 150 | bool sensors_supported; | ||
| 151 | bool lightbar_supported; | ||
| 152 | bool vibration_supported; | ||
| 153 | bool touchpad_supported; | ||
| 154 | bool effects_supported; | ||
| 155 | HIDAPI_PS4_EnhancedReportHint enhanced_report_hint; | ||
| 156 | bool enhanced_reports; | ||
| 157 | bool enhanced_mode; | ||
| 158 | bool enhanced_mode_available; | ||
| 159 | Uint8 report_interval; | ||
| 160 | bool report_sensors; | ||
| 161 | bool report_touchpad; | ||
| 162 | bool report_battery; | ||
| 163 | bool hardware_calibration; | ||
| 164 | IMUCalibrationData calibration[6]; | ||
| 165 | Uint64 last_packet; | ||
| 166 | int player_index; | ||
| 167 | Uint8 rumble_left; | ||
| 168 | Uint8 rumble_right; | ||
| 169 | bool color_set; | ||
| 170 | Uint8 led_red; | ||
| 171 | Uint8 led_green; | ||
| 172 | Uint8 led_blue; | ||
| 173 | Uint16 gyro_numerator; | ||
| 174 | Uint16 gyro_denominator; | ||
| 175 | Uint16 accel_numerator; | ||
| 176 | Uint16 accel_denominator; | ||
| 177 | Uint64 sensor_ticks; | ||
| 178 | Uint16 last_tick; | ||
| 179 | Uint16 valid_crc_packets; // wrapping counter | ||
| 180 | PS4StatePacket_t last_state; | ||
| 181 | } SDL_DriverPS4_Context; | ||
| 182 | |||
| 183 | static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage); | ||
| 184 | |||
| 185 | static void HIDAPI_DriverPS4_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 186 | { | ||
| 187 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4, callback, userdata); | ||
| 188 | } | ||
| 189 | |||
| 190 | static void HIDAPI_DriverPS4_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 191 | { | ||
| 192 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4, callback, userdata); | ||
| 193 | } | ||
| 194 | |||
| 195 | static bool HIDAPI_DriverPS4_IsEnabled(void) | ||
| 196 | { | ||
| 197 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS4, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); | ||
| 198 | } | ||
| 199 | |||
| 200 | static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length) | ||
| 201 | { | ||
| 202 | SDL_memset(report, 0, length); | ||
| 203 | report[0] = report_id; | ||
| 204 | return SDL_hid_get_feature_report(dev, report, length); | ||
| 205 | } | ||
| 206 | |||
| 207 | 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) | ||
| 208 | { | ||
| 209 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 210 | int size; | ||
| 211 | |||
| 212 | if (type == SDL_GAMEPAD_TYPE_PS4) { | ||
| 213 | return true; | ||
| 214 | } | ||
| 215 | |||
| 216 | if (HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) { | ||
| 217 | if (device && device->dev) { | ||
| 218 | size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data)); | ||
| 219 | if (size == 48 && data[2] == 0x27) { | ||
| 220 | // Supported third party controller | ||
| 221 | return true; | ||
| 222 | } else { | ||
| 223 | return false; | ||
| 224 | } | ||
| 225 | } else { | ||
| 226 | // Might be supported by this driver, enumerate and find out | ||
| 227 | return true; | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | return false; | ||
| 232 | } | ||
| 233 | |||
| 234 | static void SetLedsForPlayerIndex(DS4EffectsState_t *effects, int player_index) | ||
| 235 | { | ||
| 236 | /* This list is the same as what hid-sony.c uses in the Linux kernel. | ||
| 237 | The first 4 values correspond to what the PS4 assigns. | ||
| 238 | */ | ||
| 239 | static const Uint8 colors[7][3] = { | ||
| 240 | { 0x00, 0x00, 0x40 }, // Blue | ||
| 241 | { 0x40, 0x00, 0x00 }, // Red | ||
| 242 | { 0x00, 0x40, 0x00 }, // Green | ||
| 243 | { 0x20, 0x00, 0x20 }, // Pink | ||
| 244 | { 0x02, 0x01, 0x00 }, // Orange | ||
| 245 | { 0x00, 0x01, 0x01 }, // Teal | ||
| 246 | { 0x01, 0x01, 0x01 } // White | ||
| 247 | }; | ||
| 248 | |||
| 249 | if (player_index >= 0) { | ||
| 250 | player_index %= SDL_arraysize(colors); | ||
| 251 | } else { | ||
| 252 | player_index = 0; | ||
| 253 | } | ||
| 254 | |||
| 255 | effects->ucLedRed = colors[player_index][0]; | ||
| 256 | effects->ucLedGreen = colors[player_index][1]; | ||
| 257 | effects->ucLedBlue = colors[player_index][2]; | ||
| 258 | } | ||
| 259 | |||
| 260 | static bool ReadWiredSerial(SDL_HIDAPI_Device *device, char *serial, size_t serial_size) | ||
| 261 | { | ||
| 262 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 263 | int size; | ||
| 264 | |||
| 265 | size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data)); | ||
| 266 | if (size >= 7 && (data[1] || data[2] || data[3] || data[4] || data[5] || data[6])) { | ||
| 267 | (void)SDL_snprintf(serial, serial_size, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", | ||
| 268 | data[6], data[5], data[4], data[3], data[2], data[1]); | ||
| 269 | return true; | ||
| 270 | } | ||
| 271 | return false; | ||
| 272 | } | ||
| 273 | |||
| 274 | static bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) | ||
| 275 | { | ||
| 276 | SDL_DriverPS4_Context *ctx; | ||
| 277 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 278 | int size; | ||
| 279 | char serial[18]; | ||
| 280 | SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; | ||
| 281 | |||
| 282 | ctx = (SDL_DriverPS4_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 283 | if (!ctx) { | ||
| 284 | return false; | ||
| 285 | } | ||
| 286 | ctx->device = device; | ||
| 287 | |||
| 288 | ctx->gyro_numerator = 1; | ||
| 289 | ctx->gyro_denominator = 16; | ||
| 290 | ctx->accel_numerator = 1; | ||
| 291 | ctx->accel_denominator = 8192; | ||
| 292 | |||
| 293 | device->context = ctx; | ||
| 294 | |||
| 295 | if (device->serial && SDL_strlen(device->serial) == 12) { | ||
| 296 | int i, j; | ||
| 297 | |||
| 298 | j = -1; | ||
| 299 | for (i = 0; i < 12; i += 2) { | ||
| 300 | j += 1; | ||
| 301 | SDL_memmove(&serial[j], &device->serial[i], 2); | ||
| 302 | j += 2; | ||
| 303 | serial[j] = '-'; | ||
| 304 | } | ||
| 305 | serial[j] = '\0'; | ||
| 306 | } else { | ||
| 307 | serial[0] = '\0'; | ||
| 308 | } | ||
| 309 | |||
| 310 | // Check for type of connection | ||
| 311 | ctx->is_dongle = (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_DONGLE); | ||
| 312 | if (ctx->is_dongle) { | ||
| 313 | ReadWiredSerial(device, serial, sizeof(serial)); | ||
| 314 | ctx->enhanced_reports = true; | ||
| 315 | } else if (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) { | ||
| 316 | ctx->enhanced_reports = true; | ||
| 317 | |||
| 318 | } else if (device->vendor_id == USB_VENDOR_SONY) { | ||
| 319 | if (device->is_bluetooth) { | ||
| 320 | // Read a report to see if we're in enhanced mode | ||
| 321 | size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16); | ||
| 322 | #ifdef DEBUG_PS4_PROTOCOL | ||
| 323 | if (size > 0) { | ||
| 324 | HIDAPI_DumpPacket("PS4 first packet: size = %d", data, size); | ||
| 325 | } else { | ||
| 326 | SDL_Log("PS4 first packet: size = %d", size); | ||
| 327 | } | ||
| 328 | #endif | ||
| 329 | if (size > 0 && | ||
| 330 | data[0] >= k_EPS4ReportIdBluetoothState1 && | ||
| 331 | data[0] <= k_EPS4ReportIdBluetoothState9) { | ||
| 332 | ctx->enhanced_reports = true; | ||
| 333 | } | ||
| 334 | } else { | ||
| 335 | ReadWiredSerial(device, serial, sizeof(serial)); | ||
| 336 | ctx->enhanced_reports = true; | ||
| 337 | } | ||
| 338 | } else { | ||
| 339 | // Third party controllers appear to all be wired | ||
| 340 | ctx->enhanced_reports = true; | ||
| 341 | } | ||
| 342 | |||
| 343 | if (device->vendor_id == USB_VENDOR_SONY) { | ||
| 344 | ctx->official_controller = true; | ||
| 345 | ctx->sensors_supported = true; | ||
| 346 | ctx->lightbar_supported = true; | ||
| 347 | ctx->vibration_supported = true; | ||
| 348 | ctx->touchpad_supported = true; | ||
| 349 | } else { | ||
| 350 | // Third party controller capability request | ||
| 351 | size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data)); | ||
| 352 | // Get the device capabilities | ||
| 353 | if (size == 48 && data[2] == 0x27) { | ||
| 354 | Uint8 capabilities = data[4]; | ||
| 355 | Uint8 device_type = data[5]; | ||
| 356 | Uint16 gyro_numerator = LOAD16(data[10], data[11]); | ||
| 357 | Uint16 gyro_denominator = LOAD16(data[12], data[13]); | ||
| 358 | Uint16 accel_numerator = LOAD16(data[14], data[15]); | ||
| 359 | Uint16 accel_denominator = LOAD16(data[16], data[17]); | ||
| 360 | |||
| 361 | #ifdef DEBUG_PS4_PROTOCOL | ||
| 362 | HIDAPI_DumpPacket("PS4 capabilities: size = %d", data, size); | ||
| 363 | #endif | ||
| 364 | if (capabilities & 0x02) { | ||
| 365 | ctx->sensors_supported = true; | ||
| 366 | } | ||
| 367 | if (capabilities & 0x04) { | ||
| 368 | ctx->lightbar_supported = true; | ||
| 369 | } | ||
| 370 | if (capabilities & 0x08) { | ||
| 371 | ctx->vibration_supported = true; | ||
| 372 | } | ||
| 373 | if (capabilities & 0x40) { | ||
| 374 | ctx->touchpad_supported = true; | ||
| 375 | } | ||
| 376 | |||
| 377 | switch (device_type) { | ||
| 378 | case 0x00: | ||
| 379 | joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; | ||
| 380 | break; | ||
| 381 | case 0x01: | ||
| 382 | joystick_type = SDL_JOYSTICK_TYPE_GUITAR; | ||
| 383 | break; | ||
| 384 | case 0x02: | ||
| 385 | joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT; | ||
| 386 | break; | ||
| 387 | case 0x04: | ||
| 388 | joystick_type = SDL_JOYSTICK_TYPE_DANCE_PAD; | ||
| 389 | break; | ||
| 390 | case 0x06: | ||
| 391 | joystick_type = SDL_JOYSTICK_TYPE_WHEEL; | ||
| 392 | break; | ||
| 393 | case 0x07: | ||
| 394 | joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK; | ||
| 395 | break; | ||
| 396 | case 0x08: | ||
| 397 | joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK; | ||
| 398 | break; | ||
| 399 | default: | ||
| 400 | joystick_type = SDL_JOYSTICK_TYPE_UNKNOWN; | ||
| 401 | break; | ||
| 402 | } | ||
| 403 | |||
| 404 | if (gyro_numerator && gyro_denominator) { | ||
| 405 | ctx->gyro_numerator = gyro_numerator; | ||
| 406 | ctx->gyro_denominator = gyro_denominator; | ||
| 407 | } | ||
| 408 | if (accel_numerator && accel_denominator) { | ||
| 409 | ctx->accel_numerator = accel_numerator; | ||
| 410 | ctx->accel_denominator = accel_denominator; | ||
| 411 | } | ||
| 412 | } else if (device->vendor_id == USB_VENDOR_RAZER) { | ||
| 413 | // The Razer Raiju doesn't respond to the detection protocol, but has a touchpad and vibration | ||
| 414 | ctx->vibration_supported = true; | ||
| 415 | ctx->touchpad_supported = true; | ||
| 416 | } | ||
| 417 | } | ||
| 418 | ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported); | ||
| 419 | |||
| 420 | if (device->vendor_id == USB_VENDOR_NACON_ALT && | ||
| 421 | device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRELESS) { | ||
| 422 | ctx->is_nacon_dongle = true; | ||
| 423 | } | ||
| 424 | |||
| 425 | if (device->vendor_id == USB_VENDOR_PDP && | ||
| 426 | (device->product_id == USB_PRODUCT_VICTRIX_FS_PRO || | ||
| 427 | device->product_id == USB_PRODUCT_VICTRIX_FS_PRO_V2)) { | ||
| 428 | /* The Victrix FS Pro V2 reports that it has lightbar support, | ||
| 429 | * but it doesn't respond to the effects packet, and will hang | ||
| 430 | * on reboot if we send it. | ||
| 431 | */ | ||
| 432 | ctx->effects_supported = false; | ||
| 433 | } | ||
| 434 | |||
| 435 | device->joystick_type = joystick_type; | ||
| 436 | device->type = SDL_GAMEPAD_TYPE_PS4; | ||
| 437 | if (ctx->official_controller) { | ||
| 438 | HIDAPI_SetDeviceName(device, "PS4 Controller"); | ||
| 439 | } | ||
| 440 | HIDAPI_SetDeviceSerial(device, serial); | ||
| 441 | |||
| 442 | // Prefer the USB device over the Bluetooth device | ||
| 443 | if (device->is_bluetooth) { | ||
| 444 | if (HIDAPI_HasConnectedUSBDevice(device->serial)) { | ||
| 445 | return true; | ||
| 446 | } | ||
| 447 | } else { | ||
| 448 | HIDAPI_DisconnectBluetoothDevice(device->serial); | ||
| 449 | } | ||
| 450 | if ((ctx->is_dongle || ctx->is_nacon_dongle) && serial[0] == '\0') { | ||
| 451 | // Not yet connected | ||
| 452 | return true; | ||
| 453 | } | ||
| 454 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 455 | } | ||
| 456 | |||
| 457 | static int HIDAPI_DriverPS4_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 458 | { | ||
| 459 | return -1; | ||
| 460 | } | ||
| 461 | |||
| 462 | static bool HIDAPI_DriverPS4_LoadOfficialCalibrationData(SDL_HIDAPI_Device *device) | ||
| 463 | { | ||
| 464 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; | ||
| 465 | int i, tries, size; | ||
| 466 | bool have_data = false; | ||
| 467 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 468 | |||
| 469 | if (!ctx->official_controller) { | ||
| 470 | #ifdef DEBUG_PS4_CALIBRATION | ||
| 471 | SDL_Log("Not an official controller, ignoring calibration"); | ||
| 472 | #endif | ||
| 473 | return false; | ||
| 474 | } | ||
| 475 | |||
| 476 | for (tries = 0; tries < 5; ++tries) { | ||
| 477 | // For Bluetooth controllers, this report switches them into advanced report mode | ||
| 478 | size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdGyroCalibration_USB, data, sizeof(data)); | ||
| 479 | if (size < 35) { | ||
| 480 | #ifdef DEBUG_PS4_CALIBRATION | ||
| 481 | SDL_Log("Short read of calibration data: %d, ignoring calibration", size); | ||
| 482 | #endif | ||
| 483 | return false; | ||
| 484 | } | ||
| 485 | |||
| 486 | if (device->is_bluetooth) { | ||
| 487 | size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdGyroCalibration_BT, data, sizeof(data)); | ||
| 488 | if (size < 35) { | ||
| 489 | #ifdef DEBUG_PS4_CALIBRATION | ||
| 490 | SDL_Log("Short read of calibration data: %d, ignoring calibration", size); | ||
| 491 | #endif | ||
| 492 | return false; | ||
| 493 | } | ||
| 494 | } | ||
| 495 | |||
| 496 | // In some cases this report returns all zeros. Usually immediately after connection with the PS4 Dongle | ||
| 497 | for (i = 0; i < size; ++i) { | ||
| 498 | if (data[i]) { | ||
| 499 | have_data = true; | ||
| 500 | break; | ||
| 501 | } | ||
| 502 | } | ||
| 503 | if (have_data) { | ||
| 504 | break; | ||
| 505 | } | ||
| 506 | |||
| 507 | SDL_Delay(2); | ||
| 508 | } | ||
| 509 | |||
| 510 | if (have_data) { | ||
| 511 | Sint16 sGyroPitchBias, sGyroYawBias, sGyroRollBias; | ||
| 512 | Sint16 sGyroPitchPlus, sGyroPitchMinus; | ||
| 513 | Sint16 sGyroYawPlus, sGyroYawMinus; | ||
| 514 | Sint16 sGyroRollPlus, sGyroRollMinus; | ||
| 515 | Sint16 sGyroSpeedPlus, sGyroSpeedMinus; | ||
| 516 | |||
| 517 | Sint16 sAccXPlus, sAccXMinus; | ||
| 518 | Sint16 sAccYPlus, sAccYMinus; | ||
| 519 | Sint16 sAccZPlus, sAccZMinus; | ||
| 520 | |||
| 521 | float flNumerator; | ||
| 522 | float flDenominator; | ||
| 523 | Sint16 sRange2g; | ||
| 524 | |||
| 525 | #ifdef DEBUG_PS4_CALIBRATION | ||
| 526 | HIDAPI_DumpPacket("PS4 calibration packet: size = %d", data, size); | ||
| 527 | #endif | ||
| 528 | |||
| 529 | sGyroPitchBias = LOAD16(data[1], data[2]); | ||
| 530 | sGyroYawBias = LOAD16(data[3], data[4]); | ||
| 531 | sGyroRollBias = LOAD16(data[5], data[6]); | ||
| 532 | |||
| 533 | if (device->is_bluetooth || ctx->is_dongle) { | ||
| 534 | sGyroPitchPlus = LOAD16(data[7], data[8]); | ||
| 535 | sGyroYawPlus = LOAD16(data[9], data[10]); | ||
| 536 | sGyroRollPlus = LOAD16(data[11], data[12]); | ||
| 537 | sGyroPitchMinus = LOAD16(data[13], data[14]); | ||
| 538 | sGyroYawMinus = LOAD16(data[15], data[16]); | ||
| 539 | sGyroRollMinus = LOAD16(data[17], data[18]); | ||
| 540 | } else { | ||
| 541 | sGyroPitchPlus = LOAD16(data[7], data[8]); | ||
| 542 | sGyroPitchMinus = LOAD16(data[9], data[10]); | ||
| 543 | sGyroYawPlus = LOAD16(data[11], data[12]); | ||
| 544 | sGyroYawMinus = LOAD16(data[13], data[14]); | ||
| 545 | sGyroRollPlus = LOAD16(data[15], data[16]); | ||
| 546 | sGyroRollMinus = LOAD16(data[17], data[18]); | ||
| 547 | } | ||
| 548 | |||
| 549 | sGyroSpeedPlus = LOAD16(data[19], data[20]); | ||
| 550 | sGyroSpeedMinus = LOAD16(data[21], data[22]); | ||
| 551 | |||
| 552 | sAccXPlus = LOAD16(data[23], data[24]); | ||
| 553 | sAccXMinus = LOAD16(data[25], data[26]); | ||
| 554 | sAccYPlus = LOAD16(data[27], data[28]); | ||
| 555 | sAccYMinus = LOAD16(data[29], data[30]); | ||
| 556 | sAccZPlus = LOAD16(data[31], data[32]); | ||
| 557 | sAccZMinus = LOAD16(data[33], data[34]); | ||
| 558 | |||
| 559 | flNumerator = (float)(sGyroSpeedPlus + sGyroSpeedMinus) * ctx->gyro_denominator / ctx->gyro_numerator; | ||
| 560 | flDenominator = (float)(SDL_abs(sGyroPitchPlus - sGyroPitchBias) + SDL_abs(sGyroPitchMinus - sGyroPitchBias)); | ||
| 561 | if (flDenominator != 0.0f) { | ||
| 562 | ctx->calibration[0].bias = sGyroPitchBias; | ||
| 563 | ctx->calibration[0].scale = flNumerator / flDenominator; | ||
| 564 | } | ||
| 565 | |||
| 566 | flDenominator = (float)(SDL_abs(sGyroYawPlus - sGyroYawBias) + SDL_abs(sGyroYawMinus - sGyroYawBias)); | ||
| 567 | if (flDenominator != 0.0f) { | ||
| 568 | ctx->calibration[1].bias = sGyroYawBias; | ||
| 569 | ctx->calibration[1].scale = flNumerator / flDenominator; | ||
| 570 | } | ||
| 571 | |||
| 572 | flDenominator = (float)(SDL_abs(sGyroRollPlus - sGyroRollBias) + SDL_abs(sGyroRollMinus - sGyroRollBias)); | ||
| 573 | if (flDenominator != 0.0f) { | ||
| 574 | ctx->calibration[2].bias = sGyroRollBias; | ||
| 575 | ctx->calibration[2].scale = flNumerator / flDenominator; | ||
| 576 | } | ||
| 577 | |||
| 578 | sRange2g = sAccXPlus - sAccXMinus; | ||
| 579 | ctx->calibration[3].bias = sAccXPlus - sRange2g / 2; | ||
| 580 | ctx->calibration[3].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g; | ||
| 581 | |||
| 582 | sRange2g = sAccYPlus - sAccYMinus; | ||
| 583 | ctx->calibration[4].bias = sAccYPlus - sRange2g / 2; | ||
| 584 | ctx->calibration[4].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g; | ||
| 585 | |||
| 586 | sRange2g = sAccZPlus - sAccZMinus; | ||
| 587 | ctx->calibration[5].bias = sAccZPlus - sRange2g / 2; | ||
| 588 | ctx->calibration[5].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g; | ||
| 589 | |||
| 590 | ctx->hardware_calibration = true; | ||
| 591 | for (i = 0; i < 6; ++i) { | ||
| 592 | #ifdef DEBUG_PS4_CALIBRATION | ||
| 593 | SDL_Log("calibration[%d] bias = %d, sensitivity = %f", i, ctx->calibration[i].bias, ctx->calibration[i].scale); | ||
| 594 | #endif | ||
| 595 | // Some controllers have a bad calibration | ||
| 596 | if (SDL_abs(ctx->calibration[i].bias) > 1024 || SDL_fabsf(1.0f - ctx->calibration[i].scale) > 0.5f) { | ||
| 597 | #ifdef DEBUG_PS4_CALIBRATION | ||
| 598 | SDL_Log("invalid calibration, ignoring"); | ||
| 599 | #endif | ||
| 600 | ctx->hardware_calibration = false; | ||
| 601 | } | ||
| 602 | } | ||
| 603 | } else { | ||
| 604 | #ifdef DEBUG_PS4_CALIBRATION | ||
| 605 | SDL_Log("Calibration data not available"); | ||
| 606 | #endif | ||
| 607 | } | ||
| 608 | return ctx->hardware_calibration; | ||
| 609 | } | ||
| 610 | |||
| 611 | static void HIDAPI_DriverPS4_LoadCalibrationData(SDL_HIDAPI_Device *device) | ||
| 612 | { | ||
| 613 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; | ||
| 614 | int i; | ||
| 615 | |||
| 616 | if (!HIDAPI_DriverPS4_LoadOfficialCalibrationData(device)) { | ||
| 617 | for (i = 0; i < SDL_arraysize(ctx->calibration); ++i) { | ||
| 618 | ctx->calibration[i].bias = 0; | ||
| 619 | ctx->calibration[i].scale = 1.0f; | ||
| 620 | } | ||
| 621 | } | ||
| 622 | |||
| 623 | // Scale the raw data to the units expected by SDL | ||
| 624 | for (i = 0; i < SDL_arraysize(ctx->calibration); ++i) { | ||
| 625 | double scale = ctx->calibration[i].scale; | ||
| 626 | |||
| 627 | if (i < 3) { | ||
| 628 | scale *= ((double)ctx->gyro_numerator / ctx->gyro_denominator) * SDL_PI_D / 180.0; | ||
| 629 | |||
| 630 | if (device->vendor_id == USB_VENDOR_SONY && | ||
| 631 | device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) { | ||
| 632 | // The Armor-X Pro seems to only deliver half the rotation it should | ||
| 633 | scale *= 2.0; | ||
| 634 | } | ||
| 635 | } else { | ||
| 636 | scale *= ((double)ctx->accel_numerator / ctx->accel_denominator) * SDL_STANDARD_GRAVITY; | ||
| 637 | |||
| 638 | if (device->vendor_id == USB_VENDOR_SONY && | ||
| 639 | device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) { | ||
| 640 | /* The Armor-X Pro seems to only deliver half the acceleration it should, | ||
| 641 | * and in the opposite direction on all axes */ | ||
| 642 | scale *= -2.0; | ||
| 643 | } | ||
| 644 | } | ||
| 645 | ctx->calibration[i].scale = (float)scale; | ||
| 646 | } | ||
| 647 | } | ||
| 648 | |||
| 649 | static float HIDAPI_DriverPS4_ApplyCalibrationData(SDL_DriverPS4_Context *ctx, int index, Sint16 value) | ||
| 650 | { | ||
| 651 | IMUCalibrationData *calibration = &ctx->calibration[index]; | ||
| 652 | |||
| 653 | return ((float)value - calibration->bias) * calibration->scale; | ||
| 654 | } | ||
| 655 | |||
| 656 | static bool HIDAPI_DriverPS4_UpdateEffects(SDL_DriverPS4_Context *ctx, bool application_usage) | ||
| 657 | { | ||
| 658 | DS4EffectsState_t effects; | ||
| 659 | |||
| 660 | SDL_zero(effects); | ||
| 661 | |||
| 662 | if (ctx->vibration_supported) { | ||
| 663 | effects.ucRumbleLeft = ctx->rumble_left; | ||
| 664 | effects.ucRumbleRight = ctx->rumble_right; | ||
| 665 | } | ||
| 666 | |||
| 667 | if (ctx->lightbar_supported) { | ||
| 668 | // Populate the LED state with the appropriate color from our lookup table | ||
| 669 | if (ctx->color_set) { | ||
| 670 | effects.ucLedRed = ctx->led_red; | ||
| 671 | effects.ucLedGreen = ctx->led_green; | ||
| 672 | effects.ucLedBlue = ctx->led_blue; | ||
| 673 | } else { | ||
| 674 | SetLedsForPlayerIndex(&effects, ctx->player_index); | ||
| 675 | } | ||
| 676 | } | ||
| 677 | return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage); | ||
| 678 | } | ||
| 679 | |||
| 680 | static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device) | ||
| 681 | { | ||
| 682 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; | ||
| 683 | |||
| 684 | if (ctx->enhanced_reports) { | ||
| 685 | // This is just a dummy packet that should have no effect, since we don't set the CRC | ||
| 686 | Uint8 data[78]; | ||
| 687 | |||
| 688 | SDL_zeroa(data); | ||
| 689 | |||
| 690 | data[0] = k_EPS4ReportIdBluetoothEffects; | ||
| 691 | data[1] = 0xC0; // Magic value HID + CRC | ||
| 692 | |||
| 693 | if (SDL_HIDAPI_LockRumble()) { | ||
| 694 | SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data)); | ||
| 695 | } | ||
| 696 | } else { | ||
| 697 | #if 0 /* The 8BitDo Zero 2 has perfect emulation of a PS4 controller, except it | ||
| 698 | * only sends reports when the state changes, so we can't disconnect here. | ||
| 699 | */ | ||
| 700 | // We can't even send an invalid effects packet, or it will put the controller in enhanced mode | ||
| 701 | if (device->num_joysticks > 0) { | ||
| 702 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 703 | } | ||
| 704 | #endif | ||
| 705 | } | ||
| 706 | } | ||
| 707 | |||
| 708 | static void HIDAPI_DriverPS4_SetEnhancedModeAvailable(SDL_DriverPS4_Context *ctx) | ||
| 709 | { | ||
| 710 | if (ctx->enhanced_mode_available) { | ||
| 711 | return; | ||
| 712 | } | ||
| 713 | ctx->enhanced_mode_available = true; | ||
| 714 | |||
| 715 | if (ctx->touchpad_supported) { | ||
| 716 | SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2); | ||
| 717 | ctx->report_touchpad = true; | ||
| 718 | } | ||
| 719 | |||
| 720 | if (ctx->sensors_supported) { | ||
| 721 | SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, (float)(1000 / ctx->report_interval)); | ||
| 722 | SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval)); | ||
| 723 | } | ||
| 724 | |||
| 725 | if (ctx->official_controller) { | ||
| 726 | ctx->report_battery = true; | ||
| 727 | } | ||
| 728 | |||
| 729 | HIDAPI_UpdateDeviceProperties(ctx->device); | ||
| 730 | } | ||
| 731 | |||
| 732 | static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx) | ||
| 733 | { | ||
| 734 | HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx); | ||
| 735 | |||
| 736 | if (!ctx->enhanced_mode) { | ||
| 737 | ctx->enhanced_mode = true; | ||
| 738 | |||
| 739 | // Switch into enhanced report mode | ||
| 740 | HIDAPI_DriverPS4_UpdateEffects(ctx, false); | ||
| 741 | } | ||
| 742 | } | ||
| 743 | |||
| 744 | static void HIDAPI_DriverPS4_SetEnhancedReportHint(SDL_DriverPS4_Context *ctx, HIDAPI_PS4_EnhancedReportHint enhanced_report_hint) | ||
| 745 | { | ||
| 746 | switch (enhanced_report_hint) { | ||
| 747 | case PS4_ENHANCED_REPORT_HINT_OFF: | ||
| 748 | // Nothing to do, enhanced mode is a one-way ticket | ||
| 749 | break; | ||
| 750 | case PS4_ENHANCED_REPORT_HINT_ON: | ||
| 751 | HIDAPI_DriverPS4_SetEnhancedMode(ctx); | ||
| 752 | break; | ||
| 753 | case PS4_ENHANCED_REPORT_HINT_AUTO: | ||
| 754 | HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx); | ||
| 755 | break; | ||
| 756 | } | ||
| 757 | ctx->enhanced_report_hint = enhanced_report_hint; | ||
| 758 | } | ||
| 759 | |||
| 760 | static void HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS4_Context *ctx) | ||
| 761 | { | ||
| 762 | ctx->enhanced_reports = true; | ||
| 763 | |||
| 764 | if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) { | ||
| 765 | HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON); | ||
| 766 | } | ||
| 767 | } | ||
| 768 | |||
| 769 | static void HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS4_Context *ctx) | ||
| 770 | { | ||
| 771 | if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) { | ||
| 772 | HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON); | ||
| 773 | } | ||
| 774 | } | ||
| 775 | |||
| 776 | static void SDLCALL SDL_PS4EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 777 | { | ||
| 778 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata; | ||
| 779 | |||
| 780 | if (ctx->device->is_bluetooth) { | ||
| 781 | if (hint && SDL_strcasecmp(hint, "auto") == 0) { | ||
| 782 | HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_AUTO); | ||
| 783 | } else if (SDL_GetStringBoolean(hint, true)) { | ||
| 784 | HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON); | ||
| 785 | } else { | ||
| 786 | HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_OFF); | ||
| 787 | } | ||
| 788 | } else { | ||
| 789 | HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON); | ||
| 790 | } | ||
| 791 | } | ||
| 792 | |||
| 793 | static void SDLCALL SDL_PS4ReportIntervalHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 794 | { | ||
| 795 | const int DEFAULT_REPORT_INTERVAL = 4; | ||
| 796 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata; | ||
| 797 | int new_report_interval = DEFAULT_REPORT_INTERVAL; | ||
| 798 | |||
| 799 | if (hint) { | ||
| 800 | int report_interval = SDL_atoi(hint); | ||
| 801 | switch (report_interval) { | ||
| 802 | case 1: | ||
| 803 | case 2: | ||
| 804 | case 4: | ||
| 805 | // Valid values | ||
| 806 | new_report_interval = report_interval; | ||
| 807 | break; | ||
| 808 | default: | ||
| 809 | break; | ||
| 810 | } | ||
| 811 | } | ||
| 812 | |||
| 813 | if (new_report_interval != ctx->report_interval) { | ||
| 814 | ctx->report_interval = (Uint8)new_report_interval; | ||
| 815 | |||
| 816 | HIDAPI_DriverPS4_UpdateEffects(ctx, false); | ||
| 817 | SDL_LockJoysticks(); | ||
| 818 | SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_GYRO, (float)(1000 / ctx->report_interval)); | ||
| 819 | SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval)); | ||
| 820 | SDL_UnlockJoysticks(); | ||
| 821 | } | ||
| 822 | } | ||
| 823 | |||
| 824 | static void HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 825 | { | ||
| 826 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; | ||
| 827 | |||
| 828 | if (!ctx->joystick) { | ||
| 829 | return; | ||
| 830 | } | ||
| 831 | |||
| 832 | ctx->player_index = player_index; | ||
| 833 | |||
| 834 | // This will set the new LED state based on the new player index | ||
| 835 | // SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode | ||
| 836 | HIDAPI_DriverPS4_UpdateEffects(ctx, false); | ||
| 837 | } | ||
| 838 | |||
| 839 | static bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 840 | { | ||
| 841 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; | ||
| 842 | |||
| 843 | SDL_AssertJoysticksLocked(); | ||
| 844 | |||
| 845 | ctx->joystick = joystick; | ||
| 846 | ctx->last_packet = SDL_GetTicks(); | ||
| 847 | ctx->report_sensors = false; | ||
| 848 | ctx->report_touchpad = false; | ||
| 849 | ctx->rumble_left = 0; | ||
| 850 | ctx->rumble_right = 0; | ||
| 851 | ctx->color_set = false; | ||
| 852 | SDL_zero(ctx->last_state); | ||
| 853 | |||
| 854 | // Initialize player index (needed for setting LEDs) | ||
| 855 | ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); | ||
| 856 | |||
| 857 | // Initialize the joystick capabilities | ||
| 858 | joystick->nbuttons = 11; | ||
| 859 | if (ctx->touchpad_supported) { | ||
| 860 | joystick->nbuttons += 1; | ||
| 861 | } | ||
| 862 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 863 | joystick->nhats = 1; | ||
| 864 | |||
| 865 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL, | ||
| 866 | SDL_PS4ReportIntervalHintChanged, ctx); | ||
| 867 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, | ||
| 868 | SDL_PS4EnhancedReportsChanged, ctx); | ||
| 869 | return true; | ||
| 870 | } | ||
| 871 | |||
| 872 | static bool HIDAPI_DriverPS4_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 873 | { | ||
| 874 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; | ||
| 875 | |||
| 876 | if (!ctx->vibration_supported) { | ||
| 877 | return SDL_Unsupported(); | ||
| 878 | } | ||
| 879 | |||
| 880 | ctx->rumble_left = (low_frequency_rumble >> 8); | ||
| 881 | ctx->rumble_right = (high_frequency_rumble >> 8); | ||
| 882 | |||
| 883 | return HIDAPI_DriverPS4_UpdateEffects(ctx, true); | ||
| 884 | } | ||
| 885 | |||
| 886 | static bool HIDAPI_DriverPS4_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 887 | { | ||
| 888 | return SDL_Unsupported(); | ||
| 889 | } | ||
| 890 | |||
| 891 | static Uint32 HIDAPI_DriverPS4_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 892 | { | ||
| 893 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; | ||
| 894 | Uint32 result = 0; | ||
| 895 | |||
| 896 | if (ctx->enhanced_mode_available) { | ||
| 897 | if (ctx->lightbar_supported) { | ||
| 898 | result |= SDL_JOYSTICK_CAP_RGB_LED; | ||
| 899 | } | ||
| 900 | if (ctx->vibration_supported) { | ||
| 901 | result |= SDL_JOYSTICK_CAP_RUMBLE; | ||
| 902 | } | ||
| 903 | } | ||
| 904 | |||
| 905 | return result; | ||
| 906 | } | ||
| 907 | |||
| 908 | static bool HIDAPI_DriverPS4_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 909 | { | ||
| 910 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; | ||
| 911 | |||
| 912 | if (!ctx->lightbar_supported) { | ||
| 913 | return SDL_Unsupported(); | ||
| 914 | } | ||
| 915 | |||
| 916 | ctx->color_set = true; | ||
| 917 | ctx->led_red = red; | ||
| 918 | ctx->led_green = green; | ||
| 919 | ctx->led_blue = blue; | ||
| 920 | |||
| 921 | return HIDAPI_DriverPS4_UpdateEffects(ctx, true); | ||
| 922 | } | ||
| 923 | |||
| 924 | static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage) | ||
| 925 | { | ||
| 926 | Uint8 data[78]; | ||
| 927 | int report_size, offset; | ||
| 928 | |||
| 929 | if (!ctx->effects_supported) { | ||
| 930 | // We shouldn't be sending packets to this controller | ||
| 931 | return SDL_Unsupported(); | ||
| 932 | } | ||
| 933 | |||
| 934 | if (!ctx->enhanced_mode) { | ||
| 935 | if (application_usage) { | ||
| 936 | HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx); | ||
| 937 | } | ||
| 938 | |||
| 939 | if (!ctx->enhanced_mode) { | ||
| 940 | // We're not in enhanced mode, effects aren't allowed | ||
| 941 | return SDL_Unsupported(); | ||
| 942 | } | ||
| 943 | } | ||
| 944 | |||
| 945 | SDL_zeroa(data); | ||
| 946 | |||
| 947 | if (ctx->device->is_bluetooth && ctx->official_controller) { | ||
| 948 | data[0] = k_EPS4ReportIdBluetoothEffects; | ||
| 949 | data[1] = 0xC0 | ctx->report_interval; // Magic value HID + CRC, also sets update interval | ||
| 950 | data[3] = 0x03; // 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval | ||
| 951 | |||
| 952 | report_size = 78; | ||
| 953 | offset = 6; | ||
| 954 | } else { | ||
| 955 | data[0] = k_EPS4ReportIdUsbEffects; | ||
| 956 | data[1] = 0x07; // Magic value | ||
| 957 | |||
| 958 | report_size = 32; | ||
| 959 | offset = 4; | ||
| 960 | } | ||
| 961 | |||
| 962 | SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size)); | ||
| 963 | |||
| 964 | if (ctx->device->is_bluetooth) { | ||
| 965 | // Bluetooth reports need a CRC at the end of the packet (at least on Linux) | ||
| 966 | Uint8 ubHdr = 0xA2; // hidp header is part of the CRC calculation | ||
| 967 | Uint32 unCRC; | ||
| 968 | unCRC = SDL_crc32(0, &ubHdr, 1); | ||
| 969 | unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC))); | ||
| 970 | SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC)); | ||
| 971 | } | ||
| 972 | |||
| 973 | if (SDL_HIDAPI_SendRumble(ctx->device, data, report_size) != report_size) { | ||
| 974 | return SDL_SetError("Couldn't send rumble packet"); | ||
| 975 | } | ||
| 976 | return true; | ||
| 977 | } | ||
| 978 | |||
| 979 | static bool HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) | ||
| 980 | { | ||
| 981 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; | ||
| 982 | |||
| 983 | return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, effect, size, true); | ||
| 984 | } | ||
| 985 | |||
| 986 | static bool HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 987 | { | ||
| 988 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; | ||
| 989 | |||
| 990 | HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx); | ||
| 991 | |||
| 992 | if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) { | ||
| 993 | return SDL_Unsupported(); | ||
| 994 | } | ||
| 995 | |||
| 996 | if (enabled) { | ||
| 997 | HIDAPI_DriverPS4_LoadCalibrationData(device); | ||
| 998 | } | ||
| 999 | ctx->report_sensors = enabled; | ||
| 1000 | |||
| 1001 | return true; | ||
| 1002 | } | ||
| 1003 | |||
| 1004 | static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS4_Context *ctx, PS4StatePacket_t *packet, int size) | ||
| 1005 | { | ||
| 1006 | static const float TOUCHPAD_SCALEX = 1.0f / 1920; | ||
| 1007 | static const float TOUCHPAD_SCALEY = 1.0f / 920; // This is noted as being 944 resolution, but 920 feels better | ||
| 1008 | Sint16 axis; | ||
| 1009 | bool touchpad_down; | ||
| 1010 | int touchpad_x, touchpad_y; | ||
| 1011 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 1012 | |||
| 1013 | if (size > 9 && ctx->report_touchpad && ctx->enhanced_reports) { | ||
| 1014 | touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0); | ||
| 1015 | touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8); | ||
| 1016 | touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4); | ||
| 1017 | SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); | ||
| 1018 | |||
| 1019 | touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0); | ||
| 1020 | touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8); | ||
| 1021 | touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4); | ||
| 1022 | SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | if (ctx->last_state.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) { | ||
| 1026 | { | ||
| 1027 | Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4); | ||
| 1028 | |||
| 1029 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0)); | ||
| 1030 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0)); | ||
| 1031 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0)); | ||
| 1032 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0)); | ||
| 1033 | } | ||
| 1034 | { | ||
| 1035 | Uint8 hat; | ||
| 1036 | Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F); | ||
| 1037 | |||
| 1038 | switch (data) { | ||
| 1039 | case 0: | ||
| 1040 | hat = SDL_HAT_UP; | ||
| 1041 | break; | ||
| 1042 | case 1: | ||
| 1043 | hat = SDL_HAT_RIGHTUP; | ||
| 1044 | break; | ||
| 1045 | case 2: | ||
| 1046 | hat = SDL_HAT_RIGHT; | ||
| 1047 | break; | ||
| 1048 | case 3: | ||
| 1049 | hat = SDL_HAT_RIGHTDOWN; | ||
| 1050 | break; | ||
| 1051 | case 4: | ||
| 1052 | hat = SDL_HAT_DOWN; | ||
| 1053 | break; | ||
| 1054 | case 5: | ||
| 1055 | hat = SDL_HAT_LEFTDOWN; | ||
| 1056 | break; | ||
| 1057 | case 6: | ||
| 1058 | hat = SDL_HAT_LEFT; | ||
| 1059 | break; | ||
| 1060 | case 7: | ||
| 1061 | hat = SDL_HAT_LEFTUP; | ||
| 1062 | break; | ||
| 1063 | default: | ||
| 1064 | hat = SDL_HAT_CENTERED; | ||
| 1065 | break; | ||
| 1066 | } | ||
| 1067 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 1068 | } | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | if (ctx->last_state.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) { | ||
| 1072 | Uint8 data = packet->rgucButtonsHatAndCounter[1]; | ||
| 1073 | |||
| 1074 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0)); | ||
| 1075 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0)); | ||
| 1076 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0)); | ||
| 1077 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0)); | ||
| 1078 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0)); | ||
| 1079 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0)); | ||
| 1080 | } | ||
| 1081 | |||
| 1082 | /* 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 | ||
| 1083 | trigger is evaluated | ||
| 1084 | */ | ||
| 1085 | if (packet->rgucButtonsHatAndCounter[1] & 0x0C) { | ||
| 1086 | Uint8 data = packet->rgucButtonsHatAndCounter[1]; | ||
| 1087 | packet->ucTriggerLeft = (data & 0x04) && packet->ucTriggerLeft == 0 ? 255 : packet->ucTriggerLeft; | ||
| 1088 | packet->ucTriggerRight = (data & 0x08) && packet->ucTriggerRight == 0 ? 255 : packet->ucTriggerRight; | ||
| 1089 | } | ||
| 1090 | |||
| 1091 | if (ctx->last_state.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) { | ||
| 1092 | Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03); | ||
| 1093 | |||
| 1094 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0)); | ||
| 1095 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD, ((data & 0x02) != 0)); | ||
| 1096 | } | ||
| 1097 | |||
| 1098 | axis = ((int)packet->ucTriggerLeft * 257) - 32768; | ||
| 1099 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 1100 | axis = ((int)packet->ucTriggerRight * 257) - 32768; | ||
| 1101 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 1102 | axis = ((int)packet->ucLeftJoystickX * 257) - 32768; | ||
| 1103 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 1104 | axis = ((int)packet->ucLeftJoystickY * 257) - 32768; | ||
| 1105 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 1106 | axis = ((int)packet->ucRightJoystickX * 257) - 32768; | ||
| 1107 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 1108 | axis = ((int)packet->ucRightJoystickY * 257) - 32768; | ||
| 1109 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 1110 | |||
| 1111 | if (size > 9 && ctx->report_battery && ctx->enhanced_reports) { | ||
| 1112 | SDL_PowerState state; | ||
| 1113 | int percent; | ||
| 1114 | Uint8 level = (packet->ucBatteryLevel & 0x0F); | ||
| 1115 | |||
| 1116 | if (packet->ucBatteryLevel & 0x10) { | ||
| 1117 | if (level <= 10) { | ||
| 1118 | state = SDL_POWERSTATE_CHARGING; | ||
| 1119 | percent = SDL_min(level * 10 + 5, 100); | ||
| 1120 | } else if (level == 11) { | ||
| 1121 | state = SDL_POWERSTATE_CHARGED; | ||
| 1122 | percent = 100; | ||
| 1123 | } else { | ||
| 1124 | state = SDL_POWERSTATE_UNKNOWN; | ||
| 1125 | percent = 0; | ||
| 1126 | } | ||
| 1127 | } else { | ||
| 1128 | state = SDL_POWERSTATE_ON_BATTERY; | ||
| 1129 | percent = SDL_min(level * 10 + 5, 100); | ||
| 1130 | } | ||
| 1131 | SDL_SendJoystickPowerInfo(joystick, state, percent); | ||
| 1132 | } | ||
| 1133 | |||
| 1134 | if (size > 9 && ctx->report_sensors) { | ||
| 1135 | Uint16 tick; | ||
| 1136 | Uint16 delta; | ||
| 1137 | Uint64 sensor_timestamp; | ||
| 1138 | float data[3]; | ||
| 1139 | |||
| 1140 | tick = LOAD16(packet->rgucTimestamp[0], packet->rgucTimestamp[1]); | ||
| 1141 | if (ctx->last_tick < tick) { | ||
| 1142 | delta = (tick - ctx->last_tick); | ||
| 1143 | } else { | ||
| 1144 | delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1); | ||
| 1145 | } | ||
| 1146 | ctx->sensor_ticks += delta; | ||
| 1147 | ctx->last_tick = tick; | ||
| 1148 | |||
| 1149 | // Sensor timestamp is in 5.33us units | ||
| 1150 | sensor_timestamp = (ctx->sensor_ticks * SDL_NS_PER_US * 16) / 3; | ||
| 1151 | |||
| 1152 | data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1])); | ||
| 1153 | data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1])); | ||
| 1154 | data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1])); | ||
| 1155 | SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, data, 3); | ||
| 1156 | |||
| 1157 | data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1])); | ||
| 1158 | data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1])); | ||
| 1159 | data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1])); | ||
| 1160 | SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3); | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state)); | ||
| 1164 | } | ||
| 1165 | |||
| 1166 | static bool VerifyCRC(Uint8 *data, int size) | ||
| 1167 | { | ||
| 1168 | Uint8 ubHdr = 0xA1; // hidp header is part of the CRC calculation | ||
| 1169 | Uint32 unCRC, unPacketCRC; | ||
| 1170 | Uint8 *packetCRC = data + size - sizeof(unPacketCRC); | ||
| 1171 | unCRC = SDL_crc32(0, &ubHdr, 1); | ||
| 1172 | unCRC = SDL_crc32(unCRC, data, (size_t)(size - sizeof(unCRC))); | ||
| 1173 | |||
| 1174 | unPacketCRC = LOAD32(packetCRC[0], | ||
| 1175 | packetCRC[1], | ||
| 1176 | packetCRC[2], | ||
| 1177 | packetCRC[3]); | ||
| 1178 | return (unCRC == unPacketCRC); | ||
| 1179 | } | ||
| 1180 | |||
| 1181 | static bool HIDAPI_DriverPS4_IsPacketValid(SDL_DriverPS4_Context *ctx, Uint8 *data, int size) | ||
| 1182 | { | ||
| 1183 | switch (data[0]) { | ||
| 1184 | case k_EPS4ReportIdUsbState: | ||
| 1185 | if (size == 10) { | ||
| 1186 | // This is non-enhanced mode, this packet is fine | ||
| 1187 | return true; | ||
| 1188 | } | ||
| 1189 | |||
| 1190 | if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS4StatePacket_t))) { | ||
| 1191 | // The report timestamp doesn't change when the controller isn't connected | ||
| 1192 | PS4StatePacket_t *packet = (PS4StatePacket_t *)&data[1]; | ||
| 1193 | if (SDL_memcmp(packet->rgucTimestamp, ctx->last_state.rgucTimestamp, sizeof(packet->rgucTimestamp)) == 0) { | ||
| 1194 | return false; | ||
| 1195 | } | ||
| 1196 | if (ctx->last_state.rgucAccelX[0] == 0 && ctx->last_state.rgucAccelX[1] == 0 && | ||
| 1197 | ctx->last_state.rgucAccelY[0] == 0 && ctx->last_state.rgucAccelY[1] == 0 && | ||
| 1198 | ctx->last_state.rgucAccelZ[0] == 0 && ctx->last_state.rgucAccelZ[1] == 0) { | ||
| 1199 | // We don't have any state to compare yet, go ahead and copy it | ||
| 1200 | SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS4StatePacket_t)); | ||
| 1201 | return false; | ||
| 1202 | } | ||
| 1203 | } | ||
| 1204 | |||
| 1205 | /* In the case of a DS4 USB dongle, bit[2] of byte 31 indicates if a DS4 is actually connected (indicated by '0'). | ||
| 1206 | * For non-dongle, this bit is always 0 (connected). | ||
| 1207 | * This is usually the ID over USB, but the DS4v2 that started shipping with the PS4 Slim will also send this | ||
| 1208 | * packet over BT with a size of 128 | ||
| 1209 | */ | ||
| 1210 | if (size >= 64 && !(data[31] & 0x04)) { | ||
| 1211 | return true; | ||
| 1212 | } | ||
| 1213 | break; | ||
| 1214 | case k_EPS4ReportIdBluetoothState1: | ||
| 1215 | case k_EPS4ReportIdBluetoothState2: | ||
| 1216 | case k_EPS4ReportIdBluetoothState3: | ||
| 1217 | case k_EPS4ReportIdBluetoothState4: | ||
| 1218 | case k_EPS4ReportIdBluetoothState5: | ||
| 1219 | case k_EPS4ReportIdBluetoothState6: | ||
| 1220 | case k_EPS4ReportIdBluetoothState7: | ||
| 1221 | case k_EPS4ReportIdBluetoothState8: | ||
| 1222 | case k_EPS4ReportIdBluetoothState9: | ||
| 1223 | // Bluetooth state packets have two additional bytes at the beginning, the first notes if HID data is present | ||
| 1224 | if (size >= 78 && (data[1] & 0x80)) { | ||
| 1225 | if (VerifyCRC(data, 78)) { | ||
| 1226 | ++ctx->valid_crc_packets; | ||
| 1227 | } else { | ||
| 1228 | if (ctx->valid_crc_packets > 0) { | ||
| 1229 | --ctx->valid_crc_packets; | ||
| 1230 | } | ||
| 1231 | if (ctx->valid_crc_packets >= 3) { | ||
| 1232 | // We're generally getting valid CRC, but failed one | ||
| 1233 | return false; | ||
| 1234 | } | ||
| 1235 | } | ||
| 1236 | return true; | ||
| 1237 | } | ||
| 1238 | break; | ||
| 1239 | default: | ||
| 1240 | break; | ||
| 1241 | } | ||
| 1242 | return false; | ||
| 1243 | } | ||
| 1244 | |||
| 1245 | static bool HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 1246 | { | ||
| 1247 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; | ||
| 1248 | SDL_Joystick *joystick = NULL; | ||
| 1249 | Uint8 data[USB_PACKET_LENGTH * 2]; | ||
| 1250 | int size; | ||
| 1251 | int packet_count = 0; | ||
| 1252 | Uint64 now = SDL_GetTicks(); | ||
| 1253 | |||
| 1254 | if (device->num_joysticks > 0) { | ||
| 1255 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 1256 | } | ||
| 1257 | |||
| 1258 | while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { | ||
| 1259 | #ifdef DEBUG_PS4_PROTOCOL | ||
| 1260 | HIDAPI_DumpPacket("PS4 packet: size = %d", data, size); | ||
| 1261 | #endif | ||
| 1262 | if (!HIDAPI_DriverPS4_IsPacketValid(ctx, data, size)) { | ||
| 1263 | continue; | ||
| 1264 | } | ||
| 1265 | |||
| 1266 | ++packet_count; | ||
| 1267 | ctx->last_packet = now; | ||
| 1268 | |||
| 1269 | if (!joystick) { | ||
| 1270 | continue; | ||
| 1271 | } | ||
| 1272 | |||
| 1273 | switch (data[0]) { | ||
| 1274 | case k_EPS4ReportIdUsbState: | ||
| 1275 | HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[1], size - 1); | ||
| 1276 | break; | ||
| 1277 | case k_EPS4ReportIdBluetoothState1: | ||
| 1278 | case k_EPS4ReportIdBluetoothState2: | ||
| 1279 | case k_EPS4ReportIdBluetoothState3: | ||
| 1280 | case k_EPS4ReportIdBluetoothState4: | ||
| 1281 | case k_EPS4ReportIdBluetoothState5: | ||
| 1282 | case k_EPS4ReportIdBluetoothState6: | ||
| 1283 | case k_EPS4ReportIdBluetoothState7: | ||
| 1284 | case k_EPS4ReportIdBluetoothState8: | ||
| 1285 | case k_EPS4ReportIdBluetoothState9: | ||
| 1286 | // This is the extended report, we can enable effects now in auto mode | ||
| 1287 | HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(ctx); | ||
| 1288 | |||
| 1289 | // Bluetooth state packets have two additional bytes at the beginning, the first notes if HID is present | ||
| 1290 | HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[3], size - 3); | ||
| 1291 | break; | ||
| 1292 | default: | ||
| 1293 | #ifdef DEBUG_JOYSTICK | ||
| 1294 | SDL_Log("Unknown PS4 packet: 0x%.2x", data[0]); | ||
| 1295 | #endif | ||
| 1296 | break; | ||
| 1297 | } | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | if (device->is_bluetooth) { | ||
| 1301 | if (packet_count == 0) { | ||
| 1302 | // Check to see if it looks like the device disconnected | ||
| 1303 | if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) { | ||
| 1304 | // Send an empty output report to tickle the Bluetooth stack | ||
| 1305 | HIDAPI_DriverPS4_TickleBluetooth(device); | ||
| 1306 | ctx->last_packet = now; | ||
| 1307 | } | ||
| 1308 | } else { | ||
| 1309 | // Reconnect the Bluetooth device once the USB device is gone | ||
| 1310 | if (device->num_joysticks == 0 && | ||
| 1311 | !HIDAPI_HasConnectedUSBDevice(device->serial)) { | ||
| 1312 | HIDAPI_JoystickConnected(device, NULL); | ||
| 1313 | } | ||
| 1314 | } | ||
| 1315 | } | ||
| 1316 | |||
| 1317 | if (ctx->is_dongle || ctx->is_nacon_dongle) { | ||
| 1318 | if (packet_count == 0) { | ||
| 1319 | if (device->num_joysticks > 0) { | ||
| 1320 | // Check to see if it looks like the device disconnected | ||
| 1321 | if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) { | ||
| 1322 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 1323 | } | ||
| 1324 | } | ||
| 1325 | } else { | ||
| 1326 | if (device->num_joysticks == 0) { | ||
| 1327 | char serial[18]; | ||
| 1328 | size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data)); | ||
| 1329 | if (size >= 7 && (data[1] || data[2] || data[3] || data[4] || data[5] || data[6])) { | ||
| 1330 | (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", | ||
| 1331 | data[6], data[5], data[4], data[3], data[2], data[1]); | ||
| 1332 | HIDAPI_SetDeviceSerial(device, serial); | ||
| 1333 | } | ||
| 1334 | HIDAPI_JoystickConnected(device, NULL); | ||
| 1335 | } | ||
| 1336 | } | ||
| 1337 | } | ||
| 1338 | |||
| 1339 | if (packet_count == 0 && size < 0 && device->num_joysticks > 0) { | ||
| 1340 | // Read error, device is disconnected | ||
| 1341 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 1342 | } | ||
| 1343 | return (size >= 0); | ||
| 1344 | } | ||
| 1345 | |||
| 1346 | static void HIDAPI_DriverPS4_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1347 | { | ||
| 1348 | SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; | ||
| 1349 | |||
| 1350 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL, | ||
| 1351 | SDL_PS4ReportIntervalHintChanged, ctx); | ||
| 1352 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, | ||
| 1353 | SDL_PS4EnhancedReportsChanged, ctx); | ||
| 1354 | |||
| 1355 | ctx->joystick = NULL; | ||
| 1356 | |||
| 1357 | ctx->report_sensors = false; | ||
| 1358 | ctx->enhanced_mode = false; | ||
| 1359 | ctx->enhanced_mode_available = false; | ||
| 1360 | } | ||
| 1361 | |||
| 1362 | static void HIDAPI_DriverPS4_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 1363 | { | ||
| 1364 | } | ||
| 1365 | |||
| 1366 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 = { | ||
| 1367 | SDL_HINT_JOYSTICK_HIDAPI_PS4, | ||
| 1368 | true, | ||
| 1369 | HIDAPI_DriverPS4_RegisterHints, | ||
| 1370 | HIDAPI_DriverPS4_UnregisterHints, | ||
| 1371 | HIDAPI_DriverPS4_IsEnabled, | ||
| 1372 | HIDAPI_DriverPS4_IsSupportedDevice, | ||
| 1373 | HIDAPI_DriverPS4_InitDevice, | ||
| 1374 | HIDAPI_DriverPS4_GetDevicePlayerIndex, | ||
| 1375 | HIDAPI_DriverPS4_SetDevicePlayerIndex, | ||
| 1376 | HIDAPI_DriverPS4_UpdateDevice, | ||
| 1377 | HIDAPI_DriverPS4_OpenJoystick, | ||
| 1378 | HIDAPI_DriverPS4_RumbleJoystick, | ||
| 1379 | HIDAPI_DriverPS4_RumbleJoystickTriggers, | ||
| 1380 | HIDAPI_DriverPS4_GetJoystickCapabilities, | ||
| 1381 | HIDAPI_DriverPS4_SetJoystickLED, | ||
| 1382 | HIDAPI_DriverPS4_SendJoystickEffect, | ||
| 1383 | HIDAPI_DriverPS4_SetJoystickSensorsEnabled, | ||
| 1384 | HIDAPI_DriverPS4_CloseJoystick, | ||
| 1385 | HIDAPI_DriverPS4_FreeDevice, | ||
| 1386 | }; | ||
| 1387 | |||
| 1388 | #endif // SDL_JOYSTICK_HIDAPI_PS4 | ||
| 1389 | |||
| 1390 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../../SDL_hints_c.h" | ||
| 26 | #include "../SDL_sysjoystick.h" | ||
| 27 | #include "SDL_hidapijoystick_c.h" | ||
| 28 | #include "SDL_hidapi_rumble.h" | ||
| 29 | |||
| 30 | #ifdef SDL_JOYSTICK_HIDAPI_PS5 | ||
| 31 | |||
| 32 | // Define this if you want to log all packets from the controller | ||
| 33 | #if 0 | ||
| 34 | #define DEBUG_PS5_PROTOCOL | ||
| 35 | #endif | ||
| 36 | |||
| 37 | // Define this if you want to log calibration data | ||
| 38 | #if 0 | ||
| 39 | #define DEBUG_PS5_CALIBRATION | ||
| 40 | #endif | ||
| 41 | |||
| 42 | #define GYRO_RES_PER_DEGREE 1024.0f | ||
| 43 | #define ACCEL_RES_PER_G 8192.0f | ||
| 44 | #define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500 | ||
| 45 | |||
| 46 | #define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) | ||
| 47 | #define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ | ||
| 48 | (((Uint32)(B)) << 8) | \ | ||
| 49 | (((Uint32)(C)) << 16) | \ | ||
| 50 | (((Uint32)(D)) << 24)) | ||
| 51 | |||
| 52 | enum | ||
| 53 | { | ||
| 54 | SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD = 11, | ||
| 55 | SDL_GAMEPAD_BUTTON_PS5_MICROPHONE, | ||
| 56 | SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION, | ||
| 57 | SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION, | ||
| 58 | SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE, | ||
| 59 | SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE | ||
| 60 | }; | ||
| 61 | |||
| 62 | typedef enum | ||
| 63 | { | ||
| 64 | k_EPS5ReportIdState = 0x01, | ||
| 65 | k_EPS5ReportIdUsbEffects = 0x02, | ||
| 66 | k_EPS5ReportIdBluetoothEffects = 0x31, | ||
| 67 | k_EPS5ReportIdBluetoothState = 0x31, | ||
| 68 | } EPS5ReportId; | ||
| 69 | |||
| 70 | typedef enum | ||
| 71 | { | ||
| 72 | k_EPS5FeatureReportIdCapabilities = 0x03, | ||
| 73 | k_EPS5FeatureReportIdCalibration = 0x05, | ||
| 74 | k_EPS5FeatureReportIdSerialNumber = 0x09, | ||
| 75 | k_EPS5FeatureReportIdFirmwareInfo = 0x20, | ||
| 76 | } EPS5FeatureReportId; | ||
| 77 | |||
| 78 | typedef struct | ||
| 79 | { | ||
| 80 | Uint8 ucLeftJoystickX; | ||
| 81 | Uint8 ucLeftJoystickY; | ||
| 82 | Uint8 ucRightJoystickX; | ||
| 83 | Uint8 ucRightJoystickY; | ||
| 84 | Uint8 rgucButtonsHatAndCounter[3]; | ||
| 85 | Uint8 ucTriggerLeft; | ||
| 86 | Uint8 ucTriggerRight; | ||
| 87 | } PS5SimpleStatePacket_t; | ||
| 88 | |||
| 89 | typedef struct | ||
| 90 | { | ||
| 91 | Uint8 ucLeftJoystickX; // 0 | ||
| 92 | Uint8 ucLeftJoystickY; // 1 | ||
| 93 | Uint8 ucRightJoystickX; // 2 | ||
| 94 | Uint8 ucRightJoystickY; // 3 | ||
| 95 | Uint8 ucTriggerLeft; // 4 | ||
| 96 | Uint8 ucTriggerRight; // 5 | ||
| 97 | Uint8 ucCounter; // 6 | ||
| 98 | Uint8 rgucButtonsAndHat[4]; // 7 | ||
| 99 | Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian | ||
| 100 | Uint8 rgucGyroX[2]; // 15 | ||
| 101 | Uint8 rgucGyroY[2]; // 17 | ||
| 102 | Uint8 rgucGyroZ[2]; // 19 | ||
| 103 | Uint8 rgucAccelX[2]; // 21 | ||
| 104 | Uint8 rgucAccelY[2]; // 23 | ||
| 105 | Uint8 rgucAccelZ[2]; // 25 | ||
| 106 | Uint8 rgucSensorTimestamp[4]; // 27 - 16/32 bit little endian | ||
| 107 | |||
| 108 | } PS5StatePacketCommon_t; | ||
| 109 | |||
| 110 | typedef struct | ||
| 111 | { | ||
| 112 | Uint8 ucLeftJoystickX; // 0 | ||
| 113 | Uint8 ucLeftJoystickY; // 1 | ||
| 114 | Uint8 ucRightJoystickX; // 2 | ||
| 115 | Uint8 ucRightJoystickY; // 3 | ||
| 116 | Uint8 ucTriggerLeft; // 4 | ||
| 117 | Uint8 ucTriggerRight; // 5 | ||
| 118 | Uint8 ucCounter; // 6 | ||
| 119 | Uint8 rgucButtonsAndHat[4]; // 7 | ||
| 120 | Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian | ||
| 121 | Uint8 rgucGyroX[2]; // 15 | ||
| 122 | Uint8 rgucGyroY[2]; // 17 | ||
| 123 | Uint8 rgucGyroZ[2]; // 19 | ||
| 124 | Uint8 rgucAccelX[2]; // 21 | ||
| 125 | Uint8 rgucAccelY[2]; // 23 | ||
| 126 | Uint8 rgucAccelZ[2]; // 25 | ||
| 127 | Uint8 rgucSensorTimestamp[4]; // 27 - 32 bit little endian | ||
| 128 | Uint8 ucSensorTemp; // 31 | ||
| 129 | Uint8 ucTouchpadCounter1; // 32 - high bit clear + counter | ||
| 130 | Uint8 rgucTouchpadData1[3]; // 33 - X/Y, 12 bits per axis | ||
| 131 | Uint8 ucTouchpadCounter2; // 36 - high bit clear + counter | ||
| 132 | Uint8 rgucTouchpadData2[3]; // 37 - X/Y, 12 bits per axis | ||
| 133 | Uint8 rgucUnknown1[8]; // 40 | ||
| 134 | Uint8 rgucTimer2[4]; // 48 - 32 bit little endian | ||
| 135 | Uint8 ucBatteryLevel; // 52 | ||
| 136 | Uint8 ucConnectState; // 53 - 0x08 = USB, 0x01 = headphone | ||
| 137 | |||
| 138 | // There's more unknown data at the end, and a 32-bit CRC on Bluetooth | ||
| 139 | } PS5StatePacket_t; | ||
| 140 | |||
| 141 | typedef struct | ||
| 142 | { | ||
| 143 | Uint8 ucLeftJoystickX; // 0 | ||
| 144 | Uint8 ucLeftJoystickY; // 1 | ||
| 145 | Uint8 ucRightJoystickX; // 2 | ||
| 146 | Uint8 ucRightJoystickY; // 3 | ||
| 147 | Uint8 ucTriggerLeft; // 4 | ||
| 148 | Uint8 ucTriggerRight; // 5 | ||
| 149 | Uint8 ucCounter; // 6 | ||
| 150 | Uint8 rgucButtonsAndHat[4]; // 7 | ||
| 151 | Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian | ||
| 152 | Uint8 rgucGyroX[2]; // 15 | ||
| 153 | Uint8 rgucGyroY[2]; // 17 | ||
| 154 | Uint8 rgucGyroZ[2]; // 19 | ||
| 155 | Uint8 rgucAccelX[2]; // 21 | ||
| 156 | Uint8 rgucAccelY[2]; // 23 | ||
| 157 | Uint8 rgucAccelZ[2]; // 25 | ||
| 158 | Uint8 rgucSensorTimestamp[2]; // 27 - 16 bit little endian | ||
| 159 | Uint8 ucBatteryLevel; // 29 | ||
| 160 | Uint8 ucUnknown; // 30 | ||
| 161 | Uint8 ucTouchpadCounter1; // 31 - high bit clear + counter | ||
| 162 | Uint8 rgucTouchpadData1[3]; // 32 - X/Y, 12 bits per axis | ||
| 163 | Uint8 ucTouchpadCounter2; // 35 - high bit clear + counter | ||
| 164 | Uint8 rgucTouchpadData2[3]; // 36 - X/Y, 12 bits per axis | ||
| 165 | |||
| 166 | // There's more unknown data at the end, and a 32-bit CRC on Bluetooth | ||
| 167 | } PS5StatePacketAlt_t; | ||
| 168 | |||
| 169 | typedef struct | ||
| 170 | { | ||
| 171 | Uint8 ucEnableBits1; // 0 | ||
| 172 | Uint8 ucEnableBits2; // 1 | ||
| 173 | Uint8 ucRumbleRight; // 2 | ||
| 174 | Uint8 ucRumbleLeft; // 3 | ||
| 175 | Uint8 ucHeadphoneVolume; // 4 | ||
| 176 | Uint8 ucSpeakerVolume; // 5 | ||
| 177 | Uint8 ucMicrophoneVolume; // 6 | ||
| 178 | Uint8 ucAudioEnableBits; // 7 | ||
| 179 | Uint8 ucMicLightMode; // 8 | ||
| 180 | Uint8 ucAudioMuteBits; // 9 | ||
| 181 | Uint8 rgucRightTriggerEffect[11]; // 10 | ||
| 182 | Uint8 rgucLeftTriggerEffect[11]; // 21 | ||
| 183 | Uint8 rgucUnknown1[6]; // 32 | ||
| 184 | Uint8 ucEnableBits3; // 38 | ||
| 185 | Uint8 rgucUnknown2[2]; // 39 | ||
| 186 | Uint8 ucLedAnim; // 41 | ||
| 187 | Uint8 ucLedBrightness; // 42 | ||
| 188 | Uint8 ucPadLights; // 43 | ||
| 189 | Uint8 ucLedRed; // 44 | ||
| 190 | Uint8 ucLedGreen; // 45 | ||
| 191 | Uint8 ucLedBlue; // 46 | ||
| 192 | } DS5EffectsState_t; | ||
| 193 | |||
| 194 | typedef enum | ||
| 195 | { | ||
| 196 | k_EDS5EffectRumbleStart = (1 << 0), | ||
| 197 | k_EDS5EffectRumble = (1 << 1), | ||
| 198 | k_EDS5EffectLEDReset = (1 << 2), | ||
| 199 | k_EDS5EffectLED = (1 << 3), | ||
| 200 | k_EDS5EffectPadLights = (1 << 4), | ||
| 201 | k_EDS5EffectMicLight = (1 << 5) | ||
| 202 | } EDS5Effect; | ||
| 203 | |||
| 204 | typedef enum | ||
| 205 | { | ||
| 206 | k_EDS5LEDResetStateNone, | ||
| 207 | k_EDS5LEDResetStatePending, | ||
| 208 | k_EDS5LEDResetStateComplete, | ||
| 209 | } EDS5LEDResetState; | ||
| 210 | |||
| 211 | typedef struct | ||
| 212 | { | ||
| 213 | Sint16 bias; | ||
| 214 | float sensitivity; | ||
| 215 | } IMUCalibrationData; | ||
| 216 | |||
| 217 | /* Rumble hint mode: | ||
| 218 | * "0": enhanced features are never used | ||
| 219 | * "1": enhanced features are always used | ||
| 220 | * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it. | ||
| 221 | */ | ||
| 222 | typedef enum | ||
| 223 | { | ||
| 224 | PS5_ENHANCED_REPORT_HINT_OFF, | ||
| 225 | PS5_ENHANCED_REPORT_HINT_ON, | ||
| 226 | PS5_ENHANCED_REPORT_HINT_AUTO | ||
| 227 | } HIDAPI_PS5_EnhancedReportHint; | ||
| 228 | |||
| 229 | typedef struct | ||
| 230 | { | ||
| 231 | SDL_HIDAPI_Device *device; | ||
| 232 | SDL_Joystick *joystick; | ||
| 233 | bool is_nacon_dongle; | ||
| 234 | bool use_alternate_report; | ||
| 235 | bool sensors_supported; | ||
| 236 | bool lightbar_supported; | ||
| 237 | bool vibration_supported; | ||
| 238 | bool playerled_supported; | ||
| 239 | bool touchpad_supported; | ||
| 240 | bool effects_supported; | ||
| 241 | HIDAPI_PS5_EnhancedReportHint enhanced_report_hint; | ||
| 242 | bool enhanced_reports; | ||
| 243 | bool enhanced_mode; | ||
| 244 | bool enhanced_mode_available; | ||
| 245 | bool report_sensors; | ||
| 246 | bool report_touchpad; | ||
| 247 | bool report_battery; | ||
| 248 | bool hardware_calibration; | ||
| 249 | IMUCalibrationData calibration[6]; | ||
| 250 | Uint16 firmware_version; | ||
| 251 | Uint64 last_packet; | ||
| 252 | int player_index; | ||
| 253 | bool player_lights; | ||
| 254 | Uint8 rumble_left; | ||
| 255 | Uint8 rumble_right; | ||
| 256 | bool color_set; | ||
| 257 | Uint8 led_red; | ||
| 258 | Uint8 led_green; | ||
| 259 | Uint8 led_blue; | ||
| 260 | EDS5LEDResetState led_reset_state; | ||
| 261 | Uint64 sensor_ticks; | ||
| 262 | Uint32 last_tick; | ||
| 263 | union | ||
| 264 | { | ||
| 265 | PS5SimpleStatePacket_t simple; | ||
| 266 | PS5StatePacketCommon_t state; | ||
| 267 | PS5StatePacketAlt_t alt_state; | ||
| 268 | PS5StatePacket_t full_state; | ||
| 269 | Uint8 data[64]; | ||
| 270 | } last_state; | ||
| 271 | } SDL_DriverPS5_Context; | ||
| 272 | |||
| 273 | static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, bool application_usage); | ||
| 274 | |||
| 275 | static void HIDAPI_DriverPS5_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 276 | { | ||
| 277 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5, callback, userdata); | ||
| 278 | } | ||
| 279 | |||
| 280 | static void HIDAPI_DriverPS5_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 281 | { | ||
| 282 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5, callback, userdata); | ||
| 283 | } | ||
| 284 | |||
| 285 | static bool HIDAPI_DriverPS5_IsEnabled(void) | ||
| 286 | { | ||
| 287 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); | ||
| 288 | } | ||
| 289 | |||
| 290 | static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length) | ||
| 291 | { | ||
| 292 | SDL_memset(report, 0, length); | ||
| 293 | report[0] = report_id; | ||
| 294 | return SDL_hid_get_feature_report(dev, report, length); | ||
| 295 | } | ||
| 296 | |||
| 297 | 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) | ||
| 298 | { | ||
| 299 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 300 | int size; | ||
| 301 | |||
| 302 | if (type == SDL_GAMEPAD_TYPE_PS5) { | ||
| 303 | return true; | ||
| 304 | } | ||
| 305 | |||
| 306 | if (HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) { | ||
| 307 | if (device && device->dev) { | ||
| 308 | size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data)); | ||
| 309 | if (size == 48 && data[2] == 0x28) { | ||
| 310 | // Supported third party controller | ||
| 311 | return true; | ||
| 312 | } else { | ||
| 313 | return false; | ||
| 314 | } | ||
| 315 | } else { | ||
| 316 | // Might be supported by this driver, enumerate and find out | ||
| 317 | return true; | ||
| 318 | } | ||
| 319 | } | ||
| 320 | return false; | ||
| 321 | } | ||
| 322 | |||
| 323 | static void SetLedsForPlayerIndex(DS5EffectsState_t *effects, int player_index) | ||
| 324 | { | ||
| 325 | /* This list is the same as what hid-sony.c uses in the Linux kernel. | ||
| 326 | The first 4 values correspond to what the PS4 assigns. | ||
| 327 | */ | ||
| 328 | static const Uint8 colors[7][3] = { | ||
| 329 | { 0x00, 0x00, 0x40 }, // Blue | ||
| 330 | { 0x40, 0x00, 0x00 }, // Red | ||
| 331 | { 0x00, 0x40, 0x00 }, // Green | ||
| 332 | { 0x20, 0x00, 0x20 }, // Pink | ||
| 333 | { 0x20, 0x10, 0x00 }, // Orange | ||
| 334 | { 0x00, 0x10, 0x10 }, // Teal | ||
| 335 | { 0x10, 0x10, 0x10 } // White | ||
| 336 | }; | ||
| 337 | |||
| 338 | if (player_index >= 0) { | ||
| 339 | player_index %= SDL_arraysize(colors); | ||
| 340 | } else { | ||
| 341 | player_index = 0; | ||
| 342 | } | ||
| 343 | |||
| 344 | effects->ucLedRed = colors[player_index][0]; | ||
| 345 | effects->ucLedGreen = colors[player_index][1]; | ||
| 346 | effects->ucLedBlue = colors[player_index][2]; | ||
| 347 | } | ||
| 348 | |||
| 349 | static void SetLightsForPlayerIndex(DS5EffectsState_t *effects, int player_index) | ||
| 350 | { | ||
| 351 | static const Uint8 lights[] = { | ||
| 352 | 0x04, | ||
| 353 | 0x0A, | ||
| 354 | 0x15, | ||
| 355 | 0x1B, | ||
| 356 | 0x1F | ||
| 357 | }; | ||
| 358 | |||
| 359 | if (player_index >= 0) { | ||
| 360 | // Bitmask, 0x1F enables all lights, 0x20 changes instantly instead of fade | ||
| 361 | player_index %= SDL_arraysize(lights); | ||
| 362 | effects->ucPadLights = lights[player_index] | 0x20; | ||
| 363 | } else { | ||
| 364 | effects->ucPadLights = 0x00; | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 368 | static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) | ||
| 369 | { | ||
| 370 | SDL_DriverPS5_Context *ctx; | ||
| 371 | Uint8 data[USB_PACKET_LENGTH * 2]; | ||
| 372 | int size; | ||
| 373 | char serial[18]; | ||
| 374 | SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; | ||
| 375 | |||
| 376 | ctx = (SDL_DriverPS5_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 377 | if (!ctx) { | ||
| 378 | return false; | ||
| 379 | } | ||
| 380 | ctx->device = device; | ||
| 381 | |||
| 382 | device->context = ctx; | ||
| 383 | |||
| 384 | if (device->serial && SDL_strlen(device->serial) == 12) { | ||
| 385 | int i, j; | ||
| 386 | |||
| 387 | j = -1; | ||
| 388 | for (i = 0; i < 12; i += 2) { | ||
| 389 | j += 1; | ||
| 390 | SDL_memmove(&serial[j], &device->serial[i], 2); | ||
| 391 | j += 2; | ||
| 392 | serial[j] = '-'; | ||
| 393 | } | ||
| 394 | serial[j] = '\0'; | ||
| 395 | } else { | ||
| 396 | serial[0] = '\0'; | ||
| 397 | } | ||
| 398 | |||
| 399 | // Read a report to see what mode we're in | ||
| 400 | size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16); | ||
| 401 | #ifdef DEBUG_PS5_PROTOCOL | ||
| 402 | if (size > 0) { | ||
| 403 | HIDAPI_DumpPacket("PS5 first packet: size = %d", data, size); | ||
| 404 | } else { | ||
| 405 | SDL_Log("PS5 first packet: size = %d", size); | ||
| 406 | } | ||
| 407 | #endif | ||
| 408 | if (size == 64) { | ||
| 409 | // Connected over USB | ||
| 410 | ctx->enhanced_reports = true; | ||
| 411 | } else if (size > 0 && data[0] == k_EPS5ReportIdBluetoothEffects) { | ||
| 412 | // Connected over Bluetooth, using enhanced reports | ||
| 413 | ctx->enhanced_reports = true; | ||
| 414 | } else { | ||
| 415 | // Connected over Bluetooth, using simple reports (DirectInput enabled) | ||
| 416 | } | ||
| 417 | |||
| 418 | if (device->vendor_id == USB_VENDOR_SONY && ctx->enhanced_reports) { | ||
| 419 | /* Read the serial number (Bluetooth address in reverse byte order) | ||
| 420 | This will also enable enhanced reports over Bluetooth | ||
| 421 | */ | ||
| 422 | if (ReadFeatureReport(device->dev, k_EPS5FeatureReportIdSerialNumber, data, sizeof(data)) >= 7) { | ||
| 423 | (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", | ||
| 424 | data[6], data[5], data[4], data[3], data[2], data[1]); | ||
| 425 | } | ||
| 426 | |||
| 427 | /* Read the firmware version | ||
| 428 | This will also enable enhanced reports over Bluetooth | ||
| 429 | */ | ||
| 430 | if (ReadFeatureReport(device->dev, k_EPS5FeatureReportIdFirmwareInfo, data, USB_PACKET_LENGTH) >= 46) { | ||
| 431 | ctx->firmware_version = (Uint16)data[44] | ((Uint16)data[45] << 8); | ||
| 432 | } | ||
| 433 | } | ||
| 434 | |||
| 435 | // Get the device capabilities | ||
| 436 | if (device->vendor_id == USB_VENDOR_SONY) { | ||
| 437 | ctx->sensors_supported = true; | ||
| 438 | ctx->lightbar_supported = true; | ||
| 439 | ctx->vibration_supported = true; | ||
| 440 | ctx->playerled_supported = true; | ||
| 441 | ctx->touchpad_supported = true; | ||
| 442 | } else { | ||
| 443 | // Third party controller capability request | ||
| 444 | size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data)); | ||
| 445 | if (size == 48 && data[2] == 0x28) { | ||
| 446 | Uint8 capabilities = data[4]; | ||
| 447 | Uint8 capabilities2 = data[20]; | ||
| 448 | Uint8 device_type = data[5]; | ||
| 449 | |||
| 450 | #ifdef DEBUG_PS5_PROTOCOL | ||
| 451 | HIDAPI_DumpPacket("PS5 capabilities: size = %d", data, size); | ||
| 452 | #endif | ||
| 453 | if (capabilities & 0x02) { | ||
| 454 | ctx->sensors_supported = true; | ||
| 455 | } | ||
| 456 | if (capabilities & 0x04) { | ||
| 457 | ctx->lightbar_supported = true; | ||
| 458 | } | ||
| 459 | if (capabilities & 0x08) { | ||
| 460 | ctx->vibration_supported = true; | ||
| 461 | } | ||
| 462 | if (capabilities & 0x40) { | ||
| 463 | ctx->touchpad_supported = true; | ||
| 464 | } | ||
| 465 | if (capabilities2 & 0x80) { | ||
| 466 | ctx->playerled_supported = true; | ||
| 467 | } | ||
| 468 | |||
| 469 | switch (device_type) { | ||
| 470 | case 0x00: | ||
| 471 | joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; | ||
| 472 | break; | ||
| 473 | case 0x01: | ||
| 474 | joystick_type = SDL_JOYSTICK_TYPE_GUITAR; | ||
| 475 | break; | ||
| 476 | case 0x02: | ||
| 477 | joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT; | ||
| 478 | break; | ||
| 479 | case 0x06: | ||
| 480 | joystick_type = SDL_JOYSTICK_TYPE_WHEEL; | ||
| 481 | break; | ||
| 482 | case 0x07: | ||
| 483 | joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK; | ||
| 484 | break; | ||
| 485 | case 0x08: | ||
| 486 | joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK; | ||
| 487 | break; | ||
| 488 | default: | ||
| 489 | joystick_type = SDL_JOYSTICK_TYPE_UNKNOWN; | ||
| 490 | break; | ||
| 491 | } | ||
| 492 | |||
| 493 | ctx->use_alternate_report = true; | ||
| 494 | |||
| 495 | if (device->vendor_id == USB_VENDOR_NACON_ALT && | ||
| 496 | (device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED || | ||
| 497 | device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS)) { | ||
| 498 | // This doesn't report vibration capability, but it can do rumble | ||
| 499 | ctx->vibration_supported = true; | ||
| 500 | } | ||
| 501 | } else if (device->vendor_id == USB_VENDOR_RAZER && | ||
| 502 | (device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRED || | ||
| 503 | device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRELESS)) { | ||
| 504 | // The Razer Wolverine V2 Pro doesn't respond to the detection protocol, but has a touchpad and sensors and no vibration | ||
| 505 | ctx->sensors_supported = true; | ||
| 506 | ctx->touchpad_supported = true; | ||
| 507 | ctx->use_alternate_report = true; | ||
| 508 | } else if (device->vendor_id == USB_VENDOR_RAZER && | ||
| 509 | device->product_id == USB_PRODUCT_RAZER_KITSUNE) { | ||
| 510 | // The Razer Kitsune doesn't respond to the detection protocol, but has a touchpad | ||
| 511 | joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK; | ||
| 512 | ctx->touchpad_supported = true; | ||
| 513 | ctx->use_alternate_report = true; | ||
| 514 | } | ||
| 515 | } | ||
| 516 | ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported || ctx->playerled_supported); | ||
| 517 | |||
| 518 | if (device->vendor_id == USB_VENDOR_NACON_ALT && | ||
| 519 | device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) { | ||
| 520 | ctx->is_nacon_dongle = true; | ||
| 521 | } | ||
| 522 | |||
| 523 | device->joystick_type = joystick_type; | ||
| 524 | device->type = SDL_GAMEPAD_TYPE_PS5; | ||
| 525 | if (device->vendor_id == USB_VENDOR_SONY) { | ||
| 526 | if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) { | ||
| 527 | HIDAPI_SetDeviceName(device, "DualSense Edge Wireless Controller"); | ||
| 528 | } else { | ||
| 529 | HIDAPI_SetDeviceName(device, "DualSense Wireless Controller"); | ||
| 530 | } | ||
| 531 | } | ||
| 532 | HIDAPI_SetDeviceSerial(device, serial); | ||
| 533 | |||
| 534 | if (ctx->is_nacon_dongle) { | ||
| 535 | // We don't know if this is connected yet, wait for reports | ||
| 536 | return true; | ||
| 537 | } | ||
| 538 | |||
| 539 | // Prefer the USB device over the Bluetooth device | ||
| 540 | if (device->is_bluetooth) { | ||
| 541 | if (HIDAPI_HasConnectedUSBDevice(device->serial)) { | ||
| 542 | return true; | ||
| 543 | } | ||
| 544 | } else { | ||
| 545 | HIDAPI_DisconnectBluetoothDevice(device->serial); | ||
| 546 | } | ||
| 547 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 548 | } | ||
| 549 | |||
| 550 | static int HIDAPI_DriverPS5_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 551 | { | ||
| 552 | return -1; | ||
| 553 | } | ||
| 554 | |||
| 555 | static void HIDAPI_DriverPS5_LoadCalibrationData(SDL_HIDAPI_Device *device) | ||
| 556 | { | ||
| 557 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; | ||
| 558 | int i, size; | ||
| 559 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 560 | |||
| 561 | size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCalibration, data, sizeof(data)); | ||
| 562 | if (size < 35) { | ||
| 563 | #ifdef DEBUG_PS5_CALIBRATION | ||
| 564 | SDL_Log("Short read of calibration data: %d, ignoring calibration", size); | ||
| 565 | #endif | ||
| 566 | return; | ||
| 567 | } | ||
| 568 | |||
| 569 | { | ||
| 570 | Sint16 sGyroPitchBias, sGyroYawBias, sGyroRollBias; | ||
| 571 | Sint16 sGyroPitchPlus, sGyroPitchMinus; | ||
| 572 | Sint16 sGyroYawPlus, sGyroYawMinus; | ||
| 573 | Sint16 sGyroRollPlus, sGyroRollMinus; | ||
| 574 | Sint16 sGyroSpeedPlus, sGyroSpeedMinus; | ||
| 575 | |||
| 576 | Sint16 sAccXPlus, sAccXMinus; | ||
| 577 | Sint16 sAccYPlus, sAccYMinus; | ||
| 578 | Sint16 sAccZPlus, sAccZMinus; | ||
| 579 | |||
| 580 | float flNumerator; | ||
| 581 | Sint16 sRange2g; | ||
| 582 | |||
| 583 | #ifdef DEBUG_PS5_CALIBRATION | ||
| 584 | HIDAPI_DumpPacket("PS5 calibration packet: size = %d", data, size); | ||
| 585 | #endif | ||
| 586 | |||
| 587 | sGyroPitchBias = LOAD16(data[1], data[2]); | ||
| 588 | sGyroYawBias = LOAD16(data[3], data[4]); | ||
| 589 | sGyroRollBias = LOAD16(data[5], data[6]); | ||
| 590 | |||
| 591 | sGyroPitchPlus = LOAD16(data[7], data[8]); | ||
| 592 | sGyroPitchMinus = LOAD16(data[9], data[10]); | ||
| 593 | sGyroYawPlus = LOAD16(data[11], data[12]); | ||
| 594 | sGyroYawMinus = LOAD16(data[13], data[14]); | ||
| 595 | sGyroRollPlus = LOAD16(data[15], data[16]); | ||
| 596 | sGyroRollMinus = LOAD16(data[17], data[18]); | ||
| 597 | |||
| 598 | sGyroSpeedPlus = LOAD16(data[19], data[20]); | ||
| 599 | sGyroSpeedMinus = LOAD16(data[21], data[22]); | ||
| 600 | |||
| 601 | sAccXPlus = LOAD16(data[23], data[24]); | ||
| 602 | sAccXMinus = LOAD16(data[25], data[26]); | ||
| 603 | sAccYPlus = LOAD16(data[27], data[28]); | ||
| 604 | sAccYMinus = LOAD16(data[29], data[30]); | ||
| 605 | sAccZPlus = LOAD16(data[31], data[32]); | ||
| 606 | sAccZMinus = LOAD16(data[33], data[34]); | ||
| 607 | |||
| 608 | flNumerator = (sGyroSpeedPlus + sGyroSpeedMinus) * GYRO_RES_PER_DEGREE; | ||
| 609 | ctx->calibration[0].bias = sGyroPitchBias; | ||
| 610 | ctx->calibration[0].sensitivity = flNumerator / (sGyroPitchPlus - sGyroPitchMinus); | ||
| 611 | |||
| 612 | ctx->calibration[1].bias = sGyroYawBias; | ||
| 613 | ctx->calibration[1].sensitivity = flNumerator / (sGyroYawPlus - sGyroYawMinus); | ||
| 614 | |||
| 615 | ctx->calibration[2].bias = sGyroRollBias; | ||
| 616 | ctx->calibration[2].sensitivity = flNumerator / (sGyroRollPlus - sGyroRollMinus); | ||
| 617 | |||
| 618 | sRange2g = sAccXPlus - sAccXMinus; | ||
| 619 | ctx->calibration[3].bias = sAccXPlus - sRange2g / 2; | ||
| 620 | ctx->calibration[3].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g; | ||
| 621 | |||
| 622 | sRange2g = sAccYPlus - sAccYMinus; | ||
| 623 | ctx->calibration[4].bias = sAccYPlus - sRange2g / 2; | ||
| 624 | ctx->calibration[4].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g; | ||
| 625 | |||
| 626 | sRange2g = sAccZPlus - sAccZMinus; | ||
| 627 | ctx->calibration[5].bias = sAccZPlus - sRange2g / 2; | ||
| 628 | ctx->calibration[5].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g; | ||
| 629 | |||
| 630 | ctx->hardware_calibration = true; | ||
| 631 | for (i = 0; i < 6; ++i) { | ||
| 632 | float divisor = (i < 3 ? 64.0f : 1.0f); | ||
| 633 | #ifdef DEBUG_PS5_CALIBRATION | ||
| 634 | SDL_Log("calibration[%d] bias = %d, sensitivity = %f", i, ctx->calibration[i].bias, ctx->calibration[i].sensitivity); | ||
| 635 | #endif | ||
| 636 | // Some controllers have a bad calibration | ||
| 637 | if ((SDL_abs(ctx->calibration[i].bias) > 1024) || (SDL_fabsf(1.0f - ctx->calibration[i].sensitivity / divisor) > 0.5f)) { | ||
| 638 | #ifdef DEBUG_PS5_CALIBRATION | ||
| 639 | SDL_Log("invalid calibration, ignoring"); | ||
| 640 | #endif | ||
| 641 | ctx->hardware_calibration = false; | ||
| 642 | } | ||
| 643 | } | ||
| 644 | } | ||
| 645 | } | ||
| 646 | |||
| 647 | static float HIDAPI_DriverPS5_ApplyCalibrationData(SDL_DriverPS5_Context *ctx, int index, Sint16 value) | ||
| 648 | { | ||
| 649 | float result; | ||
| 650 | |||
| 651 | if (ctx->hardware_calibration) { | ||
| 652 | IMUCalibrationData *calibration = &ctx->calibration[index]; | ||
| 653 | |||
| 654 | result = (value - calibration->bias) * calibration->sensitivity; | ||
| 655 | } else if (index < 3) { | ||
| 656 | result = value * 64.f; | ||
| 657 | } else { | ||
| 658 | result = value; | ||
| 659 | } | ||
| 660 | |||
| 661 | // Convert the raw data to the units expected by SDL | ||
| 662 | if (index < 3) { | ||
| 663 | result = (result / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f; | ||
| 664 | } else { | ||
| 665 | result = (result / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; | ||
| 666 | } | ||
| 667 | return result; | ||
| 668 | } | ||
| 669 | |||
| 670 | static bool HIDAPI_DriverPS5_UpdateEffects(SDL_DriverPS5_Context *ctx, int effect_mask, bool application_usage) | ||
| 671 | { | ||
| 672 | DS5EffectsState_t effects; | ||
| 673 | |||
| 674 | // Make sure the Bluetooth connection sequence has completed before sending LED color change | ||
| 675 | if (ctx->device->is_bluetooth && ctx->enhanced_reports && | ||
| 676 | (effect_mask & (k_EDS5EffectLED | k_EDS5EffectPadLights)) != 0) { | ||
| 677 | if (ctx->led_reset_state != k_EDS5LEDResetStateComplete) { | ||
| 678 | ctx->led_reset_state = k_EDS5LEDResetStatePending; | ||
| 679 | return true; | ||
| 680 | } | ||
| 681 | } | ||
| 682 | |||
| 683 | SDL_zero(effects); | ||
| 684 | |||
| 685 | if (ctx->vibration_supported) { | ||
| 686 | if (ctx->rumble_left || ctx->rumble_right) { | ||
| 687 | if (ctx->firmware_version < 0x0224) { | ||
| 688 | effects.ucEnableBits1 |= 0x01; // Enable rumble emulation | ||
| 689 | |||
| 690 | // Shift to reduce effective rumble strength to match Xbox controllers | ||
| 691 | effects.ucRumbleLeft = ctx->rumble_left >> 1; | ||
| 692 | effects.ucRumbleRight = ctx->rumble_right >> 1; | ||
| 693 | } else { | ||
| 694 | effects.ucEnableBits3 |= 0x04; // Enable improved rumble emulation on 2.24 firmware and newer | ||
| 695 | |||
| 696 | effects.ucRumbleLeft = ctx->rumble_left; | ||
| 697 | effects.ucRumbleRight = ctx->rumble_right; | ||
| 698 | } | ||
| 699 | effects.ucEnableBits1 |= 0x02; // Disable audio haptics | ||
| 700 | } else { | ||
| 701 | // Leaving emulated rumble bits off will restore audio haptics | ||
| 702 | } | ||
| 703 | |||
| 704 | if ((effect_mask & k_EDS5EffectRumbleStart) != 0) { | ||
| 705 | effects.ucEnableBits1 |= 0x02; // Disable audio haptics | ||
| 706 | } | ||
| 707 | if ((effect_mask & k_EDS5EffectRumble) != 0) { | ||
| 708 | // Already handled above | ||
| 709 | } | ||
| 710 | } | ||
| 711 | if (ctx->lightbar_supported) { | ||
| 712 | if ((effect_mask & k_EDS5EffectLEDReset) != 0) { | ||
| 713 | effects.ucEnableBits2 |= 0x08; // Reset LED state | ||
| 714 | } | ||
| 715 | if ((effect_mask & k_EDS5EffectLED) != 0) { | ||
| 716 | effects.ucEnableBits2 |= 0x04; // Enable LED color | ||
| 717 | |||
| 718 | // Populate the LED state with the appropriate color from our lookup table | ||
| 719 | if (ctx->color_set) { | ||
| 720 | effects.ucLedRed = ctx->led_red; | ||
| 721 | effects.ucLedGreen = ctx->led_green; | ||
| 722 | effects.ucLedBlue = ctx->led_blue; | ||
| 723 | } else { | ||
| 724 | SetLedsForPlayerIndex(&effects, ctx->player_index); | ||
| 725 | } | ||
| 726 | } | ||
| 727 | } | ||
| 728 | if (ctx->playerled_supported) { | ||
| 729 | if ((effect_mask & k_EDS5EffectPadLights) != 0) { | ||
| 730 | effects.ucEnableBits2 |= 0x10; // Enable touchpad lights | ||
| 731 | |||
| 732 | if (ctx->player_lights) { | ||
| 733 | SetLightsForPlayerIndex(&effects, ctx->player_index); | ||
| 734 | } else { | ||
| 735 | effects.ucPadLights = 0x00; | ||
| 736 | } | ||
| 737 | } | ||
| 738 | } | ||
| 739 | if ((effect_mask & k_EDS5EffectMicLight) != 0) { | ||
| 740 | effects.ucEnableBits2 |= 0x01; // Enable microphone light | ||
| 741 | |||
| 742 | effects.ucMicLightMode = 0; // Bitmask, 0x00 = off, 0x01 = solid, 0x02 = pulse | ||
| 743 | } | ||
| 744 | |||
| 745 | return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage); | ||
| 746 | } | ||
| 747 | |||
| 748 | static void HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_DriverPS5_Context *ctx) | ||
| 749 | { | ||
| 750 | bool led_reset_complete = false; | ||
| 751 | |||
| 752 | if (ctx->enhanced_reports && ctx->sensors_supported && !ctx->use_alternate_report) { | ||
| 753 | const PS5StatePacketCommon_t *packet = &ctx->last_state.state; | ||
| 754 | |||
| 755 | // Check the timer to make sure the Bluetooth connection LED animation is complete | ||
| 756 | const Uint32 connection_complete = 10200000; | ||
| 757 | Uint32 timestamp = LOAD32(packet->rgucSensorTimestamp[0], | ||
| 758 | packet->rgucSensorTimestamp[1], | ||
| 759 | packet->rgucSensorTimestamp[2], | ||
| 760 | packet->rgucSensorTimestamp[3]); | ||
| 761 | if (timestamp >= connection_complete) { | ||
| 762 | led_reset_complete = true; | ||
| 763 | } | ||
| 764 | } else { | ||
| 765 | // We don't know how to check the timer, just assume it's complete for now | ||
| 766 | led_reset_complete = true; | ||
| 767 | } | ||
| 768 | |||
| 769 | if (led_reset_complete) { | ||
| 770 | HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLEDReset, false); | ||
| 771 | |||
| 772 | ctx->led_reset_state = k_EDS5LEDResetStateComplete; | ||
| 773 | |||
| 774 | HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false); | ||
| 775 | } | ||
| 776 | } | ||
| 777 | |||
| 778 | static void HIDAPI_DriverPS5_TickleBluetooth(SDL_HIDAPI_Device *device) | ||
| 779 | { | ||
| 780 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; | ||
| 781 | |||
| 782 | if (ctx->enhanced_reports) { | ||
| 783 | // This is just a dummy packet that should have no effect, since we don't set the CRC | ||
| 784 | Uint8 data[78]; | ||
| 785 | |||
| 786 | SDL_zeroa(data); | ||
| 787 | |||
| 788 | data[0] = k_EPS5ReportIdBluetoothEffects; | ||
| 789 | data[1] = 0x02; // Magic value | ||
| 790 | |||
| 791 | if (SDL_HIDAPI_LockRumble()) { | ||
| 792 | SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data)); | ||
| 793 | } | ||
| 794 | } else { | ||
| 795 | // We can't even send an invalid effects packet, or it will put the controller in enhanced mode | ||
| 796 | if (device->num_joysticks > 0) { | ||
| 797 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 798 | } | ||
| 799 | } | ||
| 800 | } | ||
| 801 | |||
| 802 | static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx) | ||
| 803 | { | ||
| 804 | if (ctx->enhanced_mode_available) { | ||
| 805 | return; | ||
| 806 | } | ||
| 807 | ctx->enhanced_mode_available = true; | ||
| 808 | |||
| 809 | if (ctx->touchpad_supported) { | ||
| 810 | SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2); | ||
| 811 | ctx->report_touchpad = true; | ||
| 812 | } | ||
| 813 | |||
| 814 | if (ctx->sensors_supported) { | ||
| 815 | if (ctx->device->is_bluetooth) { | ||
| 816 | // Bluetooth sensor update rate appears to be 1000 Hz | ||
| 817 | SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 1000.0f); | ||
| 818 | SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 1000.0f); | ||
| 819 | } else { | ||
| 820 | SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 250.0f); | ||
| 821 | SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 250.0f); | ||
| 822 | } | ||
| 823 | } | ||
| 824 | |||
| 825 | ctx->report_battery = true; | ||
| 826 | |||
| 827 | HIDAPI_UpdateDeviceProperties(ctx->device); | ||
| 828 | } | ||
| 829 | |||
| 830 | static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_DriverPS5_Context *ctx) | ||
| 831 | { | ||
| 832 | HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx); | ||
| 833 | |||
| 834 | if (!ctx->enhanced_mode) { | ||
| 835 | ctx->enhanced_mode = true; | ||
| 836 | |||
| 837 | // Switch into enhanced report mode | ||
| 838 | HIDAPI_DriverPS5_UpdateEffects(ctx, 0, false); | ||
| 839 | |||
| 840 | // Update the light effects | ||
| 841 | HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false); | ||
| 842 | } | ||
| 843 | } | ||
| 844 | |||
| 845 | static void HIDAPI_DriverPS5_SetEnhancedReportHint(SDL_DriverPS5_Context *ctx, HIDAPI_PS5_EnhancedReportHint enhanced_report_hint) | ||
| 846 | { | ||
| 847 | switch (enhanced_report_hint) { | ||
| 848 | case PS5_ENHANCED_REPORT_HINT_OFF: | ||
| 849 | // Nothing to do, enhanced mode is a one-way ticket | ||
| 850 | break; | ||
| 851 | case PS5_ENHANCED_REPORT_HINT_ON: | ||
| 852 | HIDAPI_DriverPS5_SetEnhancedMode(ctx); | ||
| 853 | break; | ||
| 854 | case PS5_ENHANCED_REPORT_HINT_AUTO: | ||
| 855 | HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx); | ||
| 856 | break; | ||
| 857 | } | ||
| 858 | ctx->enhanced_report_hint = enhanced_report_hint; | ||
| 859 | } | ||
| 860 | |||
| 861 | static void HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS5_Context *ctx) | ||
| 862 | { | ||
| 863 | ctx->enhanced_reports = true; | ||
| 864 | |||
| 865 | if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) { | ||
| 866 | HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON); | ||
| 867 | } | ||
| 868 | } | ||
| 869 | |||
| 870 | static void HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS5_Context *ctx) | ||
| 871 | { | ||
| 872 | if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) { | ||
| 873 | HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON); | ||
| 874 | } | ||
| 875 | } | ||
| 876 | |||
| 877 | static void SDLCALL SDL_PS5EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 878 | { | ||
| 879 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata; | ||
| 880 | |||
| 881 | if (ctx->device->is_bluetooth) { | ||
| 882 | if (hint && SDL_strcasecmp(hint, "auto") == 0) { | ||
| 883 | HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_AUTO); | ||
| 884 | } else if (SDL_GetStringBoolean(hint, true)) { | ||
| 885 | HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON); | ||
| 886 | } else { | ||
| 887 | HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_OFF); | ||
| 888 | } | ||
| 889 | } else { | ||
| 890 | HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON); | ||
| 891 | } | ||
| 892 | } | ||
| 893 | |||
| 894 | static void SDLCALL SDL_PS5PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 895 | { | ||
| 896 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata; | ||
| 897 | bool player_lights = SDL_GetStringBoolean(hint, true); | ||
| 898 | |||
| 899 | if (player_lights != ctx->player_lights) { | ||
| 900 | ctx->player_lights = player_lights; | ||
| 901 | |||
| 902 | HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectPadLights, false); | ||
| 903 | } | ||
| 904 | } | ||
| 905 | |||
| 906 | static void HIDAPI_DriverPS5_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 907 | { | ||
| 908 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; | ||
| 909 | |||
| 910 | if (!ctx->joystick) { | ||
| 911 | return; | ||
| 912 | } | ||
| 913 | |||
| 914 | ctx->player_index = player_index; | ||
| 915 | |||
| 916 | // This will set the new LED state based on the new player index | ||
| 917 | // SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode | ||
| 918 | HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false); | ||
| 919 | } | ||
| 920 | |||
| 921 | static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 922 | { | ||
| 923 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; | ||
| 924 | |||
| 925 | SDL_AssertJoysticksLocked(); | ||
| 926 | |||
| 927 | ctx->joystick = joystick; | ||
| 928 | ctx->last_packet = SDL_GetTicks(); | ||
| 929 | ctx->report_sensors = false; | ||
| 930 | ctx->report_touchpad = false; | ||
| 931 | ctx->rumble_left = 0; | ||
| 932 | ctx->rumble_right = 0; | ||
| 933 | ctx->color_set = false; | ||
| 934 | ctx->led_reset_state = k_EDS5LEDResetStateNone; | ||
| 935 | SDL_zero(ctx->last_state); | ||
| 936 | |||
| 937 | // Initialize player index (needed for setting LEDs) | ||
| 938 | ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); | ||
| 939 | ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, true); | ||
| 940 | |||
| 941 | // Initialize the joystick capabilities | ||
| 942 | if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) { | ||
| 943 | joystick->nbuttons = 17; // paddles and touchpad and microphone | ||
| 944 | } else if (ctx->touchpad_supported) { | ||
| 945 | joystick->nbuttons = 13; // touchpad and microphone | ||
| 946 | } else { | ||
| 947 | joystick->nbuttons = 11; | ||
| 948 | } | ||
| 949 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 950 | joystick->nhats = 1; | ||
| 951 | joystick->firmware_version = ctx->firmware_version; | ||
| 952 | |||
| 953 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, | ||
| 954 | SDL_PS5EnhancedReportsChanged, ctx); | ||
| 955 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, | ||
| 956 | SDL_PS5PlayerLEDHintChanged, ctx); | ||
| 957 | |||
| 958 | return true; | ||
| 959 | } | ||
| 960 | |||
| 961 | static bool HIDAPI_DriverPS5_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 962 | { | ||
| 963 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; | ||
| 964 | |||
| 965 | if (!ctx->vibration_supported) { | ||
| 966 | return SDL_Unsupported(); | ||
| 967 | } | ||
| 968 | |||
| 969 | if (!ctx->rumble_left && !ctx->rumble_right) { | ||
| 970 | HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumbleStart, true); | ||
| 971 | } | ||
| 972 | |||
| 973 | ctx->rumble_left = (low_frequency_rumble >> 8); | ||
| 974 | ctx->rumble_right = (high_frequency_rumble >> 8); | ||
| 975 | |||
| 976 | return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumble, true); | ||
| 977 | } | ||
| 978 | |||
| 979 | static bool HIDAPI_DriverPS5_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 980 | { | ||
| 981 | return SDL_Unsupported(); | ||
| 982 | } | ||
| 983 | |||
| 984 | static Uint32 HIDAPI_DriverPS5_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 985 | { | ||
| 986 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; | ||
| 987 | Uint32 result = 0; | ||
| 988 | |||
| 989 | if (ctx->enhanced_mode_available) { | ||
| 990 | if (ctx->lightbar_supported) { | ||
| 991 | result |= SDL_JOYSTICK_CAP_RGB_LED; | ||
| 992 | } | ||
| 993 | if (ctx->playerled_supported) { | ||
| 994 | result |= SDL_JOYSTICK_CAP_PLAYER_LED; | ||
| 995 | } | ||
| 996 | if (ctx->vibration_supported) { | ||
| 997 | result |= SDL_JOYSTICK_CAP_RUMBLE; | ||
| 998 | } | ||
| 999 | } | ||
| 1000 | |||
| 1001 | return result; | ||
| 1002 | } | ||
| 1003 | |||
| 1004 | static bool HIDAPI_DriverPS5_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 1005 | { | ||
| 1006 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; | ||
| 1007 | |||
| 1008 | if (!ctx->lightbar_supported) { | ||
| 1009 | return SDL_Unsupported(); | ||
| 1010 | } | ||
| 1011 | |||
| 1012 | ctx->color_set = true; | ||
| 1013 | ctx->led_red = red; | ||
| 1014 | ctx->led_green = green; | ||
| 1015 | ctx->led_blue = blue; | ||
| 1016 | |||
| 1017 | return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLED, true); | ||
| 1018 | } | ||
| 1019 | |||
| 1020 | static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, bool application_usage) | ||
| 1021 | { | ||
| 1022 | Uint8 data[78]; | ||
| 1023 | int report_size, offset; | ||
| 1024 | Uint8 *pending_data; | ||
| 1025 | int *pending_size; | ||
| 1026 | int maximum_size; | ||
| 1027 | |||
| 1028 | if (!ctx->effects_supported) { | ||
| 1029 | // We shouldn't be sending packets to this controller | ||
| 1030 | return SDL_Unsupported(); | ||
| 1031 | } | ||
| 1032 | |||
| 1033 | if (!ctx->enhanced_mode) { | ||
| 1034 | if (application_usage) { | ||
| 1035 | HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx); | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | if (!ctx->enhanced_mode) { | ||
| 1039 | // We're not in enhanced mode, effects aren't allowed | ||
| 1040 | return SDL_Unsupported(); | ||
| 1041 | } | ||
| 1042 | } | ||
| 1043 | |||
| 1044 | SDL_zeroa(data); | ||
| 1045 | |||
| 1046 | if (ctx->device->is_bluetooth) { | ||
| 1047 | data[0] = k_EPS5ReportIdBluetoothEffects; | ||
| 1048 | data[1] = 0x02; // Magic value | ||
| 1049 | |||
| 1050 | report_size = 78; | ||
| 1051 | offset = 2; | ||
| 1052 | } else { | ||
| 1053 | data[0] = k_EPS5ReportIdUsbEffects; | ||
| 1054 | |||
| 1055 | report_size = 48; | ||
| 1056 | offset = 1; | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size)); | ||
| 1060 | |||
| 1061 | if (ctx->device->is_bluetooth) { | ||
| 1062 | // Bluetooth reports need a CRC at the end of the packet (at least on Linux) | ||
| 1063 | Uint8 ubHdr = 0xA2; // hidp header is part of the CRC calculation | ||
| 1064 | Uint32 unCRC; | ||
| 1065 | unCRC = SDL_crc32(0, &ubHdr, 1); | ||
| 1066 | unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC))); | ||
| 1067 | SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC)); | ||
| 1068 | } | ||
| 1069 | |||
| 1070 | if (!SDL_HIDAPI_LockRumble()) { | ||
| 1071 | return false; | ||
| 1072 | } | ||
| 1073 | |||
| 1074 | // See if we can update an existing pending request | ||
| 1075 | if (SDL_HIDAPI_GetPendingRumbleLocked(ctx->device, &pending_data, &pending_size, &maximum_size)) { | ||
| 1076 | DS5EffectsState_t *effects = (DS5EffectsState_t *)&data[offset]; | ||
| 1077 | DS5EffectsState_t *pending_effects = (DS5EffectsState_t *)&pending_data[offset]; | ||
| 1078 | if (report_size == *pending_size && | ||
| 1079 | effects->ucEnableBits1 == pending_effects->ucEnableBits1 && | ||
| 1080 | effects->ucEnableBits2 == pending_effects->ucEnableBits2) { | ||
| 1081 | // We're simply updating the data for this request | ||
| 1082 | SDL_memcpy(pending_data, data, report_size); | ||
| 1083 | SDL_HIDAPI_UnlockRumble(); | ||
| 1084 | return true; | ||
| 1085 | } | ||
| 1086 | } | ||
| 1087 | |||
| 1088 | if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, report_size) != report_size) { | ||
| 1089 | return false; | ||
| 1090 | } | ||
| 1091 | |||
| 1092 | return true; | ||
| 1093 | } | ||
| 1094 | |||
| 1095 | static bool HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) | ||
| 1096 | { | ||
| 1097 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; | ||
| 1098 | |||
| 1099 | return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, effect, size, true); | ||
| 1100 | } | ||
| 1101 | |||
| 1102 | static bool HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 1103 | { | ||
| 1104 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; | ||
| 1105 | |||
| 1106 | HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx); | ||
| 1107 | |||
| 1108 | if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) { | ||
| 1109 | return SDL_Unsupported(); | ||
| 1110 | } | ||
| 1111 | |||
| 1112 | if (enabled) { | ||
| 1113 | HIDAPI_DriverPS5_LoadCalibrationData(device); | ||
| 1114 | } | ||
| 1115 | ctx->report_sensors = enabled; | ||
| 1116 | |||
| 1117 | return true; | ||
| 1118 | } | ||
| 1119 | |||
| 1120 | static void HIDAPI_DriverPS5_HandleSimpleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5SimpleStatePacket_t *packet, Uint64 timestamp) | ||
| 1121 | { | ||
| 1122 | Sint16 axis; | ||
| 1123 | |||
| 1124 | if (ctx->last_state.simple.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) { | ||
| 1125 | { | ||
| 1126 | Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4); | ||
| 1127 | |||
| 1128 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0)); | ||
| 1129 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0)); | ||
| 1130 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0)); | ||
| 1131 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0)); | ||
| 1132 | } | ||
| 1133 | { | ||
| 1134 | Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F); | ||
| 1135 | Uint8 hat; | ||
| 1136 | |||
| 1137 | switch (data) { | ||
| 1138 | case 0: | ||
| 1139 | hat = SDL_HAT_UP; | ||
| 1140 | break; | ||
| 1141 | case 1: | ||
| 1142 | hat = SDL_HAT_RIGHTUP; | ||
| 1143 | break; | ||
| 1144 | case 2: | ||
| 1145 | hat = SDL_HAT_RIGHT; | ||
| 1146 | break; | ||
| 1147 | case 3: | ||
| 1148 | hat = SDL_HAT_RIGHTDOWN; | ||
| 1149 | break; | ||
| 1150 | case 4: | ||
| 1151 | hat = SDL_HAT_DOWN; | ||
| 1152 | break; | ||
| 1153 | case 5: | ||
| 1154 | hat = SDL_HAT_LEFTDOWN; | ||
| 1155 | break; | ||
| 1156 | case 6: | ||
| 1157 | hat = SDL_HAT_LEFT; | ||
| 1158 | break; | ||
| 1159 | case 7: | ||
| 1160 | hat = SDL_HAT_LEFTUP; | ||
| 1161 | break; | ||
| 1162 | default: | ||
| 1163 | hat = SDL_HAT_CENTERED; | ||
| 1164 | break; | ||
| 1165 | } | ||
| 1166 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 1167 | } | ||
| 1168 | } | ||
| 1169 | |||
| 1170 | if (ctx->last_state.simple.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) { | ||
| 1171 | Uint8 data = packet->rgucButtonsHatAndCounter[1]; | ||
| 1172 | |||
| 1173 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0)); | ||
| 1174 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0)); | ||
| 1175 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0)); | ||
| 1176 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0)); | ||
| 1177 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0)); | ||
| 1178 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0)); | ||
| 1179 | } | ||
| 1180 | |||
| 1181 | if (ctx->last_state.simple.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) { | ||
| 1182 | Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03); | ||
| 1183 | |||
| 1184 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0)); | ||
| 1185 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, ((data & 0x02) != 0)); | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x04)) { | ||
| 1189 | axis = SDL_JOYSTICK_AXIS_MAX; | ||
| 1190 | } else { | ||
| 1191 | axis = ((int)packet->ucTriggerLeft * 257) - 32768; | ||
| 1192 | } | ||
| 1193 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 1194 | if (packet->ucTriggerRight == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x08)) { | ||
| 1195 | axis = SDL_JOYSTICK_AXIS_MAX; | ||
| 1196 | } else { | ||
| 1197 | axis = ((int)packet->ucTriggerRight * 257) - 32768; | ||
| 1198 | } | ||
| 1199 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 1200 | axis = ((int)packet->ucLeftJoystickX * 257) - 32768; | ||
| 1201 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 1202 | axis = ((int)packet->ucLeftJoystickY * 257) - 32768; | ||
| 1203 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 1204 | axis = ((int)packet->ucRightJoystickX * 257) - 32768; | ||
| 1205 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 1206 | axis = ((int)packet->ucRightJoystickY * 257) - 32768; | ||
| 1207 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 1208 | |||
| 1209 | SDL_memcpy(&ctx->last_state.simple, packet, sizeof(ctx->last_state.simple)); | ||
| 1210 | } | ||
| 1211 | |||
| 1212 | static void HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketCommon_t *packet, Uint64 timestamp) | ||
| 1213 | { | ||
| 1214 | Sint16 axis; | ||
| 1215 | |||
| 1216 | if (ctx->last_state.state.rgucButtonsAndHat[0] != packet->rgucButtonsAndHat[0]) { | ||
| 1217 | { | ||
| 1218 | Uint8 data = (packet->rgucButtonsAndHat[0] >> 4); | ||
| 1219 | |||
| 1220 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0)); | ||
| 1221 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0)); | ||
| 1222 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0)); | ||
| 1223 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0)); | ||
| 1224 | } | ||
| 1225 | { | ||
| 1226 | Uint8 data = (packet->rgucButtonsAndHat[0] & 0x0F); | ||
| 1227 | Uint8 hat; | ||
| 1228 | |||
| 1229 | switch (data) { | ||
| 1230 | case 0: | ||
| 1231 | hat = SDL_HAT_UP; | ||
| 1232 | break; | ||
| 1233 | case 1: | ||
| 1234 | hat = SDL_HAT_RIGHTUP; | ||
| 1235 | break; | ||
| 1236 | case 2: | ||
| 1237 | hat = SDL_HAT_RIGHT; | ||
| 1238 | break; | ||
| 1239 | case 3: | ||
| 1240 | hat = SDL_HAT_RIGHTDOWN; | ||
| 1241 | break; | ||
| 1242 | case 4: | ||
| 1243 | hat = SDL_HAT_DOWN; | ||
| 1244 | break; | ||
| 1245 | case 5: | ||
| 1246 | hat = SDL_HAT_LEFTDOWN; | ||
| 1247 | break; | ||
| 1248 | case 6: | ||
| 1249 | hat = SDL_HAT_LEFT; | ||
| 1250 | break; | ||
| 1251 | case 7: | ||
| 1252 | hat = SDL_HAT_LEFTUP; | ||
| 1253 | break; | ||
| 1254 | default: | ||
| 1255 | hat = SDL_HAT_CENTERED; | ||
| 1256 | break; | ||
| 1257 | } | ||
| 1258 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 1259 | } | ||
| 1260 | } | ||
| 1261 | |||
| 1262 | if (ctx->last_state.state.rgucButtonsAndHat[1] != packet->rgucButtonsAndHat[1]) { | ||
| 1263 | Uint8 data = packet->rgucButtonsAndHat[1]; | ||
| 1264 | |||
| 1265 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0)); | ||
| 1266 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0)); | ||
| 1267 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0)); | ||
| 1268 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0)); | ||
| 1269 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0)); | ||
| 1270 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0)); | ||
| 1271 | } | ||
| 1272 | |||
| 1273 | if (ctx->last_state.state.rgucButtonsAndHat[2] != packet->rgucButtonsAndHat[2]) { | ||
| 1274 | Uint8 data = packet->rgucButtonsAndHat[2]; | ||
| 1275 | |||
| 1276 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0)); | ||
| 1277 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, ((data & 0x02) != 0)); | ||
| 1278 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_MICROPHONE, ((data & 0x04) != 0)); | ||
| 1279 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION, ((data & 0x10) != 0)); | ||
| 1280 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION, ((data & 0x20) != 0)); | ||
| 1281 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE, ((data & 0x40) != 0)); | ||
| 1282 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE, ((data & 0x80) != 0)); | ||
| 1283 | } | ||
| 1284 | |||
| 1285 | if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsAndHat[1] & 0x04)) { | ||
| 1286 | axis = SDL_JOYSTICK_AXIS_MAX; | ||
| 1287 | } else { | ||
| 1288 | axis = ((int)packet->ucTriggerLeft * 257) - 32768; | ||
| 1289 | } | ||
| 1290 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 1291 | if (packet->ucTriggerRight == 0 && (packet->rgucButtonsAndHat[1] & 0x08)) { | ||
| 1292 | axis = SDL_JOYSTICK_AXIS_MAX; | ||
| 1293 | } else { | ||
| 1294 | axis = ((int)packet->ucTriggerRight * 257) - 32768; | ||
| 1295 | } | ||
| 1296 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 1297 | axis = ((int)packet->ucLeftJoystickX * 257) - 32768; | ||
| 1298 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 1299 | axis = ((int)packet->ucLeftJoystickY * 257) - 32768; | ||
| 1300 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 1301 | axis = ((int)packet->ucRightJoystickX * 257) - 32768; | ||
| 1302 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 1303 | axis = ((int)packet->ucRightJoystickY * 257) - 32768; | ||
| 1304 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 1305 | |||
| 1306 | if (ctx->report_sensors) { | ||
| 1307 | Uint64 sensor_timestamp; | ||
| 1308 | float data[3]; | ||
| 1309 | |||
| 1310 | if (ctx->use_alternate_report) { | ||
| 1311 | // 16-bit timestamp | ||
| 1312 | Uint32 delta; | ||
| 1313 | Uint16 tick = LOAD16(packet->rgucSensorTimestamp[0], | ||
| 1314 | packet->rgucSensorTimestamp[1]); | ||
| 1315 | if (ctx->last_tick < tick) { | ||
| 1316 | delta = (tick - ctx->last_tick); | ||
| 1317 | } else { | ||
| 1318 | delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1); | ||
| 1319 | } | ||
| 1320 | ctx->last_tick = tick; | ||
| 1321 | ctx->sensor_ticks += delta; | ||
| 1322 | |||
| 1323 | // Sensor timestamp is in 1us units | ||
| 1324 | sensor_timestamp = SDL_US_TO_NS(ctx->sensor_ticks); | ||
| 1325 | } else { | ||
| 1326 | // 32-bit timestamp | ||
| 1327 | Uint32 delta; | ||
| 1328 | Uint32 tick = LOAD32(packet->rgucSensorTimestamp[0], | ||
| 1329 | packet->rgucSensorTimestamp[1], | ||
| 1330 | packet->rgucSensorTimestamp[2], | ||
| 1331 | packet->rgucSensorTimestamp[3]); | ||
| 1332 | if (ctx->last_tick < tick) { | ||
| 1333 | delta = (tick - ctx->last_tick); | ||
| 1334 | } else { | ||
| 1335 | delta = (SDL_MAX_UINT32 - ctx->last_tick + tick + 1); | ||
| 1336 | } | ||
| 1337 | ctx->last_tick = tick; | ||
| 1338 | ctx->sensor_ticks += delta; | ||
| 1339 | |||
| 1340 | // Sensor timestamp is in 0.33us units | ||
| 1341 | sensor_timestamp = (ctx->sensor_ticks * SDL_NS_PER_US) / 3; | ||
| 1342 | } | ||
| 1343 | |||
| 1344 | data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1])); | ||
| 1345 | data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1])); | ||
| 1346 | data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1])); | ||
| 1347 | SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, data, 3); | ||
| 1348 | |||
| 1349 | data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1])); | ||
| 1350 | data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1])); | ||
| 1351 | data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1])); | ||
| 1352 | SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3); | ||
| 1353 | } | ||
| 1354 | } | ||
| 1355 | |||
| 1356 | static void HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacket_t *packet, Uint64 timestamp) | ||
| 1357 | { | ||
| 1358 | static const float TOUCHPAD_SCALEX = 1.0f / 1920; | ||
| 1359 | static const float TOUCHPAD_SCALEY = 1.0f / 1070; | ||
| 1360 | bool touchpad_down; | ||
| 1361 | int touchpad_x, touchpad_y; | ||
| 1362 | |||
| 1363 | if (ctx->report_touchpad) { | ||
| 1364 | touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0); | ||
| 1365 | touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8); | ||
| 1366 | touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4); | ||
| 1367 | SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); | ||
| 1368 | |||
| 1369 | touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0); | ||
| 1370 | touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8); | ||
| 1371 | touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4); | ||
| 1372 | SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); | ||
| 1373 | } | ||
| 1374 | |||
| 1375 | if (ctx->report_battery) { | ||
| 1376 | SDL_PowerState state; | ||
| 1377 | int percent; | ||
| 1378 | Uint8 status = (packet->ucBatteryLevel >> 4) & 0x0F; | ||
| 1379 | Uint8 level = (packet->ucBatteryLevel & 0x0F); | ||
| 1380 | |||
| 1381 | switch (status) { | ||
| 1382 | case 0: | ||
| 1383 | state = SDL_POWERSTATE_ON_BATTERY; | ||
| 1384 | percent = SDL_min(level * 10 + 5, 100); | ||
| 1385 | break; | ||
| 1386 | case 1: | ||
| 1387 | state = SDL_POWERSTATE_CHARGING; | ||
| 1388 | percent = SDL_min(level * 10 + 5, 100); | ||
| 1389 | break; | ||
| 1390 | case 2: | ||
| 1391 | state = SDL_POWERSTATE_CHARGED; | ||
| 1392 | percent = 100; | ||
| 1393 | break; | ||
| 1394 | default: | ||
| 1395 | state = SDL_POWERSTATE_UNKNOWN; | ||
| 1396 | percent = 0; | ||
| 1397 | break; | ||
| 1398 | } | ||
| 1399 | SDL_SendJoystickPowerInfo(joystick, state, percent); | ||
| 1400 | } | ||
| 1401 | |||
| 1402 | HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp); | ||
| 1403 | |||
| 1404 | SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state)); | ||
| 1405 | } | ||
| 1406 | |||
| 1407 | static void HIDAPI_DriverPS5_HandleStatePacketAlt(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketAlt_t *packet, Uint64 timestamp) | ||
| 1408 | { | ||
| 1409 | static const float TOUCHPAD_SCALEX = 1.0f / 1920; | ||
| 1410 | static const float TOUCHPAD_SCALEY = 1.0f / 1070; | ||
| 1411 | bool touchpad_down; | ||
| 1412 | int touchpad_x, touchpad_y; | ||
| 1413 | |||
| 1414 | if (ctx->report_touchpad) { | ||
| 1415 | touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0); | ||
| 1416 | touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8); | ||
| 1417 | touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4); | ||
| 1418 | SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); | ||
| 1419 | |||
| 1420 | touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0); | ||
| 1421 | touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8); | ||
| 1422 | touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4); | ||
| 1423 | SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f); | ||
| 1424 | } | ||
| 1425 | |||
| 1426 | HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp); | ||
| 1427 | |||
| 1428 | SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state)); | ||
| 1429 | } | ||
| 1430 | |||
| 1431 | static bool VerifyCRC(Uint8 *data, int size) | ||
| 1432 | { | ||
| 1433 | Uint8 ubHdr = 0xA1; // hidp header is part of the CRC calculation | ||
| 1434 | Uint32 unCRC, unPacketCRC; | ||
| 1435 | Uint8 *packetCRC = data + size - sizeof(unPacketCRC); | ||
| 1436 | unCRC = SDL_crc32(0, &ubHdr, 1); | ||
| 1437 | unCRC = SDL_crc32(unCRC, data, (size_t)(size - sizeof(unCRC))); | ||
| 1438 | |||
| 1439 | unPacketCRC = LOAD32(packetCRC[0], | ||
| 1440 | packetCRC[1], | ||
| 1441 | packetCRC[2], | ||
| 1442 | packetCRC[3]); | ||
| 1443 | return (unCRC == unPacketCRC); | ||
| 1444 | } | ||
| 1445 | |||
| 1446 | static bool HIDAPI_DriverPS5_IsPacketValid(SDL_DriverPS5_Context *ctx, Uint8 *data, int size) | ||
| 1447 | { | ||
| 1448 | switch (data[0]) { | ||
| 1449 | case k_EPS5ReportIdState: | ||
| 1450 | if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) { | ||
| 1451 | // The report timestamp doesn't change when the controller isn't connected | ||
| 1452 | PS5StatePacketAlt_t *packet = (PS5StatePacketAlt_t *)&data[1]; | ||
| 1453 | if (SDL_memcmp(packet->rgucPacketSequence, ctx->last_state.state.rgucPacketSequence, sizeof(packet->rgucPacketSequence)) == 0) { | ||
| 1454 | return false; | ||
| 1455 | } | ||
| 1456 | if (ctx->last_state.alt_state.rgucAccelX[0] == 0 && ctx->last_state.alt_state.rgucAccelX[1] == 0 && | ||
| 1457 | ctx->last_state.alt_state.rgucAccelY[0] == 0 && ctx->last_state.alt_state.rgucAccelY[1] == 0 && | ||
| 1458 | ctx->last_state.alt_state.rgucAccelZ[0] == 0 && ctx->last_state.alt_state.rgucAccelZ[1] == 0) { | ||
| 1459 | // We don't have any state to compare yet, go ahead and copy it | ||
| 1460 | SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS5StatePacketAlt_t)); | ||
| 1461 | return false; | ||
| 1462 | } | ||
| 1463 | } | ||
| 1464 | return true; | ||
| 1465 | |||
| 1466 | case k_EPS5ReportIdBluetoothState: | ||
| 1467 | if (VerifyCRC(data, size)) { | ||
| 1468 | return true; | ||
| 1469 | } | ||
| 1470 | break; | ||
| 1471 | default: | ||
| 1472 | break; | ||
| 1473 | } | ||
| 1474 | return false; | ||
| 1475 | } | ||
| 1476 | |||
| 1477 | static bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 1478 | { | ||
| 1479 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; | ||
| 1480 | SDL_Joystick *joystick = NULL; | ||
| 1481 | Uint8 data[USB_PACKET_LENGTH * 2]; | ||
| 1482 | int size; | ||
| 1483 | int packet_count = 0; | ||
| 1484 | Uint64 now = SDL_GetTicks(); | ||
| 1485 | |||
| 1486 | if (device->num_joysticks > 0) { | ||
| 1487 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 1488 | } | ||
| 1489 | |||
| 1490 | while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { | ||
| 1491 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 1492 | |||
| 1493 | #ifdef DEBUG_PS5_PROTOCOL | ||
| 1494 | HIDAPI_DumpPacket("PS5 packet: size = %d", data, size); | ||
| 1495 | #endif | ||
| 1496 | if (!HIDAPI_DriverPS5_IsPacketValid(ctx, data, size)) { | ||
| 1497 | continue; | ||
| 1498 | } | ||
| 1499 | |||
| 1500 | ++packet_count; | ||
| 1501 | ctx->last_packet = now; | ||
| 1502 | |||
| 1503 | if (!joystick) { | ||
| 1504 | continue; | ||
| 1505 | } | ||
| 1506 | |||
| 1507 | switch (data[0]) { | ||
| 1508 | case k_EPS5ReportIdState: | ||
| 1509 | if (size == 10 || size == 78) { | ||
| 1510 | HIDAPI_DriverPS5_HandleSimpleStatePacket(joystick, device->dev, ctx, (PS5SimpleStatePacket_t *)&data[1], timestamp); | ||
| 1511 | } else { | ||
| 1512 | if (ctx->use_alternate_report) { | ||
| 1513 | HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[1], timestamp); | ||
| 1514 | } else { | ||
| 1515 | HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[1], timestamp); | ||
| 1516 | } | ||
| 1517 | } | ||
| 1518 | break; | ||
| 1519 | case k_EPS5ReportIdBluetoothState: | ||
| 1520 | // This is the extended report, we can enable effects now in auto mode | ||
| 1521 | HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(ctx); | ||
| 1522 | |||
| 1523 | if (ctx->use_alternate_report) { | ||
| 1524 | HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[2], timestamp); | ||
| 1525 | } else { | ||
| 1526 | HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[2], timestamp); | ||
| 1527 | } | ||
| 1528 | if (ctx->led_reset_state == k_EDS5LEDResetStatePending) { | ||
| 1529 | HIDAPI_DriverPS5_CheckPendingLEDReset(ctx); | ||
| 1530 | } | ||
| 1531 | break; | ||
| 1532 | default: | ||
| 1533 | #ifdef DEBUG_JOYSTICK | ||
| 1534 | SDL_Log("Unknown PS5 packet: 0x%.2x", data[0]); | ||
| 1535 | #endif | ||
| 1536 | break; | ||
| 1537 | } | ||
| 1538 | } | ||
| 1539 | |||
| 1540 | if (device->is_bluetooth) { | ||
| 1541 | if (packet_count == 0) { | ||
| 1542 | // Check to see if it looks like the device disconnected | ||
| 1543 | if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) { | ||
| 1544 | // Send an empty output report to tickle the Bluetooth stack | ||
| 1545 | HIDAPI_DriverPS5_TickleBluetooth(device); | ||
| 1546 | ctx->last_packet = now; | ||
| 1547 | } | ||
| 1548 | } else { | ||
| 1549 | // Reconnect the Bluetooth device once the USB device is gone | ||
| 1550 | if (device->num_joysticks == 0 && | ||
| 1551 | !HIDAPI_HasConnectedUSBDevice(device->serial)) { | ||
| 1552 | HIDAPI_JoystickConnected(device, NULL); | ||
| 1553 | } | ||
| 1554 | } | ||
| 1555 | } | ||
| 1556 | |||
| 1557 | if (ctx->is_nacon_dongle) { | ||
| 1558 | if (packet_count == 0) { | ||
| 1559 | if (device->num_joysticks > 0) { | ||
| 1560 | // Check to see if it looks like the device disconnected | ||
| 1561 | if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) { | ||
| 1562 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 1563 | } | ||
| 1564 | } | ||
| 1565 | } else { | ||
| 1566 | if (device->num_joysticks == 0) { | ||
| 1567 | HIDAPI_JoystickConnected(device, NULL); | ||
| 1568 | } | ||
| 1569 | } | ||
| 1570 | } | ||
| 1571 | |||
| 1572 | if (packet_count == 0 && size < 0 && device->num_joysticks > 0) { | ||
| 1573 | // Read error, device is disconnected | ||
| 1574 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 1575 | } | ||
| 1576 | return (size >= 0); | ||
| 1577 | } | ||
| 1578 | |||
| 1579 | static void HIDAPI_DriverPS5_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1580 | { | ||
| 1581 | SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; | ||
| 1582 | |||
| 1583 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, | ||
| 1584 | SDL_PS5EnhancedReportsChanged, ctx); | ||
| 1585 | |||
| 1586 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, | ||
| 1587 | SDL_PS5PlayerLEDHintChanged, ctx); | ||
| 1588 | |||
| 1589 | ctx->joystick = NULL; | ||
| 1590 | |||
| 1591 | ctx->report_sensors = false; | ||
| 1592 | ctx->enhanced_mode = false; | ||
| 1593 | ctx->enhanced_mode_available = false; | ||
| 1594 | } | ||
| 1595 | |||
| 1596 | static void HIDAPI_DriverPS5_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 1597 | { | ||
| 1598 | } | ||
| 1599 | |||
| 1600 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5 = { | ||
| 1601 | SDL_HINT_JOYSTICK_HIDAPI_PS5, | ||
| 1602 | true, | ||
| 1603 | HIDAPI_DriverPS5_RegisterHints, | ||
| 1604 | HIDAPI_DriverPS5_UnregisterHints, | ||
| 1605 | HIDAPI_DriverPS5_IsEnabled, | ||
| 1606 | HIDAPI_DriverPS5_IsSupportedDevice, | ||
| 1607 | HIDAPI_DriverPS5_InitDevice, | ||
| 1608 | HIDAPI_DriverPS5_GetDevicePlayerIndex, | ||
| 1609 | HIDAPI_DriverPS5_SetDevicePlayerIndex, | ||
| 1610 | HIDAPI_DriverPS5_UpdateDevice, | ||
| 1611 | HIDAPI_DriverPS5_OpenJoystick, | ||
| 1612 | HIDAPI_DriverPS5_RumbleJoystick, | ||
| 1613 | HIDAPI_DriverPS5_RumbleJoystickTriggers, | ||
| 1614 | HIDAPI_DriverPS5_GetJoystickCapabilities, | ||
| 1615 | HIDAPI_DriverPS5_SetJoystickLED, | ||
| 1616 | HIDAPI_DriverPS5_SendJoystickEffect, | ||
| 1617 | HIDAPI_DriverPS5_SetJoystickSensorsEnabled, | ||
| 1618 | HIDAPI_DriverPS5_CloseJoystick, | ||
| 1619 | HIDAPI_DriverPS5_FreeDevice, | ||
| 1620 | }; | ||
| 1621 | |||
| 1622 | #endif // SDL_JOYSTICK_HIDAPI_PS5 | ||
| 1623 | |||
| 1624 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | // Handle rumble on a separate thread so it doesn't block the application | ||
| 26 | |||
| 27 | #include "SDL_hidapijoystick_c.h" | ||
| 28 | #include "SDL_hidapi_rumble.h" | ||
| 29 | #include "../../thread/SDL_systhread.h" | ||
| 30 | |||
| 31 | typedef struct SDL_HIDAPI_RumbleRequest | ||
| 32 | { | ||
| 33 | SDL_HIDAPI_Device *device; | ||
| 34 | Uint8 data[2 * USB_PACKET_LENGTH]; // need enough space for the biggest report: dualshock4 is 78 bytes | ||
| 35 | int size; | ||
| 36 | SDL_HIDAPI_RumbleSentCallback callback; | ||
| 37 | void *userdata; | ||
| 38 | struct SDL_HIDAPI_RumbleRequest *prev; | ||
| 39 | |||
| 40 | } SDL_HIDAPI_RumbleRequest; | ||
| 41 | |||
| 42 | typedef struct SDL_HIDAPI_RumbleContext | ||
| 43 | { | ||
| 44 | SDL_AtomicInt initialized; | ||
| 45 | SDL_AtomicInt running; | ||
| 46 | SDL_Thread *thread; | ||
| 47 | SDL_Semaphore *request_sem; | ||
| 48 | SDL_HIDAPI_RumbleRequest *requests_head; | ||
| 49 | SDL_HIDAPI_RumbleRequest *requests_tail; | ||
| 50 | } SDL_HIDAPI_RumbleContext; | ||
| 51 | |||
| 52 | #ifndef SDL_THREAD_SAFETY_ANALYSIS | ||
| 53 | static | ||
| 54 | #endif | ||
| 55 | SDL_Mutex *SDL_HIDAPI_rumble_lock; | ||
| 56 | static SDL_HIDAPI_RumbleContext rumble_context SDL_GUARDED_BY(SDL_HIDAPI_rumble_lock); | ||
| 57 | |||
| 58 | static int SDLCALL SDL_HIDAPI_RumbleThread(void *data) | ||
| 59 | { | ||
| 60 | SDL_HIDAPI_RumbleContext *ctx = (SDL_HIDAPI_RumbleContext *)data; | ||
| 61 | |||
| 62 | SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH); | ||
| 63 | |||
| 64 | while (SDL_GetAtomicInt(&ctx->running)) { | ||
| 65 | SDL_HIDAPI_RumbleRequest *request = NULL; | ||
| 66 | |||
| 67 | SDL_WaitSemaphore(ctx->request_sem); | ||
| 68 | |||
| 69 | SDL_LockMutex(SDL_HIDAPI_rumble_lock); | ||
| 70 | request = ctx->requests_tail; | ||
| 71 | if (request) { | ||
| 72 | if (request == ctx->requests_head) { | ||
| 73 | ctx->requests_head = NULL; | ||
| 74 | } | ||
| 75 | ctx->requests_tail = request->prev; | ||
| 76 | } | ||
| 77 | SDL_UnlockMutex(SDL_HIDAPI_rumble_lock); | ||
| 78 | |||
| 79 | if (request) { | ||
| 80 | SDL_LockMutex(request->device->dev_lock); | ||
| 81 | if (request->device->dev) { | ||
| 82 | #ifdef DEBUG_RUMBLE | ||
| 83 | HIDAPI_DumpPacket("Rumble packet: size = %d", request->data, request->size); | ||
| 84 | #endif | ||
| 85 | SDL_hid_write(request->device->dev, request->data, request->size); | ||
| 86 | } | ||
| 87 | SDL_UnlockMutex(request->device->dev_lock); | ||
| 88 | if (request->callback) { | ||
| 89 | request->callback(request->userdata); | ||
| 90 | } | ||
| 91 | (void)SDL_AtomicDecRef(&request->device->rumble_pending); | ||
| 92 | SDL_free(request); | ||
| 93 | |||
| 94 | // Make sure we're not starving report reads when there's lots of rumble | ||
| 95 | SDL_Delay(10); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | return 0; | ||
| 99 | } | ||
| 100 | |||
| 101 | static void SDL_HIDAPI_StopRumbleThread(SDL_HIDAPI_RumbleContext *ctx) | ||
| 102 | { | ||
| 103 | SDL_HIDAPI_RumbleRequest *request; | ||
| 104 | |||
| 105 | SDL_SetAtomicInt(&ctx->running, false); | ||
| 106 | |||
| 107 | if (ctx->thread) { | ||
| 108 | int result; | ||
| 109 | |||
| 110 | SDL_SignalSemaphore(ctx->request_sem); | ||
| 111 | SDL_WaitThread(ctx->thread, &result); | ||
| 112 | ctx->thread = NULL; | ||
| 113 | } | ||
| 114 | |||
| 115 | SDL_LockMutex(SDL_HIDAPI_rumble_lock); | ||
| 116 | while (ctx->requests_tail) { | ||
| 117 | request = ctx->requests_tail; | ||
| 118 | if (request == ctx->requests_head) { | ||
| 119 | ctx->requests_head = NULL; | ||
| 120 | } | ||
| 121 | ctx->requests_tail = request->prev; | ||
| 122 | |||
| 123 | if (request->callback) { | ||
| 124 | request->callback(request->userdata); | ||
| 125 | } | ||
| 126 | (void)SDL_AtomicDecRef(&request->device->rumble_pending); | ||
| 127 | SDL_free(request); | ||
| 128 | } | ||
| 129 | SDL_UnlockMutex(SDL_HIDAPI_rumble_lock); | ||
| 130 | |||
| 131 | if (ctx->request_sem) { | ||
| 132 | SDL_DestroySemaphore(ctx->request_sem); | ||
| 133 | ctx->request_sem = NULL; | ||
| 134 | } | ||
| 135 | |||
| 136 | if (SDL_HIDAPI_rumble_lock) { | ||
| 137 | SDL_DestroyMutex(SDL_HIDAPI_rumble_lock); | ||
| 138 | SDL_HIDAPI_rumble_lock = NULL; | ||
| 139 | } | ||
| 140 | |||
| 141 | SDL_SetAtomicInt(&ctx->initialized, false); | ||
| 142 | } | ||
| 143 | |||
| 144 | static bool SDL_HIDAPI_StartRumbleThread(SDL_HIDAPI_RumbleContext *ctx) | ||
| 145 | { | ||
| 146 | SDL_HIDAPI_rumble_lock = SDL_CreateMutex(); | ||
| 147 | if (!SDL_HIDAPI_rumble_lock) { | ||
| 148 | SDL_HIDAPI_StopRumbleThread(ctx); | ||
| 149 | return false; | ||
| 150 | } | ||
| 151 | |||
| 152 | ctx->request_sem = SDL_CreateSemaphore(0); | ||
| 153 | if (!ctx->request_sem) { | ||
| 154 | SDL_HIDAPI_StopRumbleThread(ctx); | ||
| 155 | return false; | ||
| 156 | } | ||
| 157 | |||
| 158 | SDL_SetAtomicInt(&ctx->running, true); | ||
| 159 | ctx->thread = SDL_CreateThread(SDL_HIDAPI_RumbleThread, "HIDAPI Rumble", ctx); | ||
| 160 | if (!ctx->thread) { | ||
| 161 | SDL_HIDAPI_StopRumbleThread(ctx); | ||
| 162 | return false; | ||
| 163 | } | ||
| 164 | return true; | ||
| 165 | } | ||
| 166 | |||
| 167 | bool SDL_HIDAPI_LockRumble(void) | ||
| 168 | { | ||
| 169 | SDL_HIDAPI_RumbleContext *ctx = &rumble_context; | ||
| 170 | |||
| 171 | if (SDL_CompareAndSwapAtomicInt(&ctx->initialized, false, true)) { | ||
| 172 | if (!SDL_HIDAPI_StartRumbleThread(ctx)) { | ||
| 173 | return false; | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | SDL_LockMutex(SDL_HIDAPI_rumble_lock); | ||
| 178 | return true; | ||
| 179 | } | ||
| 180 | |||
| 181 | bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size) | ||
| 182 | { | ||
| 183 | SDL_HIDAPI_RumbleContext *ctx = &rumble_context; | ||
| 184 | SDL_HIDAPI_RumbleRequest *request, *found; | ||
| 185 | |||
| 186 | found = NULL; | ||
| 187 | for (request = ctx->requests_tail; request; request = request->prev) { | ||
| 188 | if (request->device == device) { | ||
| 189 | found = request; | ||
| 190 | } | ||
| 191 | } | ||
| 192 | if (found) { | ||
| 193 | *data = found->data; | ||
| 194 | *size = &found->size; | ||
| 195 | *maximum_size = sizeof(found->data); | ||
| 196 | return true; | ||
| 197 | } | ||
| 198 | return false; | ||
| 199 | } | ||
| 200 | |||
| 201 | int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size) | ||
| 202 | { | ||
| 203 | return SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device, data, size, NULL, NULL); | ||
| 204 | } | ||
| 205 | |||
| 206 | int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size, SDL_HIDAPI_RumbleSentCallback callback, void *userdata) | ||
| 207 | { | ||
| 208 | SDL_HIDAPI_RumbleContext *ctx = &rumble_context; | ||
| 209 | SDL_HIDAPI_RumbleRequest *request; | ||
| 210 | |||
| 211 | if (size > sizeof(request->data)) { | ||
| 212 | SDL_HIDAPI_UnlockRumble(); | ||
| 213 | SDL_SetError("Couldn't send rumble, size %d is greater than %d", size, (int)sizeof(request->data)); | ||
| 214 | return -1; | ||
| 215 | } | ||
| 216 | |||
| 217 | request = (SDL_HIDAPI_RumbleRequest *)SDL_calloc(1, sizeof(*request)); | ||
| 218 | if (!request) { | ||
| 219 | SDL_HIDAPI_UnlockRumble(); | ||
| 220 | return -1; | ||
| 221 | } | ||
| 222 | request->device = device; | ||
| 223 | SDL_memcpy(request->data, data, size); | ||
| 224 | request->size = size; | ||
| 225 | request->callback = callback; | ||
| 226 | request->userdata = userdata; | ||
| 227 | |||
| 228 | SDL_AtomicIncRef(&device->rumble_pending); | ||
| 229 | |||
| 230 | if (ctx->requests_head) { | ||
| 231 | ctx->requests_head->prev = request; | ||
| 232 | } else { | ||
| 233 | ctx->requests_tail = request; | ||
| 234 | } | ||
| 235 | ctx->requests_head = request; | ||
| 236 | |||
| 237 | // Make sure we unlock before posting the semaphore so the rumble thread can run immediately | ||
| 238 | SDL_HIDAPI_UnlockRumble(); | ||
| 239 | |||
| 240 | SDL_SignalSemaphore(ctx->request_sem); | ||
| 241 | |||
| 242 | return size; | ||
| 243 | } | ||
| 244 | |||
| 245 | void SDL_HIDAPI_UnlockRumble(void) | ||
| 246 | { | ||
| 247 | SDL_UnlockMutex(SDL_HIDAPI_rumble_lock); | ||
| 248 | } | ||
| 249 | |||
| 250 | int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size) | ||
| 251 | { | ||
| 252 | Uint8 *pending_data; | ||
| 253 | int *pending_size; | ||
| 254 | int maximum_size; | ||
| 255 | |||
| 256 | if (size <= 0) { | ||
| 257 | SDL_SetError("Tried to send rumble with invalid size"); | ||
| 258 | return -1; | ||
| 259 | } | ||
| 260 | |||
| 261 | if (!SDL_HIDAPI_LockRumble()) { | ||
| 262 | return -1; | ||
| 263 | } | ||
| 264 | |||
| 265 | // check if there is a pending request for the device and update it | ||
| 266 | if (SDL_HIDAPI_GetPendingRumbleLocked(device, &pending_data, &pending_size, &maximum_size) && | ||
| 267 | size == *pending_size && data[0] == pending_data[0]) { | ||
| 268 | SDL_memcpy(pending_data, data, size); | ||
| 269 | SDL_HIDAPI_UnlockRumble(); | ||
| 270 | return size; | ||
| 271 | } | ||
| 272 | |||
| 273 | return SDL_HIDAPI_SendRumbleAndUnlock(device, data, size); | ||
| 274 | } | ||
| 275 | |||
| 276 | void SDL_HIDAPI_QuitRumble(void) | ||
| 277 | { | ||
| 278 | SDL_HIDAPI_RumbleContext *ctx = &rumble_context; | ||
| 279 | |||
| 280 | if (SDL_GetAtomicInt(&ctx->running)) { | ||
| 281 | SDL_HIDAPI_StopRumbleThread(ctx); | ||
| 282 | } | ||
| 283 | } | ||
| 284 | |||
| 285 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | // Handle rumble on a separate thread so it doesn't block the application | ||
| 26 | |||
| 27 | // Advanced API | ||
| 28 | #ifdef SDL_THREAD_SAFETY_ANALYSIS | ||
| 29 | extern SDL_Mutex *SDL_HIDAPI_rumble_lock; | ||
| 30 | #endif | ||
| 31 | bool SDL_HIDAPI_LockRumble(void) SDL_TRY_ACQUIRE(0, SDL_HIDAPI_rumble_lock); | ||
| 32 | bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size); | ||
| 33 | int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size) SDL_RELEASE(SDL_HIDAPI_rumble_lock); | ||
| 34 | typedef void (*SDL_HIDAPI_RumbleSentCallback)(void *userdata); | ||
| 35 | 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); | ||
| 36 | void SDL_HIDAPI_UnlockRumble(void) SDL_RELEASE(SDL_HIDAPI_rumble_lock); | ||
| 37 | |||
| 38 | // Simple API, will replace any pending rumble with the new data | ||
| 39 | int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size); | ||
| 40 | void SDL_HIDAPI_QuitRumble(void); | ||
| 41 | |||
| 42 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../SDL_sysjoystick.h" | ||
| 26 | #include "SDL_hidapijoystick_c.h" | ||
| 27 | #include "SDL_hidapi_rumble.h" | ||
| 28 | |||
| 29 | #ifdef SDL_JOYSTICK_HIDAPI_SHIELD | ||
| 30 | |||
| 31 | // Define this if you want to log all packets from the controller | ||
| 32 | // #define DEBUG_SHIELD_PROTOCOL | ||
| 33 | |||
| 34 | #define CMD_BATTERY_STATE 0x07 | ||
| 35 | #define CMD_RUMBLE 0x39 | ||
| 36 | #define CMD_CHARGE_STATE 0x3A | ||
| 37 | |||
| 38 | // Milliseconds between polls of battery state | ||
| 39 | #define BATTERY_POLL_INTERVAL_MS 60000 | ||
| 40 | |||
| 41 | // Milliseconds between retransmission of rumble to keep motors running | ||
| 42 | #define RUMBLE_REFRESH_INTERVAL_MS 500 | ||
| 43 | |||
| 44 | // Reports that are too small are dropped over Bluetooth | ||
| 45 | #define HID_REPORT_SIZE 33 | ||
| 46 | |||
| 47 | enum | ||
| 48 | { | ||
| 49 | SDL_GAMEPAD_BUTTON_SHIELD_SHARE = 11, | ||
| 50 | SDL_GAMEPAD_BUTTON_SHIELD_V103_TOUCHPAD, | ||
| 51 | SDL_GAMEPAD_BUTTON_SHIELD_V103_MINUS, | ||
| 52 | SDL_GAMEPAD_BUTTON_SHIELD_V103_PLUS, | ||
| 53 | SDL_GAMEPAD_NUM_SHIELD_V103_BUTTONS, | ||
| 54 | |||
| 55 | SDL_GAMEPAD_NUM_SHIELD_V104_BUTTONS = SDL_GAMEPAD_BUTTON_SHIELD_SHARE + 1, | ||
| 56 | }; | ||
| 57 | |||
| 58 | typedef enum | ||
| 59 | { | ||
| 60 | k_ShieldReportIdControllerState = 0x01, | ||
| 61 | k_ShieldReportIdControllerTouch = 0x02, | ||
| 62 | k_ShieldReportIdCommandResponse = 0x03, | ||
| 63 | k_ShieldReportIdCommandRequest = 0x04, | ||
| 64 | } EShieldReportId; | ||
| 65 | |||
| 66 | // This same report structure is used for both requests and responses | ||
| 67 | typedef struct | ||
| 68 | { | ||
| 69 | Uint8 report_id; | ||
| 70 | Uint8 cmd; | ||
| 71 | Uint8 seq_num; | ||
| 72 | Uint8 payload[HID_REPORT_SIZE - 3]; | ||
| 73 | } ShieldCommandReport_t; | ||
| 74 | SDL_COMPILE_TIME_ASSERT(ShieldCommandReport_t, sizeof(ShieldCommandReport_t) == HID_REPORT_SIZE); | ||
| 75 | |||
| 76 | typedef struct | ||
| 77 | { | ||
| 78 | Uint8 seq_num; | ||
| 79 | |||
| 80 | bool has_charging; | ||
| 81 | Uint8 charging; | ||
| 82 | bool has_battery_level; | ||
| 83 | Uint8 battery_level; | ||
| 84 | Uint64 last_battery_query_time; | ||
| 85 | |||
| 86 | bool rumble_report_pending; | ||
| 87 | bool rumble_update_pending; | ||
| 88 | Uint8 left_motor_amplitude; | ||
| 89 | Uint8 right_motor_amplitude; | ||
| 90 | Uint64 last_rumble_time; | ||
| 91 | |||
| 92 | Uint8 last_state[USB_PACKET_LENGTH]; | ||
| 93 | } SDL_DriverShield_Context; | ||
| 94 | |||
| 95 | static void HIDAPI_DriverShield_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 96 | { | ||
| 97 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, callback, userdata); | ||
| 98 | } | ||
| 99 | |||
| 100 | static void HIDAPI_DriverShield_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 101 | { | ||
| 102 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, callback, userdata); | ||
| 103 | } | ||
| 104 | |||
| 105 | static bool HIDAPI_DriverShield_IsEnabled(void) | ||
| 106 | { | ||
| 107 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); | ||
| 108 | } | ||
| 109 | |||
| 110 | 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) | ||
| 111 | { | ||
| 112 | return SDL_IsJoystickNVIDIASHIELDController(vendor_id, product_id); | ||
| 113 | } | ||
| 114 | |||
| 115 | static bool HIDAPI_DriverShield_InitDevice(SDL_HIDAPI_Device *device) | ||
| 116 | { | ||
| 117 | SDL_DriverShield_Context *ctx; | ||
| 118 | |||
| 119 | ctx = (SDL_DriverShield_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 120 | if (!ctx) { | ||
| 121 | return false; | ||
| 122 | } | ||
| 123 | device->context = ctx; | ||
| 124 | |||
| 125 | HIDAPI_SetDeviceName(device, "NVIDIA SHIELD Controller"); | ||
| 126 | |||
| 127 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 128 | } | ||
| 129 | |||
| 130 | static int HIDAPI_DriverShield_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 131 | { | ||
| 132 | return -1; | ||
| 133 | } | ||
| 134 | |||
| 135 | static void HIDAPI_DriverShield_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 136 | { | ||
| 137 | } | ||
| 138 | |||
| 139 | static bool HIDAPI_DriverShield_SendCommand(SDL_HIDAPI_Device *device, Uint8 cmd, const void *data, int size) | ||
| 140 | { | ||
| 141 | SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context; | ||
| 142 | ShieldCommandReport_t cmd_pkt; | ||
| 143 | |||
| 144 | if (size > sizeof(cmd_pkt.payload)) { | ||
| 145 | return SDL_SetError("Command data exceeds HID report size"); | ||
| 146 | } | ||
| 147 | |||
| 148 | if (!SDL_HIDAPI_LockRumble()) { | ||
| 149 | return false; | ||
| 150 | } | ||
| 151 | |||
| 152 | cmd_pkt.report_id = k_ShieldReportIdCommandRequest; | ||
| 153 | cmd_pkt.cmd = cmd; | ||
| 154 | cmd_pkt.seq_num = ctx->seq_num++; | ||
| 155 | if (data) { | ||
| 156 | SDL_memcpy(cmd_pkt.payload, data, size); | ||
| 157 | } | ||
| 158 | |||
| 159 | // Zero unused data in the payload | ||
| 160 | if (size != sizeof(cmd_pkt.payload)) { | ||
| 161 | SDL_memset(&cmd_pkt.payload[size], 0, sizeof(cmd_pkt.payload) - size); | ||
| 162 | } | ||
| 163 | |||
| 164 | if (SDL_HIDAPI_SendRumbleAndUnlock(device, (Uint8 *)&cmd_pkt, sizeof(cmd_pkt)) != sizeof(cmd_pkt)) { | ||
| 165 | return SDL_SetError("Couldn't send command packet"); | ||
| 166 | } | ||
| 167 | |||
| 168 | return true; | ||
| 169 | } | ||
| 170 | |||
| 171 | static bool HIDAPI_DriverShield_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 172 | { | ||
| 173 | SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context; | ||
| 174 | |||
| 175 | SDL_AssertJoysticksLocked(); | ||
| 176 | |||
| 177 | ctx->rumble_report_pending = false; | ||
| 178 | ctx->rumble_update_pending = false; | ||
| 179 | ctx->left_motor_amplitude = 0; | ||
| 180 | ctx->right_motor_amplitude = 0; | ||
| 181 | ctx->last_rumble_time = 0; | ||
| 182 | SDL_zeroa(ctx->last_state); | ||
| 183 | |||
| 184 | // Initialize the joystick capabilities | ||
| 185 | if (device->product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) { | ||
| 186 | joystick->nbuttons = SDL_GAMEPAD_NUM_SHIELD_V103_BUTTONS; | ||
| 187 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 188 | joystick->nhats = 1; | ||
| 189 | |||
| 190 | SDL_PrivateJoystickAddTouchpad(joystick, 1); | ||
| 191 | } else { | ||
| 192 | joystick->nbuttons = SDL_GAMEPAD_NUM_SHIELD_V104_BUTTONS; | ||
| 193 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 194 | joystick->nhats = 1; | ||
| 195 | } | ||
| 196 | |||
| 197 | // Request battery and charging info | ||
| 198 | ctx->last_battery_query_time = SDL_GetTicks(); | ||
| 199 | HIDAPI_DriverShield_SendCommand(device, CMD_CHARGE_STATE, NULL, 0); | ||
| 200 | HIDAPI_DriverShield_SendCommand(device, CMD_BATTERY_STATE, NULL, 0); | ||
| 201 | |||
| 202 | return true; | ||
| 203 | } | ||
| 204 | |||
| 205 | static bool HIDAPI_DriverShield_SendNextRumble(SDL_HIDAPI_Device *device) | ||
| 206 | { | ||
| 207 | SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context; | ||
| 208 | Uint8 rumble_data[3]; | ||
| 209 | |||
| 210 | if (!ctx->rumble_update_pending) { | ||
| 211 | return true; | ||
| 212 | } | ||
| 213 | |||
| 214 | rumble_data[0] = 0x01; // enable | ||
| 215 | rumble_data[1] = ctx->left_motor_amplitude; | ||
| 216 | rumble_data[2] = ctx->right_motor_amplitude; | ||
| 217 | |||
| 218 | ctx->rumble_update_pending = false; | ||
| 219 | ctx->last_rumble_time = SDL_GetTicks(); | ||
| 220 | |||
| 221 | return HIDAPI_DriverShield_SendCommand(device, CMD_RUMBLE, rumble_data, sizeof(rumble_data)); | ||
| 222 | } | ||
| 223 | |||
| 224 | static bool HIDAPI_DriverShield_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 225 | { | ||
| 226 | if (device->product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) { | ||
| 227 | Uint8 rumble_packet[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; | ||
| 228 | |||
| 229 | rumble_packet[2] = (low_frequency_rumble >> 8); | ||
| 230 | rumble_packet[4] = (high_frequency_rumble >> 8); | ||
| 231 | |||
| 232 | if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { | ||
| 233 | return SDL_SetError("Couldn't send rumble packet"); | ||
| 234 | } | ||
| 235 | return true; | ||
| 236 | |||
| 237 | } else { | ||
| 238 | SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context; | ||
| 239 | |||
| 240 | // The rumble motors are quite intense, so tone down the intensity like the official driver does | ||
| 241 | ctx->left_motor_amplitude = low_frequency_rumble >> 11; | ||
| 242 | ctx->right_motor_amplitude = high_frequency_rumble >> 11; | ||
| 243 | ctx->rumble_update_pending = true; | ||
| 244 | |||
| 245 | if (ctx->rumble_report_pending) { | ||
| 246 | // We will service this after the hardware acknowledges the previous request | ||
| 247 | return true; | ||
| 248 | } | ||
| 249 | |||
| 250 | return HIDAPI_DriverShield_SendNextRumble(device); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | static bool HIDAPI_DriverShield_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 255 | { | ||
| 256 | return SDL_Unsupported(); | ||
| 257 | } | ||
| 258 | |||
| 259 | static Uint32 HIDAPI_DriverShield_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 260 | { | ||
| 261 | return SDL_JOYSTICK_CAP_RUMBLE; | ||
| 262 | } | ||
| 263 | |||
| 264 | static bool HIDAPI_DriverShield_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 265 | { | ||
| 266 | return SDL_Unsupported(); | ||
| 267 | } | ||
| 268 | |||
| 269 | static bool HIDAPI_DriverShield_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 270 | { | ||
| 271 | const Uint8 *data_bytes = (const Uint8 *)data; | ||
| 272 | |||
| 273 | if (size > 1) { | ||
| 274 | // Single command byte followed by a variable length payload | ||
| 275 | return HIDAPI_DriverShield_SendCommand(device, data_bytes[0], &data_bytes[1], size - 1); | ||
| 276 | } else if (size == 1) { | ||
| 277 | // Single command byte with no payload | ||
| 278 | return HIDAPI_DriverShield_SendCommand(device, data_bytes[0], NULL, 0); | ||
| 279 | } else { | ||
| 280 | return SDL_SetError("Effect data must at least contain a command byte"); | ||
| 281 | } | ||
| 282 | } | ||
| 283 | |||
| 284 | static bool HIDAPI_DriverShield_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 285 | { | ||
| 286 | return SDL_Unsupported(); | ||
| 287 | } | ||
| 288 | |||
| 289 | static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size) | ||
| 290 | { | ||
| 291 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 292 | |||
| 293 | if (ctx->last_state[3] != data[3]) { | ||
| 294 | Uint8 hat; | ||
| 295 | |||
| 296 | switch (data[3]) { | ||
| 297 | case 0: | ||
| 298 | hat = SDL_HAT_UP; | ||
| 299 | break; | ||
| 300 | case 1: | ||
| 301 | hat = SDL_HAT_RIGHTUP; | ||
| 302 | break; | ||
| 303 | case 2: | ||
| 304 | hat = SDL_HAT_RIGHT; | ||
| 305 | break; | ||
| 306 | case 3: | ||
| 307 | hat = SDL_HAT_RIGHTDOWN; | ||
| 308 | break; | ||
| 309 | case 4: | ||
| 310 | hat = SDL_HAT_DOWN; | ||
| 311 | break; | ||
| 312 | case 5: | ||
| 313 | hat = SDL_HAT_LEFTDOWN; | ||
| 314 | break; | ||
| 315 | case 6: | ||
| 316 | hat = SDL_HAT_LEFT; | ||
| 317 | break; | ||
| 318 | case 7: | ||
| 319 | hat = SDL_HAT_LEFTUP; | ||
| 320 | break; | ||
| 321 | default: | ||
| 322 | hat = SDL_HAT_CENTERED; | ||
| 323 | break; | ||
| 324 | } | ||
| 325 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 326 | } | ||
| 327 | |||
| 328 | if (ctx->last_state[1] != data[1]) { | ||
| 329 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[1] & 0x01) != 0)); | ||
| 330 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[1] & 0x02) != 0)); | ||
| 331 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[1] & 0x04) != 0)); | ||
| 332 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[1] & 0x08) != 0)); | ||
| 333 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0)); | ||
| 334 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0)); | ||
| 335 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x40) != 0)); | ||
| 336 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x80) != 0)); | ||
| 337 | } | ||
| 338 | |||
| 339 | if (ctx->last_state[2] != data[2]) { | ||
| 340 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x02) != 0)); | ||
| 341 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_PLUS, ((data[2] & 0x08) != 0)); | ||
| 342 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_MINUS, ((data[2] & 0x10) != 0)); | ||
| 343 | //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x20) != 0)); | ||
| 344 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x40) != 0)); | ||
| 345 | //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, ((data[2] & 0x80) != 0)); | ||
| 346 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x80) != 0)); | ||
| 347 | } | ||
| 348 | |||
| 349 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[4]) - 0x8000); | ||
| 350 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[6]) - 0x8000); | ||
| 351 | |||
| 352 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[8]) - 0x8000); | ||
| 353 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[10]) - 0x8000); | ||
| 354 | |||
| 355 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[12]) - 0x8000); | ||
| 356 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[14]) - 0x8000); | ||
| 357 | |||
| 358 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 359 | } | ||
| 360 | |||
| 361 | #undef clamp | ||
| 362 | #define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) | ||
| 363 | |||
| 364 | static void HIDAPI_DriverShield_HandleTouchPacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, const Uint8 *data, int size) | ||
| 365 | { | ||
| 366 | bool touchpad_down; | ||
| 367 | float touchpad_x, touchpad_y; | ||
| 368 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 369 | |||
| 370 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_TOUCHPAD, ((data[1] & 0x01) != 0)); | ||
| 371 | |||
| 372 | // It's a triangular pad, but just use the center as the usable touch area | ||
| 373 | touchpad_down = ((data[1] & 0x80) == 0); | ||
| 374 | touchpad_x = clamp((float)(data[2] - 0x70) / 0x50, 0.0f, 1.0f); | ||
| 375 | touchpad_y = clamp((float)(data[4] - 0x40) / 0x15, 0.0f, 1.0f); | ||
| 376 | SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x, touchpad_y, touchpad_down ? 1.0f : 0.0f); | ||
| 377 | } | ||
| 378 | |||
| 379 | static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size) | ||
| 380 | { | ||
| 381 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 382 | |||
| 383 | if (size < 23) { | ||
| 384 | return; | ||
| 385 | } | ||
| 386 | |||
| 387 | if (ctx->last_state[2] != data[2]) { | ||
| 388 | Uint8 hat; | ||
| 389 | |||
| 390 | switch (data[2]) { | ||
| 391 | case 0: | ||
| 392 | hat = SDL_HAT_UP; | ||
| 393 | break; | ||
| 394 | case 1: | ||
| 395 | hat = SDL_HAT_RIGHTUP; | ||
| 396 | break; | ||
| 397 | case 2: | ||
| 398 | hat = SDL_HAT_RIGHT; | ||
| 399 | break; | ||
| 400 | case 3: | ||
| 401 | hat = SDL_HAT_RIGHTDOWN; | ||
| 402 | break; | ||
| 403 | case 4: | ||
| 404 | hat = SDL_HAT_DOWN; | ||
| 405 | break; | ||
| 406 | case 5: | ||
| 407 | hat = SDL_HAT_LEFTDOWN; | ||
| 408 | break; | ||
| 409 | case 6: | ||
| 410 | hat = SDL_HAT_LEFT; | ||
| 411 | break; | ||
| 412 | case 7: | ||
| 413 | hat = SDL_HAT_LEFTUP; | ||
| 414 | break; | ||
| 415 | default: | ||
| 416 | hat = SDL_HAT_CENTERED; | ||
| 417 | break; | ||
| 418 | } | ||
| 419 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 420 | } | ||
| 421 | |||
| 422 | if (ctx->last_state[3] != data[3]) { | ||
| 423 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x01) != 0)); | ||
| 424 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x02) != 0)); | ||
| 425 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x04) != 0)); | ||
| 426 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0)); | ||
| 427 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x10) != 0)); | ||
| 428 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x20) != 0)); | ||
| 429 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x40) != 0)); | ||
| 430 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[3] & 0x80) != 0)); | ||
| 431 | } | ||
| 432 | |||
| 433 | if (ctx->last_state[4] != data[4]) { | ||
| 434 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[4] & 0x01) != 0)); | ||
| 435 | } | ||
| 436 | |||
| 437 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[9]) - 0x8000); | ||
| 438 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[11]) - 0x8000); | ||
| 439 | |||
| 440 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[13]) - 0x8000); | ||
| 441 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[15]) - 0x8000); | ||
| 442 | |||
| 443 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[19]) - 0x8000); | ||
| 444 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[21]) - 0x8000); | ||
| 445 | |||
| 446 | if (ctx->last_state[17] != data[17]) { | ||
| 447 | //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, ((data[17] & 0x01) != 0)); | ||
| 448 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[17] & 0x02) != 0)); | ||
| 449 | //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[17] & 0x04) != 0)); | ||
| 450 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[17] & 0x01) != 0)); | ||
| 451 | } | ||
| 452 | |||
| 453 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 454 | } | ||
| 455 | |||
| 456 | static void HIDAPI_DriverShield_UpdatePowerInfo(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx) | ||
| 457 | { | ||
| 458 | if (!ctx->has_charging || !ctx->has_battery_level) { | ||
| 459 | return; | ||
| 460 | } | ||
| 461 | |||
| 462 | SDL_PowerState state = ctx->charging ? SDL_POWERSTATE_CHARGING : SDL_POWERSTATE_ON_BATTERY; | ||
| 463 | int percent = ctx->battery_level * 20; | ||
| 464 | SDL_SendJoystickPowerInfo(joystick, state, percent); | ||
| 465 | } | ||
| 466 | |||
| 467 | static bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 468 | { | ||
| 469 | SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context; | ||
| 470 | SDL_Joystick *joystick = NULL; | ||
| 471 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 472 | int size = 0; | ||
| 473 | ShieldCommandReport_t *cmd_resp_report; | ||
| 474 | |||
| 475 | if (device->num_joysticks > 0) { | ||
| 476 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 477 | } else { | ||
| 478 | return false; | ||
| 479 | } | ||
| 480 | |||
| 481 | while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { | ||
| 482 | #ifdef DEBUG_SHIELD_PROTOCOL | ||
| 483 | HIDAPI_DumpPacket("NVIDIA SHIELD packet: size = %d", data, size); | ||
| 484 | #endif | ||
| 485 | |||
| 486 | // Byte 0 is HID report ID | ||
| 487 | switch (data[0]) { | ||
| 488 | case k_ShieldReportIdControllerState: | ||
| 489 | if (!joystick) { | ||
| 490 | break; | ||
| 491 | } | ||
| 492 | if (size == 16) { | ||
| 493 | HIDAPI_DriverShield_HandleStatePacketV103(joystick, ctx, data, size); | ||
| 494 | } else { | ||
| 495 | HIDAPI_DriverShield_HandleStatePacketV104(joystick, ctx, data, size); | ||
| 496 | } | ||
| 497 | break; | ||
| 498 | case k_ShieldReportIdControllerTouch: | ||
| 499 | if (!joystick) { | ||
| 500 | break; | ||
| 501 | } | ||
| 502 | HIDAPI_DriverShield_HandleTouchPacketV103(joystick, ctx, data, size); | ||
| 503 | break; | ||
| 504 | case k_ShieldReportIdCommandResponse: | ||
| 505 | cmd_resp_report = (ShieldCommandReport_t *)data; | ||
| 506 | switch (cmd_resp_report->cmd) { | ||
| 507 | case CMD_RUMBLE: | ||
| 508 | ctx->rumble_report_pending = false; | ||
| 509 | HIDAPI_DriverShield_SendNextRumble(device); | ||
| 510 | break; | ||
| 511 | case CMD_CHARGE_STATE: | ||
| 512 | ctx->has_charging = true; | ||
| 513 | ctx->charging = cmd_resp_report->payload[0]; | ||
| 514 | HIDAPI_DriverShield_UpdatePowerInfo(joystick, ctx); | ||
| 515 | break; | ||
| 516 | case CMD_BATTERY_STATE: | ||
| 517 | ctx->has_battery_level = true; | ||
| 518 | ctx->battery_level = cmd_resp_report->payload[2]; | ||
| 519 | HIDAPI_DriverShield_UpdatePowerInfo(joystick, ctx); | ||
| 520 | break; | ||
| 521 | } | ||
| 522 | break; | ||
| 523 | } | ||
| 524 | } | ||
| 525 | |||
| 526 | // Ask for battery state again if we're due for an update | ||
| 527 | if (joystick && SDL_GetTicks() >= (ctx->last_battery_query_time + BATTERY_POLL_INTERVAL_MS)) { | ||
| 528 | ctx->last_battery_query_time = SDL_GetTicks(); | ||
| 529 | HIDAPI_DriverShield_SendCommand(device, CMD_BATTERY_STATE, NULL, 0); | ||
| 530 | } | ||
| 531 | |||
| 532 | // Retransmit rumble packets if they've lasted longer than the hardware supports | ||
| 533 | if ((ctx->left_motor_amplitude != 0 || ctx->right_motor_amplitude != 0) && | ||
| 534 | SDL_GetTicks() >= (ctx->last_rumble_time + RUMBLE_REFRESH_INTERVAL_MS)) { | ||
| 535 | ctx->rumble_update_pending = true; | ||
| 536 | HIDAPI_DriverShield_SendNextRumble(device); | ||
| 537 | } | ||
| 538 | |||
| 539 | if (size < 0) { | ||
| 540 | // Read error, device is disconnected | ||
| 541 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 542 | } | ||
| 543 | return (size >= 0); | ||
| 544 | } | ||
| 545 | |||
| 546 | static void HIDAPI_DriverShield_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 547 | { | ||
| 548 | } | ||
| 549 | |||
| 550 | static void HIDAPI_DriverShield_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 551 | { | ||
| 552 | } | ||
| 553 | |||
| 554 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield = { | ||
| 555 | SDL_HINT_JOYSTICK_HIDAPI_SHIELD, | ||
| 556 | true, | ||
| 557 | HIDAPI_DriverShield_RegisterHints, | ||
| 558 | HIDAPI_DriverShield_UnregisterHints, | ||
| 559 | HIDAPI_DriverShield_IsEnabled, | ||
| 560 | HIDAPI_DriverShield_IsSupportedDevice, | ||
| 561 | HIDAPI_DriverShield_InitDevice, | ||
| 562 | HIDAPI_DriverShield_GetDevicePlayerIndex, | ||
| 563 | HIDAPI_DriverShield_SetDevicePlayerIndex, | ||
| 564 | HIDAPI_DriverShield_UpdateDevice, | ||
| 565 | HIDAPI_DriverShield_OpenJoystick, | ||
| 566 | HIDAPI_DriverShield_RumbleJoystick, | ||
| 567 | HIDAPI_DriverShield_RumbleJoystickTriggers, | ||
| 568 | HIDAPI_DriverShield_GetJoystickCapabilities, | ||
| 569 | HIDAPI_DriverShield_SetJoystickLED, | ||
| 570 | HIDAPI_DriverShield_SendJoystickEffect, | ||
| 571 | HIDAPI_DriverShield_SetJoystickSensorsEnabled, | ||
| 572 | HIDAPI_DriverShield_CloseJoystick, | ||
| 573 | HIDAPI_DriverShield_FreeDevice, | ||
| 574 | }; | ||
| 575 | |||
| 576 | #endif // SDL_JOYSTICK_HIDAPI_SHIELD | ||
| 577 | |||
| 578 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../SDL_sysjoystick.h" | ||
| 26 | #include "SDL_hidapijoystick_c.h" | ||
| 27 | #include "SDL_hidapi_rumble.h" | ||
| 28 | |||
| 29 | #ifdef SDL_JOYSTICK_HIDAPI_STADIA | ||
| 30 | |||
| 31 | // Define this if you want to log all packets from the controller | ||
| 32 | // #define DEBUG_STADIA_PROTOCOL | ||
| 33 | |||
| 34 | enum | ||
| 35 | { | ||
| 36 | SDL_GAMEPAD_BUTTON_STADIA_CAPTURE = 11, | ||
| 37 | SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT, | ||
| 38 | SDL_GAMEPAD_NUM_STADIA_BUTTONS, | ||
| 39 | }; | ||
| 40 | |||
| 41 | typedef struct | ||
| 42 | { | ||
| 43 | bool rumble_supported; | ||
| 44 | Uint8 last_state[USB_PACKET_LENGTH]; | ||
| 45 | } SDL_DriverStadia_Context; | ||
| 46 | |||
| 47 | static void HIDAPI_DriverStadia_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 48 | { | ||
| 49 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata); | ||
| 50 | } | ||
| 51 | |||
| 52 | static void HIDAPI_DriverStadia_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 53 | { | ||
| 54 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata); | ||
| 55 | } | ||
| 56 | |||
| 57 | static bool HIDAPI_DriverStadia_IsEnabled(void) | ||
| 58 | { | ||
| 59 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STADIA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); | ||
| 60 | } | ||
| 61 | |||
| 62 | 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) | ||
| 63 | { | ||
| 64 | return SDL_IsJoystickGoogleStadiaController(vendor_id, product_id); | ||
| 65 | } | ||
| 66 | |||
| 67 | static bool HIDAPI_DriverStadia_InitDevice(SDL_HIDAPI_Device *device) | ||
| 68 | { | ||
| 69 | SDL_DriverStadia_Context *ctx; | ||
| 70 | |||
| 71 | ctx = (SDL_DriverStadia_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 72 | if (!ctx) { | ||
| 73 | return false; | ||
| 74 | } | ||
| 75 | device->context = ctx; | ||
| 76 | |||
| 77 | // Check whether rumble is supported | ||
| 78 | { | ||
| 79 | Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 }; | ||
| 80 | |||
| 81 | if (SDL_hid_write(device->dev, rumble_packet, sizeof(rumble_packet)) >= 0) { | ||
| 82 | ctx->rumble_supported = true; | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | HIDAPI_SetDeviceName(device, "Google Stadia Controller"); | ||
| 87 | |||
| 88 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 89 | } | ||
| 90 | |||
| 91 | static int HIDAPI_DriverStadia_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 92 | { | ||
| 93 | return -1; | ||
| 94 | } | ||
| 95 | |||
| 96 | static void HIDAPI_DriverStadia_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 97 | { | ||
| 98 | } | ||
| 99 | |||
| 100 | static bool HIDAPI_DriverStadia_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 101 | { | ||
| 102 | SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context; | ||
| 103 | |||
| 104 | SDL_AssertJoysticksLocked(); | ||
| 105 | |||
| 106 | SDL_zeroa(ctx->last_state); | ||
| 107 | |||
| 108 | // Initialize the joystick capabilities | ||
| 109 | joystick->nbuttons = SDL_GAMEPAD_NUM_STADIA_BUTTONS; | ||
| 110 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 111 | joystick->nhats = 1; | ||
| 112 | |||
| 113 | return true; | ||
| 114 | } | ||
| 115 | |||
| 116 | static bool HIDAPI_DriverStadia_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 117 | { | ||
| 118 | SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context; | ||
| 119 | |||
| 120 | if (ctx->rumble_supported) { | ||
| 121 | Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 }; | ||
| 122 | |||
| 123 | |||
| 124 | rumble_packet[1] = (low_frequency_rumble & 0xFF); | ||
| 125 | rumble_packet[2] = (low_frequency_rumble >> 8); | ||
| 126 | rumble_packet[3] = (high_frequency_rumble & 0xFF); | ||
| 127 | rumble_packet[4] = (high_frequency_rumble >> 8); | ||
| 128 | |||
| 129 | if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { | ||
| 130 | return SDL_SetError("Couldn't send rumble packet"); | ||
| 131 | } | ||
| 132 | return true; | ||
| 133 | } else { | ||
| 134 | return SDL_Unsupported(); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | static bool HIDAPI_DriverStadia_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 139 | { | ||
| 140 | return SDL_Unsupported(); | ||
| 141 | } | ||
| 142 | |||
| 143 | static Uint32 HIDAPI_DriverStadia_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 144 | { | ||
| 145 | SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context; | ||
| 146 | Uint32 caps = 0; | ||
| 147 | |||
| 148 | if (ctx->rumble_supported) { | ||
| 149 | caps |= SDL_JOYSTICK_CAP_RUMBLE; | ||
| 150 | } | ||
| 151 | return caps; | ||
| 152 | } | ||
| 153 | |||
| 154 | static bool HIDAPI_DriverStadia_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 155 | { | ||
| 156 | return SDL_Unsupported(); | ||
| 157 | } | ||
| 158 | |||
| 159 | static bool HIDAPI_DriverStadia_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 160 | { | ||
| 161 | return SDL_Unsupported(); | ||
| 162 | } | ||
| 163 | |||
| 164 | static bool HIDAPI_DriverStadia_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 165 | { | ||
| 166 | return SDL_Unsupported(); | ||
| 167 | } | ||
| 168 | |||
| 169 | static void HIDAPI_DriverStadia_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverStadia_Context *ctx, Uint8 *data, int size) | ||
| 170 | { | ||
| 171 | Sint16 axis; | ||
| 172 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 173 | |||
| 174 | // The format is the same but the original FW will send 10 bytes and January '21 FW update will send 11 | ||
| 175 | if (size < 10 || data[0] != 0x03) { | ||
| 176 | // We don't know how to handle this report | ||
| 177 | return; | ||
| 178 | } | ||
| 179 | |||
| 180 | if (ctx->last_state[1] != data[1]) { | ||
| 181 | Uint8 hat; | ||
| 182 | |||
| 183 | switch (data[1]) { | ||
| 184 | case 0: | ||
| 185 | hat = SDL_HAT_UP; | ||
| 186 | break; | ||
| 187 | case 1: | ||
| 188 | hat = SDL_HAT_RIGHTUP; | ||
| 189 | break; | ||
| 190 | case 2: | ||
| 191 | hat = SDL_HAT_RIGHT; | ||
| 192 | break; | ||
| 193 | case 3: | ||
| 194 | hat = SDL_HAT_RIGHTDOWN; | ||
| 195 | break; | ||
| 196 | case 4: | ||
| 197 | hat = SDL_HAT_DOWN; | ||
| 198 | break; | ||
| 199 | case 5: | ||
| 200 | hat = SDL_HAT_LEFTDOWN; | ||
| 201 | break; | ||
| 202 | case 6: | ||
| 203 | hat = SDL_HAT_LEFT; | ||
| 204 | break; | ||
| 205 | case 7: | ||
| 206 | hat = SDL_HAT_LEFTUP; | ||
| 207 | break; | ||
| 208 | default: | ||
| 209 | hat = SDL_HAT_CENTERED; | ||
| 210 | break; | ||
| 211 | } | ||
| 212 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 213 | } | ||
| 214 | |||
| 215 | if (ctx->last_state[2] != data[2]) { | ||
| 216 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x40) != 0)); | ||
| 217 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x10) != 0)); | ||
| 218 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x20) != 0)); | ||
| 219 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0)); | ||
| 220 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_CAPTURE, ((data[2] & 0x01) != 0)); | ||
| 221 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT, ((data[2] & 0x02) != 0)); | ||
| 222 | } | ||
| 223 | |||
| 224 | if (ctx->last_state[3] != data[3]) { | ||
| 225 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x40) != 0)); | ||
| 226 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0)); | ||
| 227 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x10) != 0)); | ||
| 228 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0)); | ||
| 229 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x04) != 0)); | ||
| 230 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0)); | ||
| 231 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x01) != 0)); | ||
| 232 | } | ||
| 233 | |||
| 234 | #define READ_STICK_AXIS(offset) \ | ||
| 235 | (data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), 0x01 - 0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16)) | ||
| 236 | { | ||
| 237 | axis = READ_STICK_AXIS(4); | ||
| 238 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 239 | axis = READ_STICK_AXIS(5); | ||
| 240 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 241 | axis = READ_STICK_AXIS(6); | ||
| 242 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 243 | axis = READ_STICK_AXIS(7); | ||
| 244 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 245 | } | ||
| 246 | #undef READ_STICK_AXIS | ||
| 247 | |||
| 248 | #define READ_TRIGGER_AXIS(offset) \ | ||
| 249 | (Sint16)(((int)data[offset] * 257) - 32768) | ||
| 250 | { | ||
| 251 | axis = READ_TRIGGER_AXIS(8); | ||
| 252 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 253 | axis = READ_TRIGGER_AXIS(9); | ||
| 254 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 255 | } | ||
| 256 | #undef READ_TRIGGER_AXIS | ||
| 257 | |||
| 258 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 259 | } | ||
| 260 | |||
| 261 | static bool HIDAPI_DriverStadia_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 262 | { | ||
| 263 | SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context; | ||
| 264 | SDL_Joystick *joystick = NULL; | ||
| 265 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 266 | int size = 0; | ||
| 267 | |||
| 268 | if (device->num_joysticks > 0) { | ||
| 269 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 270 | } else { | ||
| 271 | return false; | ||
| 272 | } | ||
| 273 | |||
| 274 | while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { | ||
| 275 | #ifdef DEBUG_STADIA_PROTOCOL | ||
| 276 | HIDAPI_DumpPacket("Google Stadia packet: size = %d", data, size); | ||
| 277 | #endif | ||
| 278 | if (!joystick) { | ||
| 279 | continue; | ||
| 280 | } | ||
| 281 | |||
| 282 | HIDAPI_DriverStadia_HandleStatePacket(joystick, ctx, data, size); | ||
| 283 | } | ||
| 284 | |||
| 285 | if (size < 0) { | ||
| 286 | // Read error, device is disconnected | ||
| 287 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 288 | } | ||
| 289 | return (size >= 0); | ||
| 290 | } | ||
| 291 | |||
| 292 | static void HIDAPI_DriverStadia_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 293 | { | ||
| 294 | } | ||
| 295 | |||
| 296 | static void HIDAPI_DriverStadia_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 297 | { | ||
| 298 | } | ||
| 299 | |||
| 300 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia = { | ||
| 301 | SDL_HINT_JOYSTICK_HIDAPI_STADIA, | ||
| 302 | true, | ||
| 303 | HIDAPI_DriverStadia_RegisterHints, | ||
| 304 | HIDAPI_DriverStadia_UnregisterHints, | ||
| 305 | HIDAPI_DriverStadia_IsEnabled, | ||
| 306 | HIDAPI_DriverStadia_IsSupportedDevice, | ||
| 307 | HIDAPI_DriverStadia_InitDevice, | ||
| 308 | HIDAPI_DriverStadia_GetDevicePlayerIndex, | ||
| 309 | HIDAPI_DriverStadia_SetDevicePlayerIndex, | ||
| 310 | HIDAPI_DriverStadia_UpdateDevice, | ||
| 311 | HIDAPI_DriverStadia_OpenJoystick, | ||
| 312 | HIDAPI_DriverStadia_RumbleJoystick, | ||
| 313 | HIDAPI_DriverStadia_RumbleJoystickTriggers, | ||
| 314 | HIDAPI_DriverStadia_GetJoystickCapabilities, | ||
| 315 | HIDAPI_DriverStadia_SetJoystickLED, | ||
| 316 | HIDAPI_DriverStadia_SendJoystickEffect, | ||
| 317 | HIDAPI_DriverStadia_SetJoystickSensorsEnabled, | ||
| 318 | HIDAPI_DriverStadia_CloseJoystick, | ||
| 319 | HIDAPI_DriverStadia_FreeDevice, | ||
| 320 | }; | ||
| 321 | |||
| 322 | #endif // SDL_JOYSTICK_HIDAPI_STADIA | ||
| 323 | |||
| 324 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../../SDL_hints_c.h" | ||
| 26 | #include "../SDL_sysjoystick.h" | ||
| 27 | #include "SDL_hidapijoystick_c.h" | ||
| 28 | |||
| 29 | #ifdef SDL_JOYSTICK_HIDAPI_STEAM | ||
| 30 | |||
| 31 | // Define this if you want to log all packets from the controller | ||
| 32 | // #define DEBUG_STEAM_PROTOCOL | ||
| 33 | |||
| 34 | #define SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED "SDL_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED" | ||
| 35 | |||
| 36 | #if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) | ||
| 37 | // This requires prompting for Bluetooth permissions, so make sure the application really wants it | ||
| 38 | #define SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT false | ||
| 39 | #else | ||
| 40 | #define SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT) | ||
| 41 | #endif | ||
| 42 | |||
| 43 | #define PAIRING_STATE_DURATION_SECONDS 60 | ||
| 44 | |||
| 45 | |||
| 46 | /*****************************************************************************************************/ | ||
| 47 | |||
| 48 | #include "steam/controller_constants.h" | ||
| 49 | #include "steam/controller_structs.h" | ||
| 50 | |||
| 51 | enum | ||
| 52 | { | ||
| 53 | SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE = 11, | ||
| 54 | SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE, | ||
| 55 | SDL_GAMEPAD_NUM_STEAM_BUTTONS, | ||
| 56 | }; | ||
| 57 | |||
| 58 | typedef struct SteamControllerStateInternal_t | ||
| 59 | { | ||
| 60 | // Controller Type for this Controller State | ||
| 61 | Uint32 eControllerType; | ||
| 62 | |||
| 63 | // If packet num matches that on your prior call, then the controller state hasn't been changed since | ||
| 64 | // your last call and there is no need to process it | ||
| 65 | Uint32 unPacketNum; | ||
| 66 | |||
| 67 | // bit flags for each of the buttons | ||
| 68 | Uint64 ulButtons; | ||
| 69 | |||
| 70 | // Left pad coordinates | ||
| 71 | short sLeftPadX; | ||
| 72 | short sLeftPadY; | ||
| 73 | |||
| 74 | // Right pad coordinates | ||
| 75 | short sRightPadX; | ||
| 76 | short sRightPadY; | ||
| 77 | |||
| 78 | // Center pad coordinates | ||
| 79 | short sCenterPadX; | ||
| 80 | short sCenterPadY; | ||
| 81 | |||
| 82 | // Left analog stick coordinates | ||
| 83 | short sLeftStickX; | ||
| 84 | short sLeftStickY; | ||
| 85 | |||
| 86 | // Right analog stick coordinates | ||
| 87 | short sRightStickX; | ||
| 88 | short sRightStickY; | ||
| 89 | |||
| 90 | unsigned short sTriggerL; | ||
| 91 | unsigned short sTriggerR; | ||
| 92 | |||
| 93 | short sAccelX; | ||
| 94 | short sAccelY; | ||
| 95 | short sAccelZ; | ||
| 96 | |||
| 97 | short sGyroX; | ||
| 98 | short sGyroY; | ||
| 99 | short sGyroZ; | ||
| 100 | |||
| 101 | float sGyroQuatW; | ||
| 102 | float sGyroQuatX; | ||
| 103 | float sGyroQuatY; | ||
| 104 | float sGyroQuatZ; | ||
| 105 | |||
| 106 | short sGyroSteeringAngle; | ||
| 107 | |||
| 108 | unsigned short sBatteryLevel; | ||
| 109 | |||
| 110 | // Pressure sensor data. | ||
| 111 | unsigned short sPressurePadLeft; | ||
| 112 | unsigned short sPressurePadRight; | ||
| 113 | |||
| 114 | unsigned short sPressureBumperLeft; | ||
| 115 | unsigned short sPressureBumperRight; | ||
| 116 | |||
| 117 | // Internal state data | ||
| 118 | short sPrevLeftPad[2]; | ||
| 119 | short sPrevLeftStick[2]; | ||
| 120 | } SteamControllerStateInternal_t; | ||
| 121 | |||
| 122 | // Defines for ulButtons in SteamControllerStateInternal_t | ||
| 123 | #define STEAM_RIGHT_TRIGGER_MASK 0x00000001 | ||
| 124 | #define STEAM_LEFT_TRIGGER_MASK 0x00000002 | ||
| 125 | #define STEAM_RIGHT_BUMPER_MASK 0x00000004 | ||
| 126 | #define STEAM_LEFT_BUMPER_MASK 0x00000008 | ||
| 127 | #define STEAM_BUTTON_NORTH_MASK 0x00000010 // Y | ||
| 128 | #define STEAM_BUTTON_EAST_MASK 0x00000020 // B | ||
| 129 | #define STEAM_BUTTON_WEST_MASK 0x00000040 // X | ||
| 130 | #define STEAM_BUTTON_SOUTH_MASK 0x00000080 // A | ||
| 131 | #define STEAM_DPAD_UP_MASK 0x00000100 // DPAD UP | ||
| 132 | #define STEAM_DPAD_RIGHT_MASK 0x00000200 // DPAD RIGHT | ||
| 133 | #define STEAM_DPAD_LEFT_MASK 0x00000400 // DPAD LEFT | ||
| 134 | #define STEAM_DPAD_DOWN_MASK 0x00000800 // DPAD DOWN | ||
| 135 | #define STEAM_BUTTON_MENU_MASK 0x00001000 // SELECT | ||
| 136 | #define STEAM_BUTTON_STEAM_MASK 0x00002000 // GUIDE | ||
| 137 | #define STEAM_BUTTON_ESCAPE_MASK 0x00004000 // START | ||
| 138 | #define STEAM_BUTTON_BACK_LEFT_MASK 0x00008000 | ||
| 139 | #define STEAM_BUTTON_BACK_RIGHT_MASK 0x00010000 | ||
| 140 | #define STEAM_BUTTON_LEFTPAD_CLICKED_MASK 0x00020000 | ||
| 141 | #define STEAM_BUTTON_RIGHTPAD_CLICKED_MASK 0x00040000 | ||
| 142 | #define STEAM_LEFTPAD_FINGERDOWN_MASK 0x00080000 | ||
| 143 | #define STEAM_RIGHTPAD_FINGERDOWN_MASK 0x00100000 | ||
| 144 | #define STEAM_JOYSTICK_BUTTON_MASK 0x00400000 | ||
| 145 | #define STEAM_LEFTPAD_AND_JOYSTICK_MASK 0x00800000 | ||
| 146 | |||
| 147 | // Look for report version 0x0001, type WIRELESS (3), length >= 1 byte | ||
| 148 | #define D0G_IS_VALID_WIRELESS_EVENT(data, len) ((len) >= 5 && (data)[0] == 1 && (data)[1] == 0 && (data)[2] == 3 && (data)[3] >= 1) | ||
| 149 | #define D0G_GET_WIRELESS_EVENT_TYPE(data) ((data)[4]) | ||
| 150 | #define D0G_WIRELESS_DISCONNECTED 1 | ||
| 151 | #define D0G_WIRELESS_ESTABLISHED 2 | ||
| 152 | #define D0G_WIRELESS_NEWLYPAIRED 3 | ||
| 153 | |||
| 154 | #define D0G_IS_WIRELESS_DISCONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) == D0G_WIRELESS_DISCONNECTED) | ||
| 155 | #define D0G_IS_WIRELESS_CONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) != D0G_WIRELESS_DISCONNECTED) | ||
| 156 | |||
| 157 | |||
| 158 | #define MAX_REPORT_SEGMENT_PAYLOAD_SIZE 18 | ||
| 159 | /* | ||
| 160 | * SteamControllerPacketAssembler has to be used when reading output repots from controllers. | ||
| 161 | */ | ||
| 162 | typedef struct | ||
| 163 | { | ||
| 164 | uint8_t uBuffer[MAX_REPORT_SEGMENT_PAYLOAD_SIZE * 8 + 1]; | ||
| 165 | int nExpectedSegmentNumber; | ||
| 166 | bool bIsBle; | ||
| 167 | } SteamControllerPacketAssembler; | ||
| 168 | |||
| 169 | #undef clamp | ||
| 170 | #define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) | ||
| 171 | |||
| 172 | #undef offsetof | ||
| 173 | #define offsetof(s, m) (size_t) & (((s *)0)->m) | ||
| 174 | |||
| 175 | #ifdef DEBUG_STEAM_CONTROLLER | ||
| 176 | #define DPRINTF(format, ...) printf(format, ##__VA_ARGS__) | ||
| 177 | #define HEXDUMP(ptr, len) hexdump(ptr, len) | ||
| 178 | #else | ||
| 179 | #define DPRINTF(format, ...) | ||
| 180 | #define HEXDUMP(ptr, len) | ||
| 181 | #endif | ||
| 182 | #define printf SDL_Log | ||
| 183 | |||
| 184 | #define MAX_REPORT_SEGMENT_SIZE (MAX_REPORT_SEGMENT_PAYLOAD_SIZE + 2) | ||
| 185 | #define CALC_REPORT_SEGMENT_NUM(index) ((index / MAX_REPORT_SEGMENT_PAYLOAD_SIZE) & 0x07) | ||
| 186 | #define REPORT_SEGMENT_DATA_FLAG 0x80 | ||
| 187 | #define REPORT_SEGMENT_LAST_FLAG 0x40 | ||
| 188 | #define BLE_REPORT_NUMBER 0x03 | ||
| 189 | |||
| 190 | #define STEAMCONTROLLER_TRIGGER_MAX_ANALOG 26000 | ||
| 191 | |||
| 192 | // Enable mouse mode when using the Steam Controller locally | ||
| 193 | #undef ENABLE_MOUSE_MODE | ||
| 194 | |||
| 195 | // Wireless firmware quirk: the firmware intentionally signals "failure" when performing | ||
| 196 | // SET_FEATURE / GET_FEATURE when it actually means "pending radio roundtrip". The only | ||
| 197 | // way to make SET_FEATURE / GET_FEATURE work is to loop several times with a sleep. If | ||
| 198 | // it takes more than 50ms to get the response for SET_FEATURE / GET_FEATURE, we assume | ||
| 199 | // that the controller has failed. | ||
| 200 | #define RADIO_WORKAROUND_SLEEP_ATTEMPTS 50 | ||
| 201 | #define RADIO_WORKAROUND_SLEEP_DURATION_US 500 | ||
| 202 | |||
| 203 | // This was defined by experimentation. 2000 seemed to work but to give that extra bit of margin, set to 3ms. | ||
| 204 | #define CONTROLLER_CONFIGURATION_DELAY_US 3000 | ||
| 205 | |||
| 206 | static uint8_t GetSegmentHeader(int nSegmentNumber, bool bLastPacket) | ||
| 207 | { | ||
| 208 | uint8_t header = REPORT_SEGMENT_DATA_FLAG; | ||
| 209 | header |= nSegmentNumber; | ||
| 210 | if (bLastPacket) { | ||
| 211 | header |= REPORT_SEGMENT_LAST_FLAG; | ||
| 212 | } | ||
| 213 | |||
| 214 | return header; | ||
| 215 | } | ||
| 216 | |||
| 217 | static void hexdump(const uint8_t *ptr, int len) | ||
| 218 | { | ||
| 219 | HIDAPI_DumpPacket("Data", ptr, len); | ||
| 220 | } | ||
| 221 | |||
| 222 | static void ResetSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler) | ||
| 223 | { | ||
| 224 | SDL_memset(pAssembler->uBuffer, 0, sizeof(pAssembler->uBuffer)); | ||
| 225 | pAssembler->nExpectedSegmentNumber = 0; | ||
| 226 | } | ||
| 227 | |||
| 228 | static void InitializeSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler, bool bIsBle) | ||
| 229 | { | ||
| 230 | pAssembler->bIsBle = bIsBle; | ||
| 231 | ResetSteamControllerPacketAssembler(pAssembler); | ||
| 232 | } | ||
| 233 | |||
| 234 | // Returns: | ||
| 235 | // <0 on error | ||
| 236 | // 0 on not ready | ||
| 237 | // Complete packet size on completion | ||
| 238 | static int WriteSegmentToSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler, const uint8_t *pSegment, int nSegmentLength) | ||
| 239 | { | ||
| 240 | if (pAssembler->bIsBle) { | ||
| 241 | uint8_t uSegmentHeader = pSegment[1]; | ||
| 242 | int nSegmentNumber = uSegmentHeader & 0x07; | ||
| 243 | |||
| 244 | HEXDUMP(pSegment, nSegmentLength); | ||
| 245 | |||
| 246 | if (pSegment[0] != BLE_REPORT_NUMBER) { | ||
| 247 | // We may get keyboard/mouse input events until controller stops sending them | ||
| 248 | return 0; | ||
| 249 | } | ||
| 250 | |||
| 251 | if (nSegmentLength != MAX_REPORT_SEGMENT_SIZE) { | ||
| 252 | printf("Bad segment size! %d\n", nSegmentLength); | ||
| 253 | hexdump(pSegment, nSegmentLength); | ||
| 254 | ResetSteamControllerPacketAssembler(pAssembler); | ||
| 255 | return -1; | ||
| 256 | } | ||
| 257 | |||
| 258 | DPRINTF("GOT PACKET HEADER = 0x%x\n", uSegmentHeader); | ||
| 259 | |||
| 260 | if (!(uSegmentHeader & REPORT_SEGMENT_DATA_FLAG)) { | ||
| 261 | // We get empty segments, just ignore them | ||
| 262 | return 0; | ||
| 263 | } | ||
| 264 | |||
| 265 | if (nSegmentNumber != pAssembler->nExpectedSegmentNumber) { | ||
| 266 | ResetSteamControllerPacketAssembler(pAssembler); | ||
| 267 | |||
| 268 | if (nSegmentNumber) { | ||
| 269 | // This happens occasionally | ||
| 270 | DPRINTF("Bad segment number, got %d, expected %d\n", | ||
| 271 | nSegmentNumber, pAssembler->nExpectedSegmentNumber); | ||
| 272 | return -1; | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | SDL_memcpy(pAssembler->uBuffer + nSegmentNumber * MAX_REPORT_SEGMENT_PAYLOAD_SIZE, | ||
| 277 | pSegment + 2, // ignore header and report number | ||
| 278 | MAX_REPORT_SEGMENT_PAYLOAD_SIZE); | ||
| 279 | |||
| 280 | if (uSegmentHeader & REPORT_SEGMENT_LAST_FLAG) { | ||
| 281 | pAssembler->nExpectedSegmentNumber = 0; | ||
| 282 | return (nSegmentNumber + 1) * MAX_REPORT_SEGMENT_PAYLOAD_SIZE; | ||
| 283 | } | ||
| 284 | |||
| 285 | pAssembler->nExpectedSegmentNumber++; | ||
| 286 | } else { | ||
| 287 | // Just pass through | ||
| 288 | SDL_memcpy(pAssembler->uBuffer, | ||
| 289 | pSegment, | ||
| 290 | nSegmentLength); | ||
| 291 | return nSegmentLength; | ||
| 292 | } | ||
| 293 | |||
| 294 | return 0; | ||
| 295 | } | ||
| 296 | |||
| 297 | #define BLE_MAX_READ_RETRIES 8 | ||
| 298 | |||
| 299 | static int SetFeatureReport(SDL_HIDAPI_Device *dev, const unsigned char uBuffer[65], int nActualDataLen) | ||
| 300 | { | ||
| 301 | int nRet = -1; | ||
| 302 | |||
| 303 | DPRINTF("SetFeatureReport %p %p %d\n", dev, uBuffer, nActualDataLen); | ||
| 304 | |||
| 305 | if (dev->is_bluetooth) { | ||
| 306 | int nSegmentNumber = 0; | ||
| 307 | uint8_t uPacketBuffer[MAX_REPORT_SEGMENT_SIZE]; | ||
| 308 | const unsigned char *pBufferPtr = uBuffer + 1; | ||
| 309 | |||
| 310 | if (nActualDataLen < 1) { | ||
| 311 | return -1; | ||
| 312 | } | ||
| 313 | |||
| 314 | // Skip report number in data | ||
| 315 | nActualDataLen--; | ||
| 316 | |||
| 317 | while (nActualDataLen > 0) { | ||
| 318 | int nBytesInPacket = nActualDataLen > MAX_REPORT_SEGMENT_PAYLOAD_SIZE ? MAX_REPORT_SEGMENT_PAYLOAD_SIZE : nActualDataLen; | ||
| 319 | |||
| 320 | nActualDataLen -= nBytesInPacket; | ||
| 321 | |||
| 322 | // Construct packet | ||
| 323 | SDL_memset(uPacketBuffer, 0, sizeof(uPacketBuffer)); | ||
| 324 | uPacketBuffer[0] = BLE_REPORT_NUMBER; | ||
| 325 | uPacketBuffer[1] = GetSegmentHeader(nSegmentNumber, nActualDataLen == 0); | ||
| 326 | SDL_memcpy(&uPacketBuffer[2], pBufferPtr, nBytesInPacket); | ||
| 327 | |||
| 328 | pBufferPtr += nBytesInPacket; | ||
| 329 | nSegmentNumber++; | ||
| 330 | |||
| 331 | nRet = SDL_hid_send_feature_report(dev->dev, uPacketBuffer, sizeof(uPacketBuffer)); | ||
| 332 | } | ||
| 333 | } else { | ||
| 334 | for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) { | ||
| 335 | nRet = SDL_hid_send_feature_report(dev->dev, uBuffer, 65); | ||
| 336 | if (nRet >= 0) { | ||
| 337 | break; | ||
| 338 | } | ||
| 339 | |||
| 340 | SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000); | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | DPRINTF("SetFeatureReport() ret = %d\n", nRet); | ||
| 345 | |||
| 346 | return nRet; | ||
| 347 | } | ||
| 348 | |||
| 349 | static int GetFeatureReport(SDL_HIDAPI_Device *dev, unsigned char uBuffer[65]) | ||
| 350 | { | ||
| 351 | int nRet = -1; | ||
| 352 | |||
| 353 | DPRINTF("GetFeatureReport( %p %p )\n", dev, uBuffer); | ||
| 354 | |||
| 355 | if (dev->is_bluetooth) { | ||
| 356 | int nRetries = 0; | ||
| 357 | uint8_t uSegmentBuffer[MAX_REPORT_SEGMENT_SIZE + 1]; | ||
| 358 | uint8_t ucBytesToRead = MAX_REPORT_SEGMENT_SIZE; | ||
| 359 | uint8_t ucDataStartOffset = 0; | ||
| 360 | |||
| 361 | SteamControllerPacketAssembler assembler; | ||
| 362 | InitializeSteamControllerPacketAssembler(&assembler, dev->is_bluetooth); | ||
| 363 | |||
| 364 | // On Windows and macOS, BLE devices get 2 copies of the feature report ID, one that is removed by ReadFeatureReport, | ||
| 365 | // and one that's included in the buffer we receive. We pad the bytes to read and skip over the report ID | ||
| 366 | // if necessary. | ||
| 367 | #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_MACOS) | ||
| 368 | ++ucBytesToRead; | ||
| 369 | ++ucDataStartOffset; | ||
| 370 | #endif | ||
| 371 | |||
| 372 | while (nRetries < BLE_MAX_READ_RETRIES) { | ||
| 373 | SDL_memset(uSegmentBuffer, 0, sizeof(uSegmentBuffer)); | ||
| 374 | uSegmentBuffer[0] = BLE_REPORT_NUMBER; | ||
| 375 | nRet = SDL_hid_get_feature_report(dev->dev, uSegmentBuffer, ucBytesToRead); | ||
| 376 | |||
| 377 | DPRINTF("GetFeatureReport ble ret=%d\n", nRet); | ||
| 378 | HEXDUMP(uSegmentBuffer, nRet); | ||
| 379 | |||
| 380 | // Zero retry counter if we got data | ||
| 381 | if (nRet > 2 && (uSegmentBuffer[ucDataStartOffset + 1] & REPORT_SEGMENT_DATA_FLAG)) { | ||
| 382 | nRetries = 0; | ||
| 383 | } else { | ||
| 384 | nRetries++; | ||
| 385 | } | ||
| 386 | |||
| 387 | if (nRet > 0) { | ||
| 388 | int nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&assembler, | ||
| 389 | uSegmentBuffer + ucDataStartOffset, | ||
| 390 | nRet - ucDataStartOffset); | ||
| 391 | |||
| 392 | if (nPacketLength > 0 && nPacketLength < 65) { | ||
| 393 | // Leave space for "report number" | ||
| 394 | uBuffer[0] = 0; | ||
| 395 | SDL_memcpy(uBuffer + 1, assembler.uBuffer, nPacketLength); | ||
| 396 | return nPacketLength; | ||
| 397 | } | ||
| 398 | } | ||
| 399 | } | ||
| 400 | printf("Could not get a full ble packet after %d retries\n", nRetries); | ||
| 401 | return -1; | ||
| 402 | } else { | ||
| 403 | SDL_memset(uBuffer, 0, 65); | ||
| 404 | |||
| 405 | for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) { | ||
| 406 | nRet = SDL_hid_get_feature_report(dev->dev, uBuffer, 65); | ||
| 407 | if (nRet >= 0) { | ||
| 408 | break; | ||
| 409 | } | ||
| 410 | |||
| 411 | SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000); | ||
| 412 | } | ||
| 413 | |||
| 414 | DPRINTF("GetFeatureReport USB ret=%d\n", nRet); | ||
| 415 | HEXDUMP(uBuffer, nRet); | ||
| 416 | } | ||
| 417 | |||
| 418 | return nRet; | ||
| 419 | } | ||
| 420 | |||
| 421 | static int ReadResponse(SDL_HIDAPI_Device *dev, uint8_t uBuffer[65], int nExpectedResponse) | ||
| 422 | { | ||
| 423 | for (int nRetries = 0; nRetries < 10; nRetries++) { | ||
| 424 | int nRet = GetFeatureReport(dev, uBuffer); | ||
| 425 | |||
| 426 | DPRINTF("ReadResponse( %p %p 0x%x )\n", dev, uBuffer, nExpectedResponse); | ||
| 427 | |||
| 428 | if (nRet < 0) { | ||
| 429 | continue; | ||
| 430 | } | ||
| 431 | |||
| 432 | DPRINTF("ReadResponse got %d bytes of data: ", nRet); | ||
| 433 | HEXDUMP(uBuffer, nRet); | ||
| 434 | |||
| 435 | if (uBuffer[1] != nExpectedResponse) { | ||
| 436 | continue; | ||
| 437 | } | ||
| 438 | |||
| 439 | return nRet; | ||
| 440 | } | ||
| 441 | return -1; | ||
| 442 | } | ||
| 443 | |||
| 444 | //--------------------------------------------------------------------------- | ||
| 445 | // Reset steam controller (unmap buttons and pads) and re-fetch capability bits | ||
| 446 | //--------------------------------------------------------------------------- | ||
| 447 | static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew, uint32_t *punUpdateRateUS) | ||
| 448 | { | ||
| 449 | // Firmware quirk: Set Feature and Get Feature requests always require a 65-byte buffer. | ||
| 450 | unsigned char buf[65]; | ||
| 451 | unsigned int i; | ||
| 452 | int res = -1; | ||
| 453 | int nSettings = 0; | ||
| 454 | int nAttributesLength; | ||
| 455 | FeatureReportMsg *msg; | ||
| 456 | uint32_t unUpdateRateUS = 9000; // Good default rate | ||
| 457 | |||
| 458 | DPRINTF("ResetSteamController hid=%p\n", dev); | ||
| 459 | |||
| 460 | buf[0] = 0; | ||
| 461 | buf[1] = ID_GET_ATTRIBUTES_VALUES; | ||
| 462 | res = SetFeatureReport(dev, buf, 2); | ||
| 463 | if (res < 0) { | ||
| 464 | if (!bSuppressErrorSpew) { | ||
| 465 | printf("GET_ATTRIBUTES_VALUES failed for controller %p\n", dev); | ||
| 466 | } | ||
| 467 | return false; | ||
| 468 | } | ||
| 469 | |||
| 470 | // Retrieve GET_ATTRIBUTES_VALUES result | ||
| 471 | // Wireless controller endpoints without a connected controller will return nAttrs == 0 | ||
| 472 | res = ReadResponse(dev, buf, ID_GET_ATTRIBUTES_VALUES); | ||
| 473 | if (res < 0 || buf[1] != ID_GET_ATTRIBUTES_VALUES) { | ||
| 474 | HEXDUMP(buf, res); | ||
| 475 | if (!bSuppressErrorSpew) { | ||
| 476 | printf("Bad GET_ATTRIBUTES_VALUES response for controller %p\n", dev); | ||
| 477 | } | ||
| 478 | return false; | ||
| 479 | } | ||
| 480 | |||
| 481 | nAttributesLength = buf[2]; | ||
| 482 | if (nAttributesLength > res) { | ||
| 483 | if (!bSuppressErrorSpew) { | ||
| 484 | printf("Bad GET_ATTRIBUTES_VALUES response for controller %p\n", dev); | ||
| 485 | } | ||
| 486 | return false; | ||
| 487 | } | ||
| 488 | |||
| 489 | msg = (FeatureReportMsg *)&buf[1]; | ||
| 490 | for (i = 0; i < (int)msg->header.length / sizeof(ControllerAttribute); ++i) { | ||
| 491 | uint8_t unAttribute = msg->payload.getAttributes.attributes[i].attributeTag; | ||
| 492 | uint32_t unValue = msg->payload.getAttributes.attributes[i].attributeValue; | ||
| 493 | |||
| 494 | switch (unAttribute) { | ||
| 495 | case ATTRIB_UNIQUE_ID: | ||
| 496 | break; | ||
| 497 | case ATTRIB_PRODUCT_ID: | ||
| 498 | break; | ||
| 499 | case ATTRIB_CAPABILITIES: | ||
| 500 | break; | ||
| 501 | case ATTRIB_CONNECTION_INTERVAL_IN_US: | ||
| 502 | unUpdateRateUS = unValue; | ||
| 503 | break; | ||
| 504 | default: | ||
| 505 | break; | ||
| 506 | } | ||
| 507 | } | ||
| 508 | if (punUpdateRateUS) { | ||
| 509 | *punUpdateRateUS = unUpdateRateUS; | ||
| 510 | } | ||
| 511 | |||
| 512 | // Clear digital button mappings | ||
| 513 | buf[0] = 0; | ||
| 514 | buf[1] = ID_CLEAR_DIGITAL_MAPPINGS; | ||
| 515 | res = SetFeatureReport(dev, buf, 2); | ||
| 516 | if (res < 0) { | ||
| 517 | if (!bSuppressErrorSpew) { | ||
| 518 | printf("CLEAR_DIGITAL_MAPPINGS failed for controller %p\n", dev); | ||
| 519 | } | ||
| 520 | return false; | ||
| 521 | } | ||
| 522 | |||
| 523 | // Reset the default settings | ||
| 524 | SDL_memset(buf, 0, 65); | ||
| 525 | buf[1] = ID_LOAD_DEFAULT_SETTINGS; | ||
| 526 | buf[2] = 0; | ||
| 527 | res = SetFeatureReport(dev, buf, 3); | ||
| 528 | if (res < 0) { | ||
| 529 | if (!bSuppressErrorSpew) { | ||
| 530 | printf("LOAD_DEFAULT_SETTINGS failed for controller %p\n", dev); | ||
| 531 | } | ||
| 532 | return false; | ||
| 533 | } | ||
| 534 | |||
| 535 | // Apply custom settings - clear trackpad modes (cancel mouse emulation), etc | ||
| 536 | #define ADD_SETTING(SETTING, VALUE) \ | ||
| 537 | buf[3 + nSettings * 3] = SETTING; \ | ||
| 538 | buf[3 + nSettings * 3 + 1] = ((uint16_t)VALUE) & 0xFF; \ | ||
| 539 | buf[3 + nSettings * 3 + 2] = ((uint16_t)VALUE) >> 8; \ | ||
| 540 | ++nSettings; | ||
| 541 | |||
| 542 | SDL_memset(buf, 0, 65); | ||
| 543 | buf[1] = ID_SET_SETTINGS_VALUES; | ||
| 544 | ADD_SETTING(SETTING_WIRELESS_PACKET_VERSION, 2); | ||
| 545 | ADD_SETTING(SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE); | ||
| 546 | #ifdef ENABLE_MOUSE_MODE | ||
| 547 | ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE); | ||
| 548 | ADD_SETTING(SETTING_SMOOTH_ABSOLUTE_MOUSE, 1); | ||
| 549 | ADD_SETTING(SETTING_MOMENTUM_MAXIMUM_VELOCITY, 20000); // [0-20000] default 8000 | ||
| 550 | ADD_SETTING(SETTING_MOMENTUM_DECAY_AMOUNT, 50); // [0-50] default 5 | ||
| 551 | #else | ||
| 552 | ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE); | ||
| 553 | ADD_SETTING(SETTING_SMOOTH_ABSOLUTE_MOUSE, 0); | ||
| 554 | #endif | ||
| 555 | buf[2] = (unsigned char)(nSettings * 3); | ||
| 556 | |||
| 557 | res = SetFeatureReport(dev, buf, 3 + nSettings * 3); | ||
| 558 | if (res < 0) { | ||
| 559 | if (!bSuppressErrorSpew) { | ||
| 560 | printf("SET_SETTINGS failed for controller %p\n", dev); | ||
| 561 | } | ||
| 562 | return false; | ||
| 563 | } | ||
| 564 | |||
| 565 | #ifdef ENABLE_MOUSE_MODE | ||
| 566 | // Wait for ID_CLEAR_DIGITAL_MAPPINGS to be processed on the controller | ||
| 567 | bool bMappingsCleared = false; | ||
| 568 | int iRetry; | ||
| 569 | for (iRetry = 0; iRetry < 2; ++iRetry) { | ||
| 570 | SDL_memset(buf, 0, 65); | ||
| 571 | buf[1] = ID_GET_DIGITAL_MAPPINGS; | ||
| 572 | buf[2] = 1; // one byte - requesting from index 0 | ||
| 573 | buf[3] = 0; | ||
| 574 | res = SetFeatureReport(dev, buf, 4); | ||
| 575 | if (res < 0) { | ||
| 576 | printf("GET_DIGITAL_MAPPINGS failed for controller %p\n", dev); | ||
| 577 | return false; | ||
| 578 | } | ||
| 579 | |||
| 580 | res = ReadResponse(dev, buf, ID_GET_DIGITAL_MAPPINGS); | ||
| 581 | if (res < 0 || buf[1] != ID_GET_DIGITAL_MAPPINGS) { | ||
| 582 | printf("Bad GET_DIGITAL_MAPPINGS response for controller %p\n", dev); | ||
| 583 | return false; | ||
| 584 | } | ||
| 585 | |||
| 586 | // If the length of the digital mappings result is not 1 (index byte, no mappings) then clearing hasn't executed | ||
| 587 | if (buf[2] == 1 && buf[3] == 0xFF) { | ||
| 588 | bMappingsCleared = true; | ||
| 589 | break; | ||
| 590 | } | ||
| 591 | usleep(CONTROLLER_CONFIGURATION_DELAY_US); | ||
| 592 | } | ||
| 593 | |||
| 594 | if (!bMappingsCleared && !bSuppressErrorSpew) { | ||
| 595 | printf("Warning: CLEAR_DIGITAL_MAPPINGS never completed for controller %p\n", dev); | ||
| 596 | } | ||
| 597 | |||
| 598 | // Set our new mappings | ||
| 599 | SDL_memset(buf, 0, 65); | ||
| 600 | buf[1] = ID_SET_DIGITAL_MAPPINGS; | ||
| 601 | buf[2] = 6; // 2 settings x 3 bytes | ||
| 602 | buf[3] = IO_DIGITAL_BUTTON_RIGHT_TRIGGER; | ||
| 603 | buf[4] = DEVICE_MOUSE; | ||
| 604 | buf[5] = MOUSE_BTN_LEFT; | ||
| 605 | buf[6] = IO_DIGITAL_BUTTON_LEFT_TRIGGER; | ||
| 606 | buf[7] = DEVICE_MOUSE; | ||
| 607 | buf[8] = MOUSE_BTN_RIGHT; | ||
| 608 | |||
| 609 | res = SetFeatureReport(dev, buf, 9); | ||
| 610 | if (res < 0) { | ||
| 611 | if (!bSuppressErrorSpew) { | ||
| 612 | printf("SET_DIGITAL_MAPPINGS failed for controller %p\n", dev); | ||
| 613 | } | ||
| 614 | return false; | ||
| 615 | } | ||
| 616 | #endif // ENABLE_MOUSE_MODE | ||
| 617 | |||
| 618 | return true; | ||
| 619 | } | ||
| 620 | |||
| 621 | //--------------------------------------------------------------------------- | ||
| 622 | // Read from a Steam Controller | ||
| 623 | //--------------------------------------------------------------------------- | ||
| 624 | static int ReadSteamController(SDL_hid_device *dev, uint8_t *pData, int nDataSize) | ||
| 625 | { | ||
| 626 | SDL_memset(pData, 0, nDataSize); | ||
| 627 | pData[0] = BLE_REPORT_NUMBER; // hid_read will also overwrite this with the same value, 0x03 | ||
| 628 | return SDL_hid_read(dev, pData, nDataSize); | ||
| 629 | } | ||
| 630 | |||
| 631 | //--------------------------------------------------------------------------- | ||
| 632 | // Set Steam Controller pairing state | ||
| 633 | //--------------------------------------------------------------------------- | ||
| 634 | static void SetPairingState(SDL_HIDAPI_Device *dev, bool bEnablePairing) | ||
| 635 | { | ||
| 636 | unsigned char buf[65]; | ||
| 637 | SDL_memset(buf, 0, 65); | ||
| 638 | buf[1] = ID_ENABLE_PAIRING; | ||
| 639 | buf[2] = 2; // 2 payload bytes: bool + timeout | ||
| 640 | buf[3] = bEnablePairing ? 1 : 0; | ||
| 641 | buf[4] = bEnablePairing ? PAIRING_STATE_DURATION_SECONDS : 0; | ||
| 642 | SetFeatureReport(dev, buf, 5); | ||
| 643 | } | ||
| 644 | |||
| 645 | //--------------------------------------------------------------------------- | ||
| 646 | // Commit Steam Controller pairing | ||
| 647 | //--------------------------------------------------------------------------- | ||
| 648 | static void CommitPairing(SDL_HIDAPI_Device *dev) | ||
| 649 | { | ||
| 650 | unsigned char buf[65]; | ||
| 651 | SDL_memset(buf, 0, 65); | ||
| 652 | buf[1] = ID_DONGLE_COMMIT_DEVICE; | ||
| 653 | SetFeatureReport(dev, buf, 2); | ||
| 654 | } | ||
| 655 | |||
| 656 | //--------------------------------------------------------------------------- | ||
| 657 | // Close a Steam Controller | ||
| 658 | //--------------------------------------------------------------------------- | ||
| 659 | static void CloseSteamController(SDL_HIDAPI_Device *dev) | ||
| 660 | { | ||
| 661 | // Switch the Steam Controller back to lizard mode so it works with the OS | ||
| 662 | unsigned char buf[65]; | ||
| 663 | int nSettings = 0; | ||
| 664 | |||
| 665 | // Reset digital button mappings | ||
| 666 | SDL_memset(buf, 0, 65); | ||
| 667 | buf[1] = ID_SET_DEFAULT_DIGITAL_MAPPINGS; | ||
| 668 | SetFeatureReport(dev, buf, 2); | ||
| 669 | |||
| 670 | // Reset the default settings | ||
| 671 | SDL_memset(buf, 0, 65); | ||
| 672 | buf[1] = ID_LOAD_DEFAULT_SETTINGS; | ||
| 673 | buf[2] = 0; | ||
| 674 | SetFeatureReport(dev, buf, 3); | ||
| 675 | |||
| 676 | // Reset mouse mode for lizard mode | ||
| 677 | SDL_memset(buf, 0, 65); | ||
| 678 | buf[1] = ID_SET_SETTINGS_VALUES; | ||
| 679 | ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE); | ||
| 680 | buf[2] = (unsigned char)(nSettings * 3); | ||
| 681 | SetFeatureReport(dev, buf, 3 + nSettings * 3); | ||
| 682 | } | ||
| 683 | |||
| 684 | //--------------------------------------------------------------------------- | ||
| 685 | // Scale and clamp values to a range | ||
| 686 | //--------------------------------------------------------------------------- | ||
| 687 | static float RemapValClamped(float val, float A, float B, float C, float D) | ||
| 688 | { | ||
| 689 | if (A == B) { | ||
| 690 | return (val - B) >= 0.0f ? D : C; | ||
| 691 | } else { | ||
| 692 | float cVal = (val - A) / (B - A); | ||
| 693 | cVal = clamp(cVal, 0.0f, 1.0f); | ||
| 694 | |||
| 695 | return C + (D - C) * cVal; | ||
| 696 | } | ||
| 697 | } | ||
| 698 | |||
| 699 | //--------------------------------------------------------------------------- | ||
| 700 | // Rotate the pad coordinates | ||
| 701 | //--------------------------------------------------------------------------- | ||
| 702 | static void RotatePad(int *pX, int *pY, float flAngleInRad) | ||
| 703 | { | ||
| 704 | int origX = *pX, origY = *pY; | ||
| 705 | |||
| 706 | *pX = (int)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY); | ||
| 707 | *pY = (int)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY); | ||
| 708 | } | ||
| 709 | static void RotatePadShort(short *pX, short *pY, float flAngleInRad) | ||
| 710 | { | ||
| 711 | int origX = *pX, origY = *pY; | ||
| 712 | |||
| 713 | *pX = (short)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY); | ||
| 714 | *pY = (short)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY); | ||
| 715 | } | ||
| 716 | |||
| 717 | //--------------------------------------------------------------------------- | ||
| 718 | // Format the first part of the state packet | ||
| 719 | //--------------------------------------------------------------------------- | ||
| 720 | static void FormatStatePacketUntilGyro(SteamControllerStateInternal_t *pState, ValveControllerStatePacket_t *pStatePacket) | ||
| 721 | { | ||
| 722 | int nLeftPadX; | ||
| 723 | int nLeftPadY; | ||
| 724 | int nRightPadX; | ||
| 725 | int nRightPadY; | ||
| 726 | int nPadOffset; | ||
| 727 | |||
| 728 | // 15 degrees in rad | ||
| 729 | const float flRotationAngle = 0.261799f; | ||
| 730 | |||
| 731 | SDL_memset(pState, 0, offsetof(SteamControllerStateInternal_t, sBatteryLevel)); | ||
| 732 | |||
| 733 | // pState->eControllerType = m_eControllerType; | ||
| 734 | pState->eControllerType = 2; // k_eControllerType_SteamController; | ||
| 735 | pState->unPacketNum = pStatePacket->unPacketNum; | ||
| 736 | |||
| 737 | // We have a chunk of trigger data in the packet format here, so zero it out afterwards | ||
| 738 | SDL_memcpy(&pState->ulButtons, &pStatePacket->ButtonTriggerData.ulButtons, 8); | ||
| 739 | pState->ulButtons &= ~0xFFFF000000LL; | ||
| 740 | |||
| 741 | // The firmware uses this bit to tell us what kind of data is packed into the left two axes | ||
| 742 | if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) { | ||
| 743 | // Finger-down bit not set; "left pad" is actually trackpad | ||
| 744 | pState->sLeftPadX = pState->sPrevLeftPad[0] = pStatePacket->sLeftPadX; | ||
| 745 | pState->sLeftPadY = pState->sPrevLeftPad[1] = pStatePacket->sLeftPadY; | ||
| 746 | |||
| 747 | if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) { | ||
| 748 | // The controller is interleaving both stick and pad data, both are active | ||
| 749 | pState->sLeftStickX = pState->sPrevLeftStick[0]; | ||
| 750 | pState->sLeftStickY = pState->sPrevLeftStick[1]; | ||
| 751 | } else { | ||
| 752 | // The stick is not active | ||
| 753 | pState->sPrevLeftStick[0] = 0; | ||
| 754 | pState->sPrevLeftStick[1] = 0; | ||
| 755 | } | ||
| 756 | } else { | ||
| 757 | // Finger-down bit not set; "left pad" is actually joystick | ||
| 758 | |||
| 759 | // XXX there's a firmware bug where sometimes padX is 0 and padY is a large number (actually the battery voltage) | ||
| 760 | // If that happens skip this packet and report last frames stick | ||
| 761 | /* | ||
| 762 | if ( m_eControllerType == k_eControllerType_SteamControllerV2 && pStatePacket->sLeftPadY > 900 ) { | ||
| 763 | pState->sLeftStickX = pState->sPrevLeftStick[0]; | ||
| 764 | pState->sLeftStickY = pState->sPrevLeftStick[1]; | ||
| 765 | } else | ||
| 766 | */ | ||
| 767 | { | ||
| 768 | pState->sPrevLeftStick[0] = pState->sLeftStickX = pStatePacket->sLeftPadX; | ||
| 769 | pState->sPrevLeftStick[1] = pState->sLeftStickY = pStatePacket->sLeftPadY; | ||
| 770 | } | ||
| 771 | /* | ||
| 772 | if (m_eControllerType == k_eControllerType_SteamControllerV2) { | ||
| 773 | UpdateV2JoystickCap(&state); | ||
| 774 | } | ||
| 775 | */ | ||
| 776 | |||
| 777 | if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) { | ||
| 778 | // The controller is interleaving both stick and pad data, both are active | ||
| 779 | pState->sLeftPadX = pState->sPrevLeftPad[0]; | ||
| 780 | pState->sLeftPadY = pState->sPrevLeftPad[1]; | ||
| 781 | } else { | ||
| 782 | // The trackpad is not active | ||
| 783 | pState->sPrevLeftPad[0] = 0; | ||
| 784 | pState->sPrevLeftPad[1] = 0; | ||
| 785 | |||
| 786 | // Old controllers send trackpad click for joystick button when trackpad is not active | ||
| 787 | if (pState->ulButtons & STEAM_BUTTON_LEFTPAD_CLICKED_MASK) { | ||
| 788 | pState->ulButtons &= ~STEAM_BUTTON_LEFTPAD_CLICKED_MASK; | ||
| 789 | pState->ulButtons |= STEAM_JOYSTICK_BUTTON_MASK; | ||
| 790 | } | ||
| 791 | } | ||
| 792 | } | ||
| 793 | |||
| 794 | // Fingerdown bit indicates if the packed left axis data was joystick or pad, | ||
| 795 | // but if we are interleaving both, the left finger is definitely on the pad. | ||
| 796 | if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) { | ||
| 797 | pState->ulButtons |= STEAM_LEFTPAD_FINGERDOWN_MASK; | ||
| 798 | } | ||
| 799 | |||
| 800 | pState->sRightPadX = pStatePacket->sRightPadX; | ||
| 801 | pState->sRightPadY = pStatePacket->sRightPadY; | ||
| 802 | |||
| 803 | nLeftPadX = pState->sLeftPadX; | ||
| 804 | nLeftPadY = pState->sLeftPadY; | ||
| 805 | nRightPadX = pState->sRightPadX; | ||
| 806 | nRightPadY = pState->sRightPadY; | ||
| 807 | |||
| 808 | RotatePad(&nLeftPadX, &nLeftPadY, -flRotationAngle); | ||
| 809 | RotatePad(&nRightPadX, &nRightPadY, flRotationAngle); | ||
| 810 | |||
| 811 | if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) { | ||
| 812 | nPadOffset = 1000; | ||
| 813 | } else { | ||
| 814 | nPadOffset = 0; | ||
| 815 | } | ||
| 816 | |||
| 817 | pState->sLeftPadX = (short)clamp(nLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); | ||
| 818 | pState->sLeftPadY = (short)clamp(nLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); | ||
| 819 | |||
| 820 | nPadOffset = 0; | ||
| 821 | if (pState->ulButtons & STEAM_RIGHTPAD_FINGERDOWN_MASK) { | ||
| 822 | nPadOffset = 1000; | ||
| 823 | } else { | ||
| 824 | nPadOffset = 0; | ||
| 825 | } | ||
| 826 | |||
| 827 | pState->sRightPadX = (short)clamp(nRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); | ||
| 828 | pState->sRightPadY = (short)clamp(nRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); | ||
| 829 | |||
| 830 | pState->sTriggerL = (unsigned short)RemapValClamped((float)((pStatePacket->ButtonTriggerData.Triggers.nLeft << 7) | pStatePacket->ButtonTriggerData.Triggers.nLeft), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16); | ||
| 831 | pState->sTriggerR = (unsigned short)RemapValClamped((float)((pStatePacket->ButtonTriggerData.Triggers.nRight << 7) | pStatePacket->ButtonTriggerData.Triggers.nRight), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16); | ||
| 832 | } | ||
| 833 | |||
| 834 | //--------------------------------------------------------------------------- | ||
| 835 | // Update Steam Controller state from a BLE data packet, returns true if it parsed data | ||
| 836 | //--------------------------------------------------------------------------- | ||
| 837 | static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState) | ||
| 838 | { | ||
| 839 | const float flRotationAngle = 0.261799f; | ||
| 840 | uint32_t ucOptionDataMask; | ||
| 841 | |||
| 842 | pState->unPacketNum++; | ||
| 843 | ucOptionDataMask = (*pData++ & 0xF0); | ||
| 844 | ucOptionDataMask |= (uint32_t)(*pData++) << 8; | ||
| 845 | if (ucOptionDataMask & k_EBLEButtonChunk1) { | ||
| 846 | SDL_memcpy(&pState->ulButtons, pData, 3); | ||
| 847 | pData += 3; | ||
| 848 | } | ||
| 849 | if (ucOptionDataMask & k_EBLEButtonChunk2) { | ||
| 850 | // 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 | ||
| 851 | pState->sTriggerL = (unsigned short)RemapValClamped((float)((pData[0] << 7) | pData[0]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16); | ||
| 852 | pState->sTriggerR = (unsigned short)RemapValClamped((float)((pData[1] << 7) | pData[1]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16); | ||
| 853 | pData += 2; | ||
| 854 | } | ||
| 855 | if (ucOptionDataMask & k_EBLEButtonChunk3) { | ||
| 856 | uint8_t *pButtonByte = (uint8_t *)&pState->ulButtons; | ||
| 857 | pButtonByte[5] = *pData++; | ||
| 858 | pButtonByte[6] = *pData++; | ||
| 859 | pButtonByte[7] = *pData++; | ||
| 860 | } | ||
| 861 | if (ucOptionDataMask & k_EBLELeftJoystickChunk) { | ||
| 862 | // This doesn't handle any of the special headcrab stuff for raw joystick which is OK for now since that FW doesn't support | ||
| 863 | // this protocol yet either | ||
| 864 | int nLength = sizeof(pState->sLeftStickX) + sizeof(pState->sLeftStickY); | ||
| 865 | SDL_memcpy(&pState->sLeftStickX, pData, nLength); | ||
| 866 | pData += nLength; | ||
| 867 | } | ||
| 868 | if (ucOptionDataMask & k_EBLELeftTrackpadChunk) { | ||
| 869 | int nLength = sizeof(pState->sLeftPadX) + sizeof(pState->sLeftPadY); | ||
| 870 | int nPadOffset; | ||
| 871 | SDL_memcpy(&pState->sLeftPadX, pData, nLength); | ||
| 872 | if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) { | ||
| 873 | nPadOffset = 1000; | ||
| 874 | } else { | ||
| 875 | nPadOffset = 0; | ||
| 876 | } | ||
| 877 | |||
| 878 | RotatePadShort(&pState->sLeftPadX, &pState->sLeftPadY, -flRotationAngle); | ||
| 879 | pState->sLeftPadX = (short)clamp(pState->sLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); | ||
| 880 | pState->sLeftPadY = (short)clamp(pState->sLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); | ||
| 881 | pData += nLength; | ||
| 882 | } | ||
| 883 | if (ucOptionDataMask & k_EBLERightTrackpadChunk) { | ||
| 884 | int nLength = sizeof(pState->sRightPadX) + sizeof(pState->sRightPadY); | ||
| 885 | int nPadOffset = 0; | ||
| 886 | |||
| 887 | SDL_memcpy(&pState->sRightPadX, pData, nLength); | ||
| 888 | |||
| 889 | if (pState->ulButtons & STEAM_RIGHTPAD_FINGERDOWN_MASK) { | ||
| 890 | nPadOffset = 1000; | ||
| 891 | } else { | ||
| 892 | nPadOffset = 0; | ||
| 893 | } | ||
| 894 | |||
| 895 | RotatePadShort(&pState->sRightPadX, &pState->sRightPadY, flRotationAngle); | ||
| 896 | pState->sRightPadX = (short)clamp(pState->sRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); | ||
| 897 | pState->sRightPadY = (short)clamp(pState->sRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); | ||
| 898 | pData += nLength; | ||
| 899 | } | ||
| 900 | if (ucOptionDataMask & k_EBLEIMUAccelChunk) { | ||
| 901 | int nLength = sizeof(pState->sAccelX) + sizeof(pState->sAccelY) + sizeof(pState->sAccelZ); | ||
| 902 | SDL_memcpy(&pState->sAccelX, pData, nLength); | ||
| 903 | pData += nLength; | ||
| 904 | } | ||
| 905 | if (ucOptionDataMask & k_EBLEIMUGyroChunk) { | ||
| 906 | int nLength = sizeof(pState->sAccelX) + sizeof(pState->sAccelY) + sizeof(pState->sAccelZ); | ||
| 907 | SDL_memcpy(&pState->sGyroX, pData, nLength); | ||
| 908 | pData += nLength; | ||
| 909 | } | ||
| 910 | if (ucOptionDataMask & k_EBLEIMUQuatChunk) { | ||
| 911 | int nLength = sizeof(pState->sGyroQuatW) + sizeof(pState->sGyroQuatX) + sizeof(pState->sGyroQuatY) + sizeof(pState->sGyroQuatZ); | ||
| 912 | SDL_memcpy(&pState->sGyroQuatW, pData, nLength); | ||
| 913 | pData += nLength; | ||
| 914 | } | ||
| 915 | return true; | ||
| 916 | } | ||
| 917 | |||
| 918 | //--------------------------------------------------------------------------- | ||
| 919 | // Update Steam Controller state from a data packet, returns true if it parsed data | ||
| 920 | //--------------------------------------------------------------------------- | ||
| 921 | static bool UpdateSteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState) | ||
| 922 | { | ||
| 923 | ValveInReport_t *pInReport = (ValveInReport_t *)pData; | ||
| 924 | |||
| 925 | if (pInReport->header.unReportVersion != k_ValveInReportMsgVersion) { | ||
| 926 | if ((pData[0] & 0x0F) == k_EBLEReportState) { | ||
| 927 | return UpdateBLESteamControllerState(pData, nDataSize, pState); | ||
| 928 | } | ||
| 929 | return false; | ||
| 930 | } | ||
| 931 | |||
| 932 | if ((pInReport->header.ucType != ID_CONTROLLER_STATE) && | ||
| 933 | (pInReport->header.ucType != ID_CONTROLLER_BLE_STATE)) { | ||
| 934 | return false; | ||
| 935 | } | ||
| 936 | |||
| 937 | if (pInReport->header.ucType == ID_CONTROLLER_STATE) { | ||
| 938 | ValveControllerStatePacket_t *pStatePacket = &pInReport->payload.controllerState; | ||
| 939 | |||
| 940 | // No new data to process; indicate that we received a state packet, but otherwise do nothing. | ||
| 941 | if (pState->unPacketNum == pStatePacket->unPacketNum) { | ||
| 942 | return true; | ||
| 943 | } | ||
| 944 | |||
| 945 | FormatStatePacketUntilGyro(pState, pStatePacket); | ||
| 946 | |||
| 947 | pState->sAccelX = pStatePacket->sAccelX; | ||
| 948 | pState->sAccelY = pStatePacket->sAccelY; | ||
| 949 | pState->sAccelZ = pStatePacket->sAccelZ; | ||
| 950 | |||
| 951 | pState->sGyroQuatW = pStatePacket->sGyroQuatW; | ||
| 952 | pState->sGyroQuatX = pStatePacket->sGyroQuatX; | ||
| 953 | pState->sGyroQuatY = pStatePacket->sGyroQuatY; | ||
| 954 | pState->sGyroQuatZ = pStatePacket->sGyroQuatZ; | ||
| 955 | |||
| 956 | pState->sGyroX = pStatePacket->sGyroX; | ||
| 957 | pState->sGyroY = pStatePacket->sGyroY; | ||
| 958 | pState->sGyroZ = pStatePacket->sGyroZ; | ||
| 959 | |||
| 960 | } else if (pInReport->header.ucType == ID_CONTROLLER_BLE_STATE) { | ||
| 961 | ValveControllerBLEStatePacket_t *pBLEStatePacket = &pInReport->payload.controllerBLEState; | ||
| 962 | ValveControllerStatePacket_t *pStatePacket = &pInReport->payload.controllerState; | ||
| 963 | |||
| 964 | // No new data to process; indicate that we received a state packet, but otherwise do nothing. | ||
| 965 | if (pState->unPacketNum == pStatePacket->unPacketNum) { | ||
| 966 | return true; | ||
| 967 | } | ||
| 968 | |||
| 969 | FormatStatePacketUntilGyro(pState, pStatePacket); | ||
| 970 | |||
| 971 | switch (pBLEStatePacket->ucGyroDataType) { | ||
| 972 | case 1: | ||
| 973 | pState->sGyroQuatW = ((float)pBLEStatePacket->sGyro[0]); | ||
| 974 | pState->sGyroQuatX = ((float)pBLEStatePacket->sGyro[1]); | ||
| 975 | pState->sGyroQuatY = ((float)pBLEStatePacket->sGyro[2]); | ||
| 976 | pState->sGyroQuatZ = ((float)pBLEStatePacket->sGyro[3]); | ||
| 977 | break; | ||
| 978 | |||
| 979 | case 2: | ||
| 980 | pState->sAccelX = pBLEStatePacket->sGyro[0]; | ||
| 981 | pState->sAccelY = pBLEStatePacket->sGyro[1]; | ||
| 982 | pState->sAccelZ = pBLEStatePacket->sGyro[2]; | ||
| 983 | break; | ||
| 984 | |||
| 985 | case 3: | ||
| 986 | pState->sGyroX = pBLEStatePacket->sGyro[0]; | ||
| 987 | pState->sGyroY = pBLEStatePacket->sGyro[1]; | ||
| 988 | pState->sGyroZ = pBLEStatePacket->sGyro[2]; | ||
| 989 | break; | ||
| 990 | |||
| 991 | default: | ||
| 992 | break; | ||
| 993 | } | ||
| 994 | } | ||
| 995 | |||
| 996 | return true; | ||
| 997 | } | ||
| 998 | |||
| 999 | /*****************************************************************************************************/ | ||
| 1000 | |||
| 1001 | typedef struct | ||
| 1002 | { | ||
| 1003 | SDL_HIDAPI_Device *device; | ||
| 1004 | bool connected; | ||
| 1005 | bool report_sensors; | ||
| 1006 | uint32_t update_rate_in_us; | ||
| 1007 | Uint64 sensor_timestamp; | ||
| 1008 | Uint64 pairing_time; | ||
| 1009 | |||
| 1010 | SteamControllerPacketAssembler m_assembler; | ||
| 1011 | SteamControllerStateInternal_t m_state; | ||
| 1012 | SteamControllerStateInternal_t m_last_state; | ||
| 1013 | } SDL_DriverSteam_Context; | ||
| 1014 | |||
| 1015 | static bool IsDongle(Uint16 product_id) | ||
| 1016 | { | ||
| 1017 | return (product_id == USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE); | ||
| 1018 | } | ||
| 1019 | |||
| 1020 | static void HIDAPI_DriverSteam_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 1021 | { | ||
| 1022 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata); | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | static void HIDAPI_DriverSteam_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 1026 | { | ||
| 1027 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata); | ||
| 1028 | } | ||
| 1029 | |||
| 1030 | static bool HIDAPI_DriverSteam_IsEnabled(void) | ||
| 1031 | { | ||
| 1032 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT); | ||
| 1033 | } | ||
| 1034 | |||
| 1035 | 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) | ||
| 1036 | { | ||
| 1037 | if (!SDL_IsJoystickSteamController(vendor_id, product_id)) { | ||
| 1038 | return false; | ||
| 1039 | } | ||
| 1040 | |||
| 1041 | if (device->is_bluetooth) { | ||
| 1042 | return true; | ||
| 1043 | } | ||
| 1044 | |||
| 1045 | if (IsDongle(product_id)) { | ||
| 1046 | if (interface_number >= 1 && interface_number <= 4) { | ||
| 1047 | // This is one of the wireless controller interfaces | ||
| 1048 | return true; | ||
| 1049 | } | ||
| 1050 | } else { | ||
| 1051 | if (interface_number == 2) { | ||
| 1052 | // This is the controller interface (not mouse or keyboard) | ||
| 1053 | return true; | ||
| 1054 | } | ||
| 1055 | } | ||
| 1056 | return false; | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | static void HIDAPI_DriverSteam_SetPairingState(SDL_DriverSteam_Context *ctx, bool enabled) | ||
| 1060 | { | ||
| 1061 | // Only have one dongle in pairing mode at a time | ||
| 1062 | static SDL_DriverSteam_Context *s_PairingContext = NULL; | ||
| 1063 | |||
| 1064 | if (enabled && s_PairingContext != NULL) { | ||
| 1065 | return; | ||
| 1066 | } | ||
| 1067 | |||
| 1068 | if (!enabled && s_PairingContext != ctx) { | ||
| 1069 | return; | ||
| 1070 | } | ||
| 1071 | |||
| 1072 | if (ctx->connected) { | ||
| 1073 | return; | ||
| 1074 | } | ||
| 1075 | |||
| 1076 | SetPairingState(ctx->device, enabled); | ||
| 1077 | |||
| 1078 | if (enabled) { | ||
| 1079 | ctx->pairing_time = SDL_GetTicks(); | ||
| 1080 | s_PairingContext = ctx; | ||
| 1081 | } else { | ||
| 1082 | ctx->pairing_time = 0; | ||
| 1083 | s_PairingContext = NULL; | ||
| 1084 | } | ||
| 1085 | } | ||
| 1086 | |||
| 1087 | static void HIDAPI_DriverSteam_RenewPairingState(SDL_DriverSteam_Context *ctx) | ||
| 1088 | { | ||
| 1089 | Uint64 now = SDL_GetTicks(); | ||
| 1090 | |||
| 1091 | if (now >= ctx->pairing_time + PAIRING_STATE_DURATION_SECONDS * 1000) { | ||
| 1092 | SetPairingState(ctx->device, true); | ||
| 1093 | ctx->pairing_time = now; | ||
| 1094 | } | ||
| 1095 | } | ||
| 1096 | |||
| 1097 | static void HIDAPI_DriverSteam_CommitPairing(SDL_DriverSteam_Context *ctx) | ||
| 1098 | { | ||
| 1099 | CommitPairing(ctx->device); | ||
| 1100 | } | ||
| 1101 | |||
| 1102 | static void SDLCALL SDL_PairingEnabledHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 1103 | { | ||
| 1104 | SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)userdata; | ||
| 1105 | bool enabled = SDL_GetStringBoolean(hint, false); | ||
| 1106 | |||
| 1107 | HIDAPI_DriverSteam_SetPairingState(ctx, enabled); | ||
| 1108 | } | ||
| 1109 | |||
| 1110 | static bool HIDAPI_DriverSteam_InitDevice(SDL_HIDAPI_Device *device) | ||
| 1111 | { | ||
| 1112 | SDL_DriverSteam_Context *ctx; | ||
| 1113 | |||
| 1114 | ctx = (SDL_DriverSteam_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 1115 | if (!ctx) { | ||
| 1116 | return false; | ||
| 1117 | } | ||
| 1118 | ctx->device = device; | ||
| 1119 | device->context = ctx; | ||
| 1120 | |||
| 1121 | #ifdef SDL_PLATFORM_WIN32 | ||
| 1122 | if (device->serial) { | ||
| 1123 | // We get a garbage serial number on Windows | ||
| 1124 | SDL_free(device->serial); | ||
| 1125 | device->serial = NULL; | ||
| 1126 | } | ||
| 1127 | #endif // SDL_PLATFORM_WIN32 | ||
| 1128 | |||
| 1129 | HIDAPI_SetDeviceName(device, "Steam Controller"); | ||
| 1130 | |||
| 1131 | // If this is a wireless dongle, request a wireless state update | ||
| 1132 | if (IsDongle(device->product_id)) { | ||
| 1133 | unsigned char buf[65]; | ||
| 1134 | int res; | ||
| 1135 | |||
| 1136 | buf[0] = 0; | ||
| 1137 | buf[1] = ID_DONGLE_GET_WIRELESS_STATE; | ||
| 1138 | res = SetFeatureReport(device, buf, 2); | ||
| 1139 | if (res < 0) { | ||
| 1140 | return SDL_SetError("Failed to send ID_DONGLE_GET_WIRELESS_STATE request"); | ||
| 1141 | } | ||
| 1142 | |||
| 1143 | for (int attempt = 0; attempt < 5; ++attempt) { | ||
| 1144 | uint8_t data[128]; | ||
| 1145 | |||
| 1146 | res = ReadSteamController(device->dev, data, sizeof(data)); | ||
| 1147 | if (res == 0) { | ||
| 1148 | SDL_Delay(1); | ||
| 1149 | continue; | ||
| 1150 | } | ||
| 1151 | if (res < 0) { | ||
| 1152 | break; | ||
| 1153 | } | ||
| 1154 | |||
| 1155 | #ifdef DEBUG_STEAM_PROTOCOL | ||
| 1156 | HIDAPI_DumpPacket("Initial dongle packet: size = %d", data, res); | ||
| 1157 | #endif | ||
| 1158 | |||
| 1159 | if (D0G_IS_WIRELESS_CONNECT(data, res)) { | ||
| 1160 | ctx->connected = true; | ||
| 1161 | break; | ||
| 1162 | } else if (D0G_IS_WIRELESS_DISCONNECT(data, res)) { | ||
| 1163 | ctx->connected = false; | ||
| 1164 | break; | ||
| 1165 | } | ||
| 1166 | } | ||
| 1167 | |||
| 1168 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED, | ||
| 1169 | SDL_PairingEnabledHintChanged, ctx); | ||
| 1170 | } else { | ||
| 1171 | // Wired and BLE controllers are always connected if HIDAPI can see them | ||
| 1172 | ctx->connected = true; | ||
| 1173 | } | ||
| 1174 | |||
| 1175 | if (ctx->connected) { | ||
| 1176 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 1177 | } else { | ||
| 1178 | // We will enumerate any attached controllers in UpdateDevice() | ||
| 1179 | return true; | ||
| 1180 | } | ||
| 1181 | } | ||
| 1182 | |||
| 1183 | static int HIDAPI_DriverSteam_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 1184 | { | ||
| 1185 | return -1; | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | static void HIDAPI_DriverSteam_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 1189 | { | ||
| 1190 | } | ||
| 1191 | |||
| 1192 | static bool SetHomeLED(SDL_HIDAPI_Device *device, Uint8 value) | ||
| 1193 | { | ||
| 1194 | unsigned char buf[65]; | ||
| 1195 | int nSettings = 0; | ||
| 1196 | |||
| 1197 | SDL_memset(buf, 0, 65); | ||
| 1198 | buf[1] = ID_SET_SETTINGS_VALUES; | ||
| 1199 | ADD_SETTING(SETTING_LED_USER_BRIGHTNESS, value); | ||
| 1200 | buf[2] = (unsigned char)(nSettings * 3); | ||
| 1201 | if (SetFeatureReport(device, buf, 3 + nSettings * 3) < 0) { | ||
| 1202 | return SDL_SetError("Couldn't write feature report"); | ||
| 1203 | } | ||
| 1204 | return true; | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 1208 | { | ||
| 1209 | SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)userdata; | ||
| 1210 | |||
| 1211 | if (hint && *hint) { | ||
| 1212 | int value; | ||
| 1213 | |||
| 1214 | if (SDL_strchr(hint, '.') != NULL) { | ||
| 1215 | value = (int)(100.0f * SDL_atof(hint)); | ||
| 1216 | if (value > 255) { | ||
| 1217 | value = 255; | ||
| 1218 | } | ||
| 1219 | } else if (SDL_GetStringBoolean(hint, true)) { | ||
| 1220 | value = 100; | ||
| 1221 | } else { | ||
| 1222 | value = 0; | ||
| 1223 | } | ||
| 1224 | SetHomeLED(ctx->device, (Uint8)value); | ||
| 1225 | } | ||
| 1226 | } | ||
| 1227 | |||
| 1228 | static bool HIDAPI_DriverSteam_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1229 | { | ||
| 1230 | SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; | ||
| 1231 | float update_rate_in_hz = 0.0f; | ||
| 1232 | |||
| 1233 | SDL_AssertJoysticksLocked(); | ||
| 1234 | |||
| 1235 | ctx->report_sensors = false; | ||
| 1236 | SDL_zero(ctx->m_assembler); | ||
| 1237 | SDL_zero(ctx->m_state); | ||
| 1238 | SDL_zero(ctx->m_last_state); | ||
| 1239 | |||
| 1240 | if (!ResetSteamController(device, false, &ctx->update_rate_in_us)) { | ||
| 1241 | SDL_SetError("Couldn't reset controller"); | ||
| 1242 | return false; | ||
| 1243 | } | ||
| 1244 | if (ctx->update_rate_in_us > 0) { | ||
| 1245 | update_rate_in_hz = 1000000.0f / ctx->update_rate_in_us; | ||
| 1246 | } | ||
| 1247 | |||
| 1248 | InitializeSteamControllerPacketAssembler(&ctx->m_assembler, device->is_bluetooth); | ||
| 1249 | |||
| 1250 | // Initialize the joystick capabilities | ||
| 1251 | joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_BUTTONS; | ||
| 1252 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 1253 | joystick->nhats = 1; | ||
| 1254 | |||
| 1255 | if (IsDongle(device->product_id)) { | ||
| 1256 | joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; | ||
| 1257 | } | ||
| 1258 | |||
| 1259 | SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz); | ||
| 1260 | SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz); | ||
| 1261 | |||
| 1262 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED, | ||
| 1263 | SDL_HomeLEDHintChanged, ctx); | ||
| 1264 | |||
| 1265 | return true; | ||
| 1266 | } | ||
| 1267 | |||
| 1268 | static bool HIDAPI_DriverSteam_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 1269 | { | ||
| 1270 | // You should use the full Steam Input API for rumble support | ||
| 1271 | return SDL_Unsupported(); | ||
| 1272 | } | ||
| 1273 | |||
| 1274 | static bool HIDAPI_DriverSteam_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 1275 | { | ||
| 1276 | return SDL_Unsupported(); | ||
| 1277 | } | ||
| 1278 | |||
| 1279 | static Uint32 HIDAPI_DriverSteam_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1280 | { | ||
| 1281 | // You should use the full Steam Input API for extended capabilities | ||
| 1282 | return 0; | ||
| 1283 | } | ||
| 1284 | |||
| 1285 | static bool HIDAPI_DriverSteam_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 1286 | { | ||
| 1287 | // You should use the full Steam Input API for LED support | ||
| 1288 | return SDL_Unsupported(); | ||
| 1289 | } | ||
| 1290 | |||
| 1291 | static bool HIDAPI_DriverSteam_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 1292 | { | ||
| 1293 | if (size == 65) { | ||
| 1294 | if (SetFeatureReport(device, data, size) < 0) { | ||
| 1295 | return SDL_SetError("Couldn't write feature report"); | ||
| 1296 | } | ||
| 1297 | return true; | ||
| 1298 | } | ||
| 1299 | return SDL_Unsupported(); | ||
| 1300 | } | ||
| 1301 | |||
| 1302 | static bool HIDAPI_DriverSteam_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 1303 | { | ||
| 1304 | SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; | ||
| 1305 | unsigned char buf[65]; | ||
| 1306 | int nSettings = 0; | ||
| 1307 | |||
| 1308 | SDL_memset(buf, 0, 65); | ||
| 1309 | buf[1] = ID_SET_SETTINGS_VALUES; | ||
| 1310 | if (enabled) { | ||
| 1311 | ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO); | ||
| 1312 | } else { | ||
| 1313 | ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_OFF); | ||
| 1314 | } | ||
| 1315 | buf[2] = (unsigned char)(nSettings * 3); | ||
| 1316 | if (SetFeatureReport(device, buf, 3 + nSettings * 3) < 0) { | ||
| 1317 | return SDL_SetError("Couldn't write feature report"); | ||
| 1318 | } | ||
| 1319 | |||
| 1320 | ctx->report_sensors = enabled; | ||
| 1321 | |||
| 1322 | return true; | ||
| 1323 | } | ||
| 1324 | |||
| 1325 | static bool ControllerConnected(SDL_HIDAPI_Device *device, SDL_Joystick **joystick) | ||
| 1326 | { | ||
| 1327 | SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; | ||
| 1328 | |||
| 1329 | if (!HIDAPI_JoystickConnected(device, NULL)) { | ||
| 1330 | return false; | ||
| 1331 | } | ||
| 1332 | |||
| 1333 | // We'll automatically accept this controller if we're in pairing mode | ||
| 1334 | HIDAPI_DriverSteam_CommitPairing(ctx); | ||
| 1335 | |||
| 1336 | *joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 1337 | ctx->connected = true; | ||
| 1338 | return true; | ||
| 1339 | } | ||
| 1340 | |||
| 1341 | static void ControllerDisconnected(SDL_HIDAPI_Device *device, SDL_Joystick **joystick) | ||
| 1342 | { | ||
| 1343 | SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; | ||
| 1344 | |||
| 1345 | if (device->joysticks) { | ||
| 1346 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 1347 | } | ||
| 1348 | ctx->connected = false; | ||
| 1349 | *joystick = NULL; | ||
| 1350 | } | ||
| 1351 | |||
| 1352 | static bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 1353 | { | ||
| 1354 | SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; | ||
| 1355 | SDL_Joystick *joystick = NULL; | ||
| 1356 | |||
| 1357 | if (device->num_joysticks > 0) { | ||
| 1358 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 1359 | } | ||
| 1360 | |||
| 1361 | if (ctx->pairing_time) { | ||
| 1362 | HIDAPI_DriverSteam_RenewPairingState(ctx); | ||
| 1363 | } | ||
| 1364 | |||
| 1365 | for (;;) { | ||
| 1366 | uint8_t data[128]; | ||
| 1367 | int r, nPacketLength; | ||
| 1368 | const Uint8 *pPacket; | ||
| 1369 | |||
| 1370 | r = ReadSteamController(device->dev, data, sizeof(data)); | ||
| 1371 | if (r == 0) { | ||
| 1372 | break; | ||
| 1373 | } | ||
| 1374 | if (r < 0) { | ||
| 1375 | // Failed to read from controller | ||
| 1376 | ControllerDisconnected(device, &joystick); | ||
| 1377 | return false; | ||
| 1378 | } | ||
| 1379 | |||
| 1380 | #ifdef DEBUG_STEAM_PROTOCOL | ||
| 1381 | HIDAPI_DumpPacket("Steam Controller packet: size = %d", data, r); | ||
| 1382 | #endif | ||
| 1383 | |||
| 1384 | nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&ctx->m_assembler, data, r); | ||
| 1385 | pPacket = ctx->m_assembler.uBuffer; | ||
| 1386 | |||
| 1387 | if (nPacketLength > 0 && UpdateSteamControllerState(pPacket, nPacketLength, &ctx->m_state)) { | ||
| 1388 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 1389 | |||
| 1390 | if (!ctx->connected) { | ||
| 1391 | // Maybe we missed a wireless status packet? | ||
| 1392 | ControllerConnected(device, &joystick); | ||
| 1393 | } | ||
| 1394 | |||
| 1395 | if (!joystick) { | ||
| 1396 | continue; | ||
| 1397 | } | ||
| 1398 | |||
| 1399 | if (ctx->m_state.ulButtons != ctx->m_last_state.ulButtons) { | ||
| 1400 | Uint8 hat = 0; | ||
| 1401 | |||
| 1402 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, | ||
| 1403 | ((ctx->m_state.ulButtons & STEAM_BUTTON_SOUTH_MASK) != 0)); | ||
| 1404 | |||
| 1405 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, | ||
| 1406 | ((ctx->m_state.ulButtons & STEAM_BUTTON_EAST_MASK) != 0)); | ||
| 1407 | |||
| 1408 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, | ||
| 1409 | ((ctx->m_state.ulButtons & STEAM_BUTTON_WEST_MASK) != 0)); | ||
| 1410 | |||
| 1411 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, | ||
| 1412 | ((ctx->m_state.ulButtons & STEAM_BUTTON_NORTH_MASK) != 0)); | ||
| 1413 | |||
| 1414 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, | ||
| 1415 | ((ctx->m_state.ulButtons & STEAM_LEFT_BUMPER_MASK) != 0)); | ||
| 1416 | |||
| 1417 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, | ||
| 1418 | ((ctx->m_state.ulButtons & STEAM_RIGHT_BUMPER_MASK) != 0)); | ||
| 1419 | |||
| 1420 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, | ||
| 1421 | ((ctx->m_state.ulButtons & STEAM_BUTTON_MENU_MASK) != 0)); | ||
| 1422 | |||
| 1423 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, | ||
| 1424 | ((ctx->m_state.ulButtons & STEAM_BUTTON_ESCAPE_MASK) != 0)); | ||
| 1425 | |||
| 1426 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, | ||
| 1427 | ((ctx->m_state.ulButtons & STEAM_BUTTON_STEAM_MASK) != 0)); | ||
| 1428 | |||
| 1429 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, | ||
| 1430 | ((ctx->m_state.ulButtons & STEAM_JOYSTICK_BUTTON_MASK) != 0)); | ||
| 1431 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE, | ||
| 1432 | ((ctx->m_state.ulButtons & STEAM_BUTTON_BACK_LEFT_MASK) != 0)); | ||
| 1433 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE, | ||
| 1434 | ((ctx->m_state.ulButtons & STEAM_BUTTON_BACK_RIGHT_MASK) != 0)); | ||
| 1435 | |||
| 1436 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, | ||
| 1437 | ((ctx->m_state.ulButtons & STEAM_BUTTON_RIGHTPAD_CLICKED_MASK) != 0)); | ||
| 1438 | |||
| 1439 | if (ctx->m_state.ulButtons & STEAM_DPAD_UP_MASK) { | ||
| 1440 | hat |= SDL_HAT_UP; | ||
| 1441 | } | ||
| 1442 | if (ctx->m_state.ulButtons & STEAM_DPAD_DOWN_MASK) { | ||
| 1443 | hat |= SDL_HAT_DOWN; | ||
| 1444 | } | ||
| 1445 | if (ctx->m_state.ulButtons & STEAM_DPAD_LEFT_MASK) { | ||
| 1446 | hat |= SDL_HAT_LEFT; | ||
| 1447 | } | ||
| 1448 | if (ctx->m_state.ulButtons & STEAM_DPAD_RIGHT_MASK) { | ||
| 1449 | hat |= SDL_HAT_RIGHT; | ||
| 1450 | } | ||
| 1451 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 1452 | } | ||
| 1453 | |||
| 1454 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (int)ctx->m_state.sTriggerL * 2 - 32768); | ||
| 1455 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (int)ctx->m_state.sTriggerR * 2 - 32768); | ||
| 1456 | |||
| 1457 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, ctx->m_state.sLeftStickX); | ||
| 1458 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~ctx->m_state.sLeftStickY); | ||
| 1459 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, ctx->m_state.sRightPadX); | ||
| 1460 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~ctx->m_state.sRightPadY); | ||
| 1461 | |||
| 1462 | if (ctx->report_sensors) { | ||
| 1463 | float values[3]; | ||
| 1464 | |||
| 1465 | ctx->sensor_timestamp += SDL_US_TO_NS(ctx->update_rate_in_us); | ||
| 1466 | |||
| 1467 | values[0] = (ctx->m_state.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); | ||
| 1468 | values[1] = (ctx->m_state.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); | ||
| 1469 | values[2] = (ctx->m_state.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); | ||
| 1470 | SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp, values, 3); | ||
| 1471 | |||
| 1472 | values[0] = (ctx->m_state.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; | ||
| 1473 | values[1] = (ctx->m_state.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; | ||
| 1474 | values[2] = (-ctx->m_state.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; | ||
| 1475 | SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp, values, 3); | ||
| 1476 | } | ||
| 1477 | |||
| 1478 | ctx->m_last_state = ctx->m_state; | ||
| 1479 | } else if (!ctx->connected && D0G_IS_WIRELESS_CONNECT(pPacket, nPacketLength)) { | ||
| 1480 | ControllerConnected(device, &joystick); | ||
| 1481 | } else if (ctx->connected && D0G_IS_WIRELESS_DISCONNECT(pPacket, nPacketLength)) { | ||
| 1482 | ControllerDisconnected(device, &joystick); | ||
| 1483 | } | ||
| 1484 | } | ||
| 1485 | return true; | ||
| 1486 | } | ||
| 1487 | |||
| 1488 | static void HIDAPI_DriverSteam_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1489 | { | ||
| 1490 | SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; | ||
| 1491 | |||
| 1492 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED, | ||
| 1493 | SDL_HomeLEDHintChanged, ctx); | ||
| 1494 | |||
| 1495 | CloseSteamController(device); | ||
| 1496 | } | ||
| 1497 | |||
| 1498 | static void HIDAPI_DriverSteam_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 1499 | { | ||
| 1500 | SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context; | ||
| 1501 | |||
| 1502 | if (IsDongle(device->product_id)) { | ||
| 1503 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED, | ||
| 1504 | SDL_PairingEnabledHintChanged, ctx); | ||
| 1505 | |||
| 1506 | HIDAPI_DriverSteam_SetPairingState(ctx, false); | ||
| 1507 | } | ||
| 1508 | } | ||
| 1509 | |||
| 1510 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam = { | ||
| 1511 | SDL_HINT_JOYSTICK_HIDAPI_STEAM, | ||
| 1512 | true, | ||
| 1513 | HIDAPI_DriverSteam_RegisterHints, | ||
| 1514 | HIDAPI_DriverSteam_UnregisterHints, | ||
| 1515 | HIDAPI_DriverSteam_IsEnabled, | ||
| 1516 | HIDAPI_DriverSteam_IsSupportedDevice, | ||
| 1517 | HIDAPI_DriverSteam_InitDevice, | ||
| 1518 | HIDAPI_DriverSteam_GetDevicePlayerIndex, | ||
| 1519 | HIDAPI_DriverSteam_SetDevicePlayerIndex, | ||
| 1520 | HIDAPI_DriverSteam_UpdateDevice, | ||
| 1521 | HIDAPI_DriverSteam_OpenJoystick, | ||
| 1522 | HIDAPI_DriverSteam_RumbleJoystick, | ||
| 1523 | HIDAPI_DriverSteam_RumbleJoystickTriggers, | ||
| 1524 | HIDAPI_DriverSteam_GetJoystickCapabilities, | ||
| 1525 | HIDAPI_DriverSteam_SetJoystickLED, | ||
| 1526 | HIDAPI_DriverSteam_SendJoystickEffect, | ||
| 1527 | HIDAPI_DriverSteam_SetSensorsEnabled, | ||
| 1528 | HIDAPI_DriverSteam_CloseJoystick, | ||
| 1529 | HIDAPI_DriverSteam_FreeDevice, | ||
| 1530 | }; | ||
| 1531 | |||
| 1532 | #endif // SDL_JOYSTICK_HIDAPI_STEAM | ||
| 1533 | |||
| 1534 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../SDL_sysjoystick.h" | ||
| 26 | #include "SDL_hidapijoystick_c.h" | ||
| 27 | #include "SDL_hidapi_rumble.h" | ||
| 28 | #include "../SDL_joystick_c.h" | ||
| 29 | |||
| 30 | #ifdef SDL_JOYSTICK_HIDAPI_STEAM_HORI | ||
| 31 | |||
| 32 | /* Define this if you want to log all packets from the controller */ | ||
| 33 | /*#define DEBUG_HORI_PROTOCOL*/ | ||
| 34 | |||
| 35 | #define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) | ||
| 36 | |||
| 37 | enum | ||
| 38 | { | ||
| 39 | SDL_GAMEPAD_BUTTON_HORI_QAM = 11, | ||
| 40 | SDL_GAMEPAD_BUTTON_HORI_FR, | ||
| 41 | SDL_GAMEPAD_BUTTON_HORI_FL, | ||
| 42 | SDL_GAMEPAD_BUTTON_HORI_M1, | ||
| 43 | SDL_GAMEPAD_BUTTON_HORI_M2, | ||
| 44 | SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, | ||
| 45 | SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, | ||
| 46 | SDL_GAMEPAD_NUM_HORI_BUTTONS | ||
| 47 | }; | ||
| 48 | |||
| 49 | typedef struct | ||
| 50 | { | ||
| 51 | Uint8 last_state[USB_PACKET_LENGTH]; | ||
| 52 | Uint64 sensor_ticks; | ||
| 53 | Uint32 last_tick; | ||
| 54 | bool wireless; | ||
| 55 | bool serial_needs_init; | ||
| 56 | } SDL_DriverSteamHori_Context; | ||
| 57 | |||
| 58 | static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device); | ||
| 59 | |||
| 60 | static void HIDAPI_DriverSteamHori_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 61 | { | ||
| 62 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata); | ||
| 63 | } | ||
| 64 | |||
| 65 | static void HIDAPI_DriverSteamHori_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 66 | { | ||
| 67 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata); | ||
| 68 | } | ||
| 69 | |||
| 70 | static bool HIDAPI_DriverSteamHori_IsEnabled(void) | ||
| 71 | { | ||
| 72 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); | ||
| 73 | } | ||
| 74 | |||
| 75 | 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) | ||
| 76 | { | ||
| 77 | return SDL_IsJoystickHoriSteamController(vendor_id, product_id); | ||
| 78 | } | ||
| 79 | |||
| 80 | static bool HIDAPI_DriverSteamHori_InitDevice(SDL_HIDAPI_Device *device) | ||
| 81 | { | ||
| 82 | SDL_DriverSteamHori_Context *ctx; | ||
| 83 | |||
| 84 | ctx = (SDL_DriverSteamHori_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 85 | if (!ctx) { | ||
| 86 | return false; | ||
| 87 | } | ||
| 88 | |||
| 89 | device->context = ctx; | ||
| 90 | ctx->serial_needs_init = true; | ||
| 91 | |||
| 92 | HIDAPI_SetDeviceName(device, "Wireless HORIPAD For Steam"); | ||
| 93 | |||
| 94 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 95 | } | ||
| 96 | |||
| 97 | static int HIDAPI_DriverSteamHori_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 98 | { | ||
| 99 | return -1; | ||
| 100 | } | ||
| 101 | |||
| 102 | static void HIDAPI_DriverSteamHori_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 103 | { | ||
| 104 | } | ||
| 105 | |||
| 106 | static bool HIDAPI_DriverSteamHori_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 107 | { | ||
| 108 | SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context; | ||
| 109 | |||
| 110 | SDL_AssertJoysticksLocked(); | ||
| 111 | |||
| 112 | SDL_zeroa(ctx->last_state); | ||
| 113 | |||
| 114 | /* Initialize the joystick capabilities */ | ||
| 115 | joystick->nbuttons = SDL_GAMEPAD_NUM_HORI_BUTTONS; | ||
| 116 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 117 | joystick->nhats = 1; | ||
| 118 | |||
| 119 | ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT; | ||
| 120 | |||
| 121 | if (ctx->wireless && device->serial) { | ||
| 122 | joystick->serial = SDL_strdup(device->serial); | ||
| 123 | ctx->serial_needs_init = false; | ||
| 124 | } else if (!ctx->wireless) { | ||
| 125 | // Need to actual read from the device to init the serial | ||
| 126 | HIDAPI_DriverSteamHori_UpdateDevice(device); | ||
| 127 | } | ||
| 128 | |||
| 129 | SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); | ||
| 130 | SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); | ||
| 131 | |||
| 132 | return true; | ||
| 133 | } | ||
| 134 | |||
| 135 | static bool HIDAPI_DriverSteamHori_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 136 | { | ||
| 137 | // Device doesn't support rumble | ||
| 138 | return SDL_Unsupported(); | ||
| 139 | } | ||
| 140 | |||
| 141 | static bool HIDAPI_DriverSteamHori_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 142 | { | ||
| 143 | return SDL_Unsupported(); | ||
| 144 | } | ||
| 145 | |||
| 146 | static Uint32 HIDAPI_DriverSteamHori_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 147 | { | ||
| 148 | return 0; | ||
| 149 | } | ||
| 150 | |||
| 151 | static bool HIDAPI_DriverSteamHori_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 152 | { | ||
| 153 | return SDL_Unsupported(); | ||
| 154 | } | ||
| 155 | |||
| 156 | static bool HIDAPI_DriverSteamHori_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 157 | { | ||
| 158 | return SDL_Unsupported(); | ||
| 159 | } | ||
| 160 | |||
| 161 | static bool HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 162 | { | ||
| 163 | return true; | ||
| 164 | } | ||
| 165 | |||
| 166 | #undef clamp | ||
| 167 | #define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) | ||
| 168 | |||
| 169 | #ifndef DEG2RAD | ||
| 170 | #define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) | ||
| 171 | #endif | ||
| 172 | |||
| 173 | //--------------------------------------------------------------------------- | ||
| 174 | // Scale and clamp values to a range | ||
| 175 | //--------------------------------------------------------------------------- | ||
| 176 | static float RemapValClamped(float val, float A, float B, float C, float D) | ||
| 177 | { | ||
| 178 | if (A == B) { | ||
| 179 | return (val - B) >= 0.0f ? D : C; | ||
| 180 | } else { | ||
| 181 | float cVal = (val - A) / (B - A); | ||
| 182 | cVal = clamp(cVal, 0.0f, 1.0f); | ||
| 183 | |||
| 184 | return C + (D - C) * cVal; | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | #define REPORT_HEADER_USB 0x07 | ||
| 189 | #define REPORT_HEADER_BT 0x00 | ||
| 190 | |||
| 191 | static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSteamHori_Context *ctx, Uint8 *data, int size) | ||
| 192 | { | ||
| 193 | Sint16 axis; | ||
| 194 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 195 | |||
| 196 | // Make sure it's gamepad state and not OTA FW update info | ||
| 197 | if (data[0] != REPORT_HEADER_USB && data[0] != REPORT_HEADER_BT) { | ||
| 198 | /* We don't know how to handle this report */ | ||
| 199 | return; | ||
| 200 | } | ||
| 201 | |||
| 202 | #define READ_STICK_AXIS(offset) \ | ||
| 203 | (data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), -0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16)) | ||
| 204 | { | ||
| 205 | axis = READ_STICK_AXIS(1); | ||
| 206 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 207 | axis = READ_STICK_AXIS(2); | ||
| 208 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 209 | axis = READ_STICK_AXIS(3); | ||
| 210 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 211 | axis = READ_STICK_AXIS(4); | ||
| 212 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 213 | } | ||
| 214 | #undef READ_STICK_AXIS | ||
| 215 | |||
| 216 | if (ctx->last_state[5] != data[5]) { | ||
| 217 | Uint8 hat; | ||
| 218 | |||
| 219 | switch (data[5] & 0xF) { | ||
| 220 | case 0: | ||
| 221 | hat = SDL_HAT_UP; | ||
| 222 | break; | ||
| 223 | case 1: | ||
| 224 | hat = SDL_HAT_RIGHTUP; | ||
| 225 | break; | ||
| 226 | case 2: | ||
| 227 | hat = SDL_HAT_RIGHT; | ||
| 228 | break; | ||
| 229 | case 3: | ||
| 230 | hat = SDL_HAT_RIGHTDOWN; | ||
| 231 | break; | ||
| 232 | case 4: | ||
| 233 | hat = SDL_HAT_DOWN; | ||
| 234 | break; | ||
| 235 | case 5: | ||
| 236 | hat = SDL_HAT_LEFTDOWN; | ||
| 237 | break; | ||
| 238 | case 6: | ||
| 239 | hat = SDL_HAT_LEFT; | ||
| 240 | break; | ||
| 241 | case 7: | ||
| 242 | hat = SDL_HAT_LEFTUP; | ||
| 243 | break; | ||
| 244 | default: | ||
| 245 | hat = SDL_HAT_CENTERED; | ||
| 246 | break; | ||
| 247 | } | ||
| 248 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 249 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x10) != 0)); | ||
| 250 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x20) != 0)); | ||
| 251 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_QAM, ((data[5] & 0x40) != 0)); | ||
| 252 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x80) != 0)); | ||
| 253 | |||
| 254 | } | ||
| 255 | |||
| 256 | if (ctx->last_state[6] != data[6]) { | ||
| 257 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[6] & 0x01) != 0)); | ||
| 258 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M1 /* M1 */, ((data[6] & 0x02) != 0)); | ||
| 259 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[6] & 0x04) != 0)); | ||
| 260 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[6] & 0x08) != 0)); | ||
| 261 | |||
| 262 | // TODO: can we handle the digital trigger mode? The data seems to come through analog regardless of the trigger state | ||
| 263 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[6] & 0x40) != 0)); | ||
| 264 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x80) != 0)); | ||
| 265 | } | ||
| 266 | |||
| 267 | if (ctx->last_state[7] != data[7]) { | ||
| 268 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[7] & 0x01) != 0)); | ||
| 269 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[7] & 0x02) != 0)); | ||
| 270 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[7] & 0x04) != 0)); | ||
| 271 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M2, ((data[7] & 0x08) != 0)); | ||
| 272 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, ((data[7] & 0x10) != 0)); | ||
| 273 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, ((data[7] & 0x20) != 0)); | ||
| 274 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FR, ((data[7] & 0x40) != 0)); | ||
| 275 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0)); | ||
| 276 | } | ||
| 277 | |||
| 278 | if (!ctx->wireless && ctx->serial_needs_init) { | ||
| 279 | char serial[18]; | ||
| 280 | (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", | ||
| 281 | data[38], data[39], data[40], data[41], data[42], data[43]); | ||
| 282 | |||
| 283 | joystick->serial = SDL_strdup(serial); | ||
| 284 | ctx->serial_needs_init = false; | ||
| 285 | } | ||
| 286 | |||
| 287 | #define READ_TRIGGER_AXIS(offset) \ | ||
| 288 | (Sint16)(((int)data[offset] * 257) - 32768) | ||
| 289 | { | ||
| 290 | axis = READ_TRIGGER_AXIS(8); | ||
| 291 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 292 | axis = READ_TRIGGER_AXIS(9); | ||
| 293 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 294 | } | ||
| 295 | #undef READ_TRIGGER_AXIS | ||
| 296 | |||
| 297 | if (1) { | ||
| 298 | Uint64 sensor_timestamp; | ||
| 299 | float imu_data[3]; | ||
| 300 | |||
| 301 | /* 16-bit timestamp */ | ||
| 302 | Uint32 delta; | ||
| 303 | Uint16 tick = LOAD16(data[10], | ||
| 304 | data[11]); | ||
| 305 | if (ctx->last_tick < tick) { | ||
| 306 | delta = (tick - ctx->last_tick); | ||
| 307 | } else { | ||
| 308 | delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1); | ||
| 309 | } | ||
| 310 | |||
| 311 | ctx->last_tick = tick; | ||
| 312 | ctx->sensor_ticks += delta; | ||
| 313 | |||
| 314 | /* Sensor timestamp is in 1us units, but there seems to be some issues with the values reported from the device */ | ||
| 315 | sensor_timestamp = timestamp; // if the values were good we woudl call SDL_US_TO_NS(ctx->sensor_ticks); | ||
| 316 | |||
| 317 | const float accelScale = SDL_STANDARD_GRAVITY * 8 / 32768.0f; | ||
| 318 | const float gyroScale = DEG2RAD(2048); | ||
| 319 | |||
| 320 | imu_data[1] = RemapValClamped(-1.0f * LOAD16(data[12], data[13]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); | ||
| 321 | imu_data[2] = RemapValClamped(-1.0f * LOAD16(data[14], data[15]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); | ||
| 322 | imu_data[0] = RemapValClamped(-1.0f * LOAD16(data[16], data[17]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); | ||
| 323 | |||
| 324 | |||
| 325 | SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, imu_data, 3); | ||
| 326 | |||
| 327 | // SDL_Log("%u %f, %f, %f ", data[0], imu_data[0], imu_data[1], imu_data[2] ); | ||
| 328 | imu_data[2] = LOAD16(data[18], data[19]) * accelScale; | ||
| 329 | imu_data[1] = -1 * LOAD16(data[20], data[21]) * accelScale; | ||
| 330 | imu_data[0] = LOAD16(data[22], data[23]) * accelScale; | ||
| 331 | SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, imu_data, 3); | ||
| 332 | } | ||
| 333 | |||
| 334 | if (ctx->last_state[24] != data[24]) { | ||
| 335 | bool bCharging = (data[24] & 0x10) != 0; | ||
| 336 | int percent = (data[24] & 0xF) * 10; | ||
| 337 | SDL_PowerState state; | ||
| 338 | if (bCharging) { | ||
| 339 | state = SDL_POWERSTATE_CHARGING; | ||
| 340 | } else if (ctx->wireless) { | ||
| 341 | state = SDL_POWERSTATE_ON_BATTERY; | ||
| 342 | } else { | ||
| 343 | state = SDL_POWERSTATE_CHARGED; | ||
| 344 | } | ||
| 345 | |||
| 346 | SDL_SendJoystickPowerInfo(joystick, state, percent); | ||
| 347 | } | ||
| 348 | |||
| 349 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 350 | } | ||
| 351 | |||
| 352 | static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 353 | { | ||
| 354 | SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context; | ||
| 355 | SDL_Joystick *joystick = NULL; | ||
| 356 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 357 | int size = 0; | ||
| 358 | |||
| 359 | if (device->num_joysticks > 0) { | ||
| 360 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 361 | } else { | ||
| 362 | return false; | ||
| 363 | } | ||
| 364 | |||
| 365 | while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { | ||
| 366 | #ifdef DEBUG_HORI_PROTOCOL | ||
| 367 | HIDAPI_DumpPacket("Google Hori packet: size = %d", data, size); | ||
| 368 | #endif | ||
| 369 | if (!joystick) { | ||
| 370 | continue; | ||
| 371 | } | ||
| 372 | |||
| 373 | HIDAPI_DriverSteamHori_HandleStatePacket(joystick, ctx, data, size); | ||
| 374 | } | ||
| 375 | |||
| 376 | if (size < 0) { | ||
| 377 | /* Read error, device is disconnected */ | ||
| 378 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 379 | } | ||
| 380 | return (size >= 0); | ||
| 381 | } | ||
| 382 | |||
| 383 | static void HIDAPI_DriverSteamHori_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 384 | { | ||
| 385 | } | ||
| 386 | |||
| 387 | static void HIDAPI_DriverSteamHori_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 388 | { | ||
| 389 | } | ||
| 390 | |||
| 391 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori = { | ||
| 392 | SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, | ||
| 393 | true, | ||
| 394 | HIDAPI_DriverSteamHori_RegisterHints, | ||
| 395 | HIDAPI_DriverSteamHori_UnregisterHints, | ||
| 396 | HIDAPI_DriverSteamHori_IsEnabled, | ||
| 397 | HIDAPI_DriverSteamHori_IsSupportedDevice, | ||
| 398 | HIDAPI_DriverSteamHori_InitDevice, | ||
| 399 | HIDAPI_DriverSteamHori_GetDevicePlayerIndex, | ||
| 400 | HIDAPI_DriverSteamHori_SetDevicePlayerIndex, | ||
| 401 | HIDAPI_DriverSteamHori_UpdateDevice, | ||
| 402 | HIDAPI_DriverSteamHori_OpenJoystick, | ||
| 403 | HIDAPI_DriverSteamHori_RumbleJoystick, | ||
| 404 | HIDAPI_DriverSteamHori_RumbleJoystickTriggers, | ||
| 405 | HIDAPI_DriverSteamHori_GetJoystickCapabilities, | ||
| 406 | HIDAPI_DriverSteamHori_SetJoystickLED, | ||
| 407 | HIDAPI_DriverSteamHori_SendJoystickEffect, | ||
| 408 | HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled, | ||
| 409 | HIDAPI_DriverSteamHori_CloseJoystick, | ||
| 410 | HIDAPI_DriverSteamHori_FreeDevice, | ||
| 411 | }; | ||
| 412 | |||
| 413 | #endif /* SDL_JOYSTICK_HIDAPI_STEAM_HORI */ | ||
| 414 | |||
| 415 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 2023 Max Maisel <max.maisel@posteo.de> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../SDL_sysjoystick.h" | ||
| 26 | #include "SDL_hidapijoystick_c.h" | ||
| 27 | |||
| 28 | #ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK | ||
| 29 | |||
| 30 | /*****************************************************************************************************/ | ||
| 31 | |||
| 32 | #include "steam/controller_constants.h" | ||
| 33 | #include "steam/controller_structs.h" | ||
| 34 | |||
| 35 | enum | ||
| 36 | { | ||
| 37 | SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM = 11, | ||
| 38 | SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1, | ||
| 39 | SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1, | ||
| 40 | SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2, | ||
| 41 | SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2, | ||
| 42 | SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS, | ||
| 43 | }; | ||
| 44 | |||
| 45 | typedef enum | ||
| 46 | { | ||
| 47 | STEAMDECK_LBUTTON_R2 = 0x00000001, | ||
| 48 | STEAMDECK_LBUTTON_L2 = 0x00000002, | ||
| 49 | STEAMDECK_LBUTTON_R = 0x00000004, | ||
| 50 | STEAMDECK_LBUTTON_L = 0x00000008, | ||
| 51 | STEAMDECK_LBUTTON_Y = 0x00000010, | ||
| 52 | STEAMDECK_LBUTTON_B = 0x00000020, | ||
| 53 | STEAMDECK_LBUTTON_X = 0x00000040, | ||
| 54 | STEAMDECK_LBUTTON_A = 0x00000080, | ||
| 55 | STEAMDECK_LBUTTON_DPAD_UP = 0x00000100, | ||
| 56 | STEAMDECK_LBUTTON_DPAD_RIGHT = 0x00000200, | ||
| 57 | STEAMDECK_LBUTTON_DPAD_LEFT = 0x00000400, | ||
| 58 | STEAMDECK_LBUTTON_DPAD_DOWN = 0x00000800, | ||
| 59 | STEAMDECK_LBUTTON_VIEW = 0x00001000, | ||
| 60 | STEAMDECK_LBUTTON_STEAM = 0x00002000, | ||
| 61 | STEAMDECK_LBUTTON_MENU = 0x00004000, | ||
| 62 | STEAMDECK_LBUTTON_L5 = 0x00008000, | ||
| 63 | STEAMDECK_LBUTTON_R5 = 0x00010000, | ||
| 64 | STEAMDECK_LBUTTON_LEFT_PAD = 0x00020000, | ||
| 65 | STEAMDECK_LBUTTON_RIGHT_PAD = 0x00040000, | ||
| 66 | STEAMDECK_LBUTTON_L3 = 0x00400000, | ||
| 67 | STEAMDECK_LBUTTON_R3 = 0x04000000, | ||
| 68 | |||
| 69 | STEAMDECK_HBUTTON_L4 = 0x00000200, | ||
| 70 | STEAMDECK_HBUTTON_R4 = 0x00000400, | ||
| 71 | STEAMDECK_HBUTTON_QAM = 0x00040000, | ||
| 72 | } SteamDeckButtons; | ||
| 73 | |||
| 74 | typedef struct | ||
| 75 | { | ||
| 76 | Uint32 update_rate_us; | ||
| 77 | Uint32 sensor_timestamp_us; | ||
| 78 | Uint64 last_button_state; | ||
| 79 | Uint8 watchdog_counter; | ||
| 80 | } SDL_DriverSteamDeck_Context; | ||
| 81 | |||
| 82 | static bool DisableDeckLizardMode(SDL_hid_device *dev) | ||
| 83 | { | ||
| 84 | int rc; | ||
| 85 | Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 }; | ||
| 86 | FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); | ||
| 87 | |||
| 88 | msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS; | ||
| 89 | |||
| 90 | rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); | ||
| 91 | if (rc != sizeof(buffer)) | ||
| 92 | return false; | ||
| 93 | |||
| 94 | msg->header.type = ID_SET_SETTINGS_VALUES; | ||
| 95 | msg->header.length = 5 * sizeof(ControllerSetting); | ||
| 96 | msg->payload.setSettingsValues.settings[0].settingNum = SETTING_SMOOTH_ABSOLUTE_MOUSE; | ||
| 97 | msg->payload.setSettingsValues.settings[0].settingValue = 0; | ||
| 98 | msg->payload.setSettingsValues.settings[1].settingNum = SETTING_LEFT_TRACKPAD_MODE; | ||
| 99 | msg->payload.setSettingsValues.settings[1].settingValue = TRACKPAD_NONE; | ||
| 100 | msg->payload.setSettingsValues.settings[2].settingNum = SETTING_RIGHT_TRACKPAD_MODE; // disable mouse | ||
| 101 | msg->payload.setSettingsValues.settings[2].settingValue = TRACKPAD_NONE; | ||
| 102 | msg->payload.setSettingsValues.settings[3].settingNum = SETTING_LEFT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad | ||
| 103 | msg->payload.setSettingsValues.settings[3].settingValue = 0xFFFF; | ||
| 104 | msg->payload.setSettingsValues.settings[4].settingNum = SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad | ||
| 105 | msg->payload.setSettingsValues.settings[4].settingValue = 0xFFFF; | ||
| 106 | |||
| 107 | rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); | ||
| 108 | if (rc != sizeof(buffer)) | ||
| 109 | return false; | ||
| 110 | |||
| 111 | // There may be a lingering report read back after changing settings. | ||
| 112 | // Discard it. | ||
| 113 | SDL_hid_get_feature_report(dev, buffer, sizeof(buffer)); | ||
| 114 | |||
| 115 | return true; | ||
| 116 | } | ||
| 117 | |||
| 118 | static bool FeedDeckLizardWatchdog(SDL_hid_device *dev) | ||
| 119 | { | ||
| 120 | int rc; | ||
| 121 | Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 }; | ||
| 122 | FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); | ||
| 123 | |||
| 124 | msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS; | ||
| 125 | |||
| 126 | rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); | ||
| 127 | if (rc != sizeof(buffer)) | ||
| 128 | return false; | ||
| 129 | |||
| 130 | msg->header.type = ID_SET_SETTINGS_VALUES; | ||
| 131 | msg->header.length = 1 * sizeof(ControllerSetting); | ||
| 132 | msg->payload.setSettingsValues.settings[0].settingNum = SETTING_RIGHT_TRACKPAD_MODE; | ||
| 133 | msg->payload.setSettingsValues.settings[0].settingValue = TRACKPAD_NONE; | ||
| 134 | |||
| 135 | rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer)); | ||
| 136 | if (rc != sizeof(buffer)) | ||
| 137 | return false; | ||
| 138 | |||
| 139 | // There may be a lingering report read back after changing settings. | ||
| 140 | // Discard it. | ||
| 141 | SDL_hid_get_feature_report(dev, buffer, sizeof(buffer)); | ||
| 142 | |||
| 143 | return true; | ||
| 144 | } | ||
| 145 | |||
| 146 | static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device, | ||
| 147 | SDL_Joystick *joystick, | ||
| 148 | ValveInReport_t *pInReport) | ||
| 149 | { | ||
| 150 | float values[3]; | ||
| 151 | SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context; | ||
| 152 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 153 | |||
| 154 | if (pInReport->payload.deckState.ulButtons != ctx->last_button_state) { | ||
| 155 | Uint8 hat = 0; | ||
| 156 | |||
| 157 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, | ||
| 158 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_A) != 0)); | ||
| 159 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, | ||
| 160 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_B) != 0)); | ||
| 161 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, | ||
| 162 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_X) != 0)); | ||
| 163 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, | ||
| 164 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_Y) != 0)); | ||
| 165 | |||
| 166 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, | ||
| 167 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L) != 0)); | ||
| 168 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, | ||
| 169 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R) != 0)); | ||
| 170 | |||
| 171 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, | ||
| 172 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_VIEW) != 0)); | ||
| 173 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, | ||
| 174 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_MENU) != 0)); | ||
| 175 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, | ||
| 176 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_STEAM) != 0)); | ||
| 177 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM, | ||
| 178 | ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_QAM) != 0)); | ||
| 179 | |||
| 180 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, | ||
| 181 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L3) != 0)); | ||
| 182 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, | ||
| 183 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R3) != 0)); | ||
| 184 | |||
| 185 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1, | ||
| 186 | ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_R4) != 0)); | ||
| 187 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1, | ||
| 188 | ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_L4) != 0)); | ||
| 189 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2, | ||
| 190 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R5) != 0)); | ||
| 191 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2, | ||
| 192 | ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L5) != 0)); | ||
| 193 | |||
| 194 | if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_UP) { | ||
| 195 | hat |= SDL_HAT_UP; | ||
| 196 | } | ||
| 197 | if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_DOWN) { | ||
| 198 | hat |= SDL_HAT_DOWN; | ||
| 199 | } | ||
| 200 | if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_LEFT) { | ||
| 201 | hat |= SDL_HAT_LEFT; | ||
| 202 | } | ||
| 203 | if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_RIGHT) { | ||
| 204 | hat |= SDL_HAT_RIGHT; | ||
| 205 | } | ||
| 206 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 207 | |||
| 208 | ctx->last_button_state = pInReport->payload.deckState.ulButtons; | ||
| 209 | } | ||
| 210 | |||
| 211 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, | ||
| 212 | (int)pInReport->payload.deckState.sTriggerRawL * 2 - 32768); | ||
| 213 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, | ||
| 214 | (int)pInReport->payload.deckState.sTriggerRawR * 2 - 32768); | ||
| 215 | |||
| 216 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, | ||
| 217 | pInReport->payload.deckState.sLeftStickX); | ||
| 218 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, | ||
| 219 | -pInReport->payload.deckState.sLeftStickY); | ||
| 220 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, | ||
| 221 | pInReport->payload.deckState.sRightStickX); | ||
| 222 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, | ||
| 223 | -pInReport->payload.deckState.sRightStickY); | ||
| 224 | |||
| 225 | ctx->sensor_timestamp_us += ctx->update_rate_us; | ||
| 226 | |||
| 227 | values[0] = (pInReport->payload.deckState.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); | ||
| 228 | values[1] = (pInReport->payload.deckState.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); | ||
| 229 | values[2] = (-pInReport->payload.deckState.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f)); | ||
| 230 | SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_us, values, 3); | ||
| 231 | |||
| 232 | values[0] = (pInReport->payload.deckState.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; | ||
| 233 | values[1] = (pInReport->payload.deckState.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; | ||
| 234 | values[2] = (-pInReport->payload.deckState.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; | ||
| 235 | SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_us, values, 3); | ||
| 236 | } | ||
| 237 | |||
| 238 | /*****************************************************************************************************/ | ||
| 239 | |||
| 240 | static void HIDAPI_DriverSteamDeck_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 241 | { | ||
| 242 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata); | ||
| 243 | } | ||
| 244 | |||
| 245 | static void HIDAPI_DriverSteamDeck_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 246 | { | ||
| 247 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata); | ||
| 248 | } | ||
| 249 | |||
| 250 | static bool HIDAPI_DriverSteamDeck_IsEnabled(void) | ||
| 251 | { | ||
| 252 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, | ||
| 253 | SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); | ||
| 254 | } | ||
| 255 | |||
| 256 | static bool HIDAPI_DriverSteamDeck_IsSupportedDevice( | ||
| 257 | SDL_HIDAPI_Device *device, | ||
| 258 | const char *name, | ||
| 259 | SDL_GamepadType type, | ||
| 260 | Uint16 vendor_id, | ||
| 261 | Uint16 product_id, | ||
| 262 | Uint16 version, | ||
| 263 | int interface_number, | ||
| 264 | int interface_class, | ||
| 265 | int interface_subclass, | ||
| 266 | int interface_protocol) | ||
| 267 | { | ||
| 268 | return SDL_IsJoystickSteamDeck(vendor_id, product_id); | ||
| 269 | } | ||
| 270 | |||
| 271 | static bool HIDAPI_DriverSteamDeck_InitDevice(SDL_HIDAPI_Device *device) | ||
| 272 | { | ||
| 273 | int size; | ||
| 274 | Uint8 data[64]; | ||
| 275 | SDL_DriverSteamDeck_Context *ctx; | ||
| 276 | |||
| 277 | ctx = (SDL_DriverSteamDeck_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 278 | if (ctx == NULL) { | ||
| 279 | return false; | ||
| 280 | } | ||
| 281 | |||
| 282 | // Always 1kHz according to USB descriptor, but actually about 4 ms. | ||
| 283 | ctx->update_rate_us = 4000; | ||
| 284 | |||
| 285 | device->context = ctx; | ||
| 286 | |||
| 287 | // Read a report to see if this is the correct endpoint. | ||
| 288 | // Mouse, Keyboard and Controller have the same VID/PID but | ||
| 289 | // only the controller hidraw device receives hid reports. | ||
| 290 | size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16); | ||
| 291 | if (size == 0) | ||
| 292 | return false; | ||
| 293 | |||
| 294 | if (!DisableDeckLizardMode(device->dev)) | ||
| 295 | return false; | ||
| 296 | |||
| 297 | HIDAPI_SetDeviceName(device, "Steam Deck"); | ||
| 298 | |||
| 299 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 300 | } | ||
| 301 | |||
| 302 | static int HIDAPI_DriverSteamDeck_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 303 | { | ||
| 304 | return -1; | ||
| 305 | } | ||
| 306 | |||
| 307 | static void HIDAPI_DriverSteamDeck_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 308 | { | ||
| 309 | } | ||
| 310 | |||
| 311 | static bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 312 | { | ||
| 313 | SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context; | ||
| 314 | SDL_Joystick *joystick = NULL; | ||
| 315 | int r; | ||
| 316 | uint8_t data[64]; | ||
| 317 | ValveInReport_t *pInReport = (ValveInReport_t *)data; | ||
| 318 | |||
| 319 | if (device->num_joysticks > 0) { | ||
| 320 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 321 | if (joystick == NULL) { | ||
| 322 | return false; | ||
| 323 | } | ||
| 324 | } else { | ||
| 325 | return false; | ||
| 326 | } | ||
| 327 | |||
| 328 | if (ctx->watchdog_counter++ > 200) { | ||
| 329 | ctx->watchdog_counter = 0; | ||
| 330 | if (!FeedDeckLizardWatchdog(device->dev)) | ||
| 331 | return false; | ||
| 332 | } | ||
| 333 | |||
| 334 | SDL_memset(data, 0, sizeof(data)); | ||
| 335 | |||
| 336 | do { | ||
| 337 | r = SDL_hid_read(device->dev, data, sizeof(data)); | ||
| 338 | |||
| 339 | if (r < 0) { | ||
| 340 | // Failed to read from controller | ||
| 341 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 342 | return false; | ||
| 343 | } else if (r == 64 && | ||
| 344 | pInReport->header.unReportVersion == k_ValveInReportMsgVersion && | ||
| 345 | pInReport->header.ucType == ID_CONTROLLER_DECK_STATE && | ||
| 346 | pInReport->header.ucLength == 64) { | ||
| 347 | HIDAPI_DriverSteamDeck_HandleState(device, joystick, pInReport); | ||
| 348 | } | ||
| 349 | } while (r > 0); | ||
| 350 | |||
| 351 | return true; | ||
| 352 | } | ||
| 353 | |||
| 354 | static bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 355 | { | ||
| 356 | SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context; | ||
| 357 | float update_rate_in_hz = 1.0f / (float)(ctx->update_rate_us) * 1.0e6f; | ||
| 358 | |||
| 359 | SDL_AssertJoysticksLocked(); | ||
| 360 | |||
| 361 | // Initialize the joystick capabilities | ||
| 362 | joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS; | ||
| 363 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 364 | joystick->nhats = 1; | ||
| 365 | |||
| 366 | SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz); | ||
| 367 | SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz); | ||
| 368 | |||
| 369 | return true; | ||
| 370 | } | ||
| 371 | |||
| 372 | static bool HIDAPI_DriverSteamDeck_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 373 | { | ||
| 374 | int rc; | ||
| 375 | Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 }; | ||
| 376 | FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1); | ||
| 377 | |||
| 378 | msg->header.type = ID_TRIGGER_RUMBLE_CMD; | ||
| 379 | msg->payload.simpleRumble.unRumbleType = 0; | ||
| 380 | msg->payload.simpleRumble.unIntensity = HAPTIC_INTENSITY_SYSTEM; | ||
| 381 | msg->payload.simpleRumble.unLeftMotorSpeed = low_frequency_rumble; | ||
| 382 | msg->payload.simpleRumble.unRightMotorSpeed = high_frequency_rumble; | ||
| 383 | msg->payload.simpleRumble.nLeftGain = 2; | ||
| 384 | msg->payload.simpleRumble.nRightGain = 0; | ||
| 385 | |||
| 386 | rc = SDL_hid_send_feature_report(device->dev, buffer, sizeof(buffer)); | ||
| 387 | if (rc != sizeof(buffer)) | ||
| 388 | return false; | ||
| 389 | return true; | ||
| 390 | } | ||
| 391 | |||
| 392 | static bool HIDAPI_DriverSteamDeck_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 393 | { | ||
| 394 | return SDL_Unsupported(); | ||
| 395 | } | ||
| 396 | |||
| 397 | static Uint32 HIDAPI_DriverSteamDeck_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 398 | { | ||
| 399 | return SDL_JOYSTICK_CAP_RUMBLE; | ||
| 400 | } | ||
| 401 | |||
| 402 | static bool HIDAPI_DriverSteamDeck_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 403 | { | ||
| 404 | return SDL_Unsupported(); | ||
| 405 | } | ||
| 406 | |||
| 407 | static bool HIDAPI_DriverSteamDeck_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 408 | { | ||
| 409 | return SDL_Unsupported(); | ||
| 410 | } | ||
| 411 | |||
| 412 | static bool HIDAPI_DriverSteamDeck_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 413 | { | ||
| 414 | // On steam deck, sensors are enabled by default. Nothing to do here. | ||
| 415 | return true; | ||
| 416 | } | ||
| 417 | |||
| 418 | static void HIDAPI_DriverSteamDeck_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 419 | { | ||
| 420 | // Lizard mode id automatically re-enabled by watchdog. Nothing to do here. | ||
| 421 | } | ||
| 422 | |||
| 423 | static void HIDAPI_DriverSteamDeck_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 424 | { | ||
| 425 | } | ||
| 426 | |||
| 427 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck = { | ||
| 428 | SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, | ||
| 429 | true, | ||
| 430 | HIDAPI_DriverSteamDeck_RegisterHints, | ||
| 431 | HIDAPI_DriverSteamDeck_UnregisterHints, | ||
| 432 | HIDAPI_DriverSteamDeck_IsEnabled, | ||
| 433 | HIDAPI_DriverSteamDeck_IsSupportedDevice, | ||
| 434 | HIDAPI_DriverSteamDeck_InitDevice, | ||
| 435 | HIDAPI_DriverSteamDeck_GetDevicePlayerIndex, | ||
| 436 | HIDAPI_DriverSteamDeck_SetDevicePlayerIndex, | ||
| 437 | HIDAPI_DriverSteamDeck_UpdateDevice, | ||
| 438 | HIDAPI_DriverSteamDeck_OpenJoystick, | ||
| 439 | HIDAPI_DriverSteamDeck_RumbleJoystick, | ||
| 440 | HIDAPI_DriverSteamDeck_RumbleJoystickTriggers, | ||
| 441 | HIDAPI_DriverSteamDeck_GetJoystickCapabilities, | ||
| 442 | HIDAPI_DriverSteamDeck_SetJoystickLED, | ||
| 443 | HIDAPI_DriverSteamDeck_SendJoystickEffect, | ||
| 444 | HIDAPI_DriverSteamDeck_SetSensorsEnabled, | ||
| 445 | HIDAPI_DriverSteamDeck_CloseJoystick, | ||
| 446 | HIDAPI_DriverSteamDeck_FreeDevice, | ||
| 447 | }; | ||
| 448 | |||
| 449 | #endif // SDL_JOYSTICK_HIDAPI_STEAMDECK | ||
| 450 | |||
| 451 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | /* This driver supports the Nintendo Switch Pro controller. | ||
| 22 | Code and logic contributed by Valve Corporation under the SDL zlib license. | ||
| 23 | */ | ||
| 24 | #include "SDL_internal.h" | ||
| 25 | |||
| 26 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 27 | |||
| 28 | #include "../../SDL_hints_c.h" | ||
| 29 | #include "../SDL_sysjoystick.h" | ||
| 30 | #include "SDL_hidapijoystick_c.h" | ||
| 31 | #include "SDL_hidapi_rumble.h" | ||
| 32 | #include "SDL_hidapi_nintendo.h" | ||
| 33 | |||
| 34 | #ifdef SDL_JOYSTICK_HIDAPI_SWITCH | ||
| 35 | |||
| 36 | // Define this if you want to log all packets from the controller | ||
| 37 | // #define DEBUG_SWITCH_PROTOCOL | ||
| 38 | |||
| 39 | // Define this to get log output for rumble logic | ||
| 40 | // #define DEBUG_RUMBLE | ||
| 41 | |||
| 42 | /* The initialization sequence doesn't appear to work correctly on Windows unless | ||
| 43 | the reads and writes are on the same thread. | ||
| 44 | |||
| 45 | ... and now I can't reproduce this, so I'm leaving it in, but disabled for now. | ||
| 46 | */ | ||
| 47 | // #define SWITCH_SYNCHRONOUS_WRITES | ||
| 48 | |||
| 49 | /* How often you can write rumble commands to the controller. | ||
| 50 | If you send commands more frequently than this, you can turn off the controller | ||
| 51 | in Bluetooth mode, or the motors can miss the command in USB mode. | ||
| 52 | */ | ||
| 53 | #define RUMBLE_WRITE_FREQUENCY_MS 30 | ||
| 54 | |||
| 55 | // How often you have to refresh a long duration rumble to keep the motors running | ||
| 56 | #define RUMBLE_REFRESH_FREQUENCY_MS 50 | ||
| 57 | |||
| 58 | #define SWITCH_GYRO_SCALE 14.2842f | ||
| 59 | #define SWITCH_ACCEL_SCALE 4096.f | ||
| 60 | |||
| 61 | #define SWITCH_GYRO_SCALE_OFFSET 13371.0f | ||
| 62 | #define SWITCH_GYRO_SCALE_MULT 936.0f | ||
| 63 | #define SWITCH_ACCEL_SCALE_OFFSET 16384.0f | ||
| 64 | #define SWITCH_ACCEL_SCALE_MULT 4.0f | ||
| 65 | |||
| 66 | enum | ||
| 67 | { | ||
| 68 | SDL_GAMEPAD_BUTTON_SWITCH_SHARE = 11, | ||
| 69 | SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, | ||
| 70 | SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, | ||
| 71 | SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, | ||
| 72 | SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, | ||
| 73 | SDL_GAMEPAD_NUM_SWITCH_BUTTONS, | ||
| 74 | }; | ||
| 75 | |||
| 76 | typedef enum | ||
| 77 | { | ||
| 78 | k_eSwitchInputReportIDs_SubcommandReply = 0x21, | ||
| 79 | k_eSwitchInputReportIDs_FullControllerState = 0x30, | ||
| 80 | k_eSwitchInputReportIDs_FullControllerAndMcuState = 0x31, | ||
| 81 | k_eSwitchInputReportIDs_SimpleControllerState = 0x3F, | ||
| 82 | k_eSwitchInputReportIDs_CommandAck = 0x81, | ||
| 83 | } ESwitchInputReportIDs; | ||
| 84 | |||
| 85 | typedef enum | ||
| 86 | { | ||
| 87 | k_eSwitchOutputReportIDs_RumbleAndSubcommand = 0x01, | ||
| 88 | k_eSwitchOutputReportIDs_Rumble = 0x10, | ||
| 89 | k_eSwitchOutputReportIDs_Proprietary = 0x80, | ||
| 90 | } ESwitchOutputReportIDs; | ||
| 91 | |||
| 92 | typedef enum | ||
| 93 | { | ||
| 94 | k_eSwitchSubcommandIDs_BluetoothManualPair = 0x01, | ||
| 95 | k_eSwitchSubcommandIDs_RequestDeviceInfo = 0x02, | ||
| 96 | k_eSwitchSubcommandIDs_SetInputReportMode = 0x03, | ||
| 97 | k_eSwitchSubcommandIDs_SetHCIState = 0x06, | ||
| 98 | k_eSwitchSubcommandIDs_SPIFlashRead = 0x10, | ||
| 99 | k_eSwitchSubcommandIDs_SetPlayerLights = 0x30, | ||
| 100 | k_eSwitchSubcommandIDs_SetHomeLight = 0x38, | ||
| 101 | k_eSwitchSubcommandIDs_EnableIMU = 0x40, | ||
| 102 | k_eSwitchSubcommandIDs_SetIMUSensitivity = 0x41, | ||
| 103 | k_eSwitchSubcommandIDs_EnableVibration = 0x48, | ||
| 104 | } ESwitchSubcommandIDs; | ||
| 105 | |||
| 106 | typedef enum | ||
| 107 | { | ||
| 108 | k_eSwitchProprietaryCommandIDs_Status = 0x01, | ||
| 109 | k_eSwitchProprietaryCommandIDs_Handshake = 0x02, | ||
| 110 | k_eSwitchProprietaryCommandIDs_HighSpeed = 0x03, | ||
| 111 | k_eSwitchProprietaryCommandIDs_ForceUSB = 0x04, | ||
| 112 | k_eSwitchProprietaryCommandIDs_ClearUSB = 0x05, | ||
| 113 | k_eSwitchProprietaryCommandIDs_ResetMCU = 0x06, | ||
| 114 | } ESwitchProprietaryCommandIDs; | ||
| 115 | |||
| 116 | #define k_unSwitchOutputPacketDataLength 49 | ||
| 117 | #define k_unSwitchMaxOutputPacketLength 64 | ||
| 118 | #define k_unSwitchBluetoothPacketLength k_unSwitchOutputPacketDataLength | ||
| 119 | #define k_unSwitchUSBPacketLength k_unSwitchMaxOutputPacketLength | ||
| 120 | |||
| 121 | #define k_unSPIStickFactoryCalibrationStartOffset 0x603D | ||
| 122 | #define k_unSPIStickFactoryCalibrationEndOffset 0x604E | ||
| 123 | #define k_unSPIStickFactoryCalibrationLength (k_unSPIStickFactoryCalibrationEndOffset - k_unSPIStickFactoryCalibrationStartOffset + 1) | ||
| 124 | |||
| 125 | #define k_unSPIStickUserCalibrationStartOffset 0x8010 | ||
| 126 | #define k_unSPIStickUserCalibrationEndOffset 0x8025 | ||
| 127 | #define k_unSPIStickUserCalibrationLength (k_unSPIStickUserCalibrationEndOffset - k_unSPIStickUserCalibrationStartOffset + 1) | ||
| 128 | |||
| 129 | #define k_unSPIIMUScaleStartOffset 0x6020 | ||
| 130 | #define k_unSPIIMUScaleEndOffset 0x6037 | ||
| 131 | #define k_unSPIIMUScaleLength (k_unSPIIMUScaleEndOffset - k_unSPIIMUScaleStartOffset + 1) | ||
| 132 | |||
| 133 | #define k_unSPIIMUUserScaleStartOffset 0x8026 | ||
| 134 | #define k_unSPIIMUUserScaleEndOffset 0x8039 | ||
| 135 | #define k_unSPIIMUUserScaleLength (k_unSPIIMUUserScaleEndOffset - k_unSPIIMUUserScaleStartOffset + 1) | ||
| 136 | |||
| 137 | #pragma pack(1) | ||
| 138 | typedef struct | ||
| 139 | { | ||
| 140 | Uint8 rgucButtons[2]; | ||
| 141 | Uint8 ucStickHat; | ||
| 142 | Uint8 rgucJoystickLeft[2]; | ||
| 143 | Uint8 rgucJoystickRight[2]; | ||
| 144 | } SwitchInputOnlyControllerStatePacket_t; | ||
| 145 | |||
| 146 | typedef struct | ||
| 147 | { | ||
| 148 | Uint8 rgucButtons[2]; | ||
| 149 | Uint8 ucStickHat; | ||
| 150 | Sint16 sJoystickLeft[2]; | ||
| 151 | Sint16 sJoystickRight[2]; | ||
| 152 | } SwitchSimpleStatePacket_t; | ||
| 153 | |||
| 154 | typedef struct | ||
| 155 | { | ||
| 156 | Uint8 ucCounter; | ||
| 157 | Uint8 ucBatteryAndConnection; | ||
| 158 | Uint8 rgucButtons[3]; | ||
| 159 | Uint8 rgucJoystickLeft[3]; | ||
| 160 | Uint8 rgucJoystickRight[3]; | ||
| 161 | Uint8 ucVibrationCode; | ||
| 162 | } SwitchControllerStatePacket_t; | ||
| 163 | |||
| 164 | typedef struct | ||
| 165 | { | ||
| 166 | SwitchControllerStatePacket_t controllerState; | ||
| 167 | |||
| 168 | struct | ||
| 169 | { | ||
| 170 | Sint16 sAccelX; | ||
| 171 | Sint16 sAccelY; | ||
| 172 | Sint16 sAccelZ; | ||
| 173 | |||
| 174 | Sint16 sGyroX; | ||
| 175 | Sint16 sGyroY; | ||
| 176 | Sint16 sGyroZ; | ||
| 177 | } imuState[3]; | ||
| 178 | } SwitchStatePacket_t; | ||
| 179 | |||
| 180 | typedef struct | ||
| 181 | { | ||
| 182 | Uint32 unAddress; | ||
| 183 | Uint8 ucLength; | ||
| 184 | } SwitchSPIOpData_t; | ||
| 185 | |||
| 186 | typedef struct | ||
| 187 | { | ||
| 188 | SwitchControllerStatePacket_t m_controllerState; | ||
| 189 | |||
| 190 | Uint8 ucSubcommandAck; | ||
| 191 | Uint8 ucSubcommandID; | ||
| 192 | |||
| 193 | #define k_unSubcommandDataBytes 35 | ||
| 194 | union | ||
| 195 | { | ||
| 196 | Uint8 rgucSubcommandData[k_unSubcommandDataBytes]; | ||
| 197 | |||
| 198 | struct | ||
| 199 | { | ||
| 200 | SwitchSPIOpData_t opData; | ||
| 201 | Uint8 rgucReadData[k_unSubcommandDataBytes - sizeof(SwitchSPIOpData_t)]; | ||
| 202 | } spiReadData; | ||
| 203 | |||
| 204 | struct | ||
| 205 | { | ||
| 206 | Uint8 rgucFirmwareVersion[2]; | ||
| 207 | Uint8 ucDeviceType; | ||
| 208 | Uint8 ucFiller1; | ||
| 209 | Uint8 rgucMACAddress[6]; | ||
| 210 | Uint8 ucFiller2; | ||
| 211 | Uint8 ucColorLocation; | ||
| 212 | } deviceInfo; | ||
| 213 | |||
| 214 | struct | ||
| 215 | { | ||
| 216 | SwitchSPIOpData_t opData; | ||
| 217 | Uint8 rgucLeftCalibration[9]; | ||
| 218 | Uint8 rgucRightCalibration[9]; | ||
| 219 | } stickFactoryCalibration; | ||
| 220 | |||
| 221 | struct | ||
| 222 | { | ||
| 223 | SwitchSPIOpData_t opData; | ||
| 224 | Uint8 rgucLeftMagic[2]; | ||
| 225 | Uint8 rgucLeftCalibration[9]; | ||
| 226 | Uint8 rgucRightMagic[2]; | ||
| 227 | Uint8 rgucRightCalibration[9]; | ||
| 228 | } stickUserCalibration; | ||
| 229 | }; | ||
| 230 | } SwitchSubcommandInputPacket_t; | ||
| 231 | |||
| 232 | typedef struct | ||
| 233 | { | ||
| 234 | Uint8 ucPacketType; | ||
| 235 | Uint8 ucCommandID; | ||
| 236 | Uint8 ucFiller; | ||
| 237 | |||
| 238 | Uint8 ucDeviceType; | ||
| 239 | Uint8 rgucMACAddress[6]; | ||
| 240 | } SwitchProprietaryStatusPacket_t; | ||
| 241 | |||
| 242 | typedef struct | ||
| 243 | { | ||
| 244 | Uint8 rgucData[4]; | ||
| 245 | } SwitchRumbleData_t; | ||
| 246 | |||
| 247 | typedef struct | ||
| 248 | { | ||
| 249 | Uint8 ucPacketType; | ||
| 250 | Uint8 ucPacketNumber; | ||
| 251 | SwitchRumbleData_t rumbleData[2]; | ||
| 252 | } SwitchCommonOutputPacket_t; | ||
| 253 | |||
| 254 | typedef struct | ||
| 255 | { | ||
| 256 | SwitchCommonOutputPacket_t commonData; | ||
| 257 | |||
| 258 | Uint8 ucSubcommandID; | ||
| 259 | Uint8 rgucSubcommandData[k_unSwitchOutputPacketDataLength - sizeof(SwitchCommonOutputPacket_t) - 1]; | ||
| 260 | } SwitchSubcommandOutputPacket_t; | ||
| 261 | |||
| 262 | typedef struct | ||
| 263 | { | ||
| 264 | Uint8 ucPacketType; | ||
| 265 | Uint8 ucProprietaryID; | ||
| 266 | |||
| 267 | Uint8 rgucProprietaryData[k_unSwitchOutputPacketDataLength - 1 - 1]; | ||
| 268 | } SwitchProprietaryOutputPacket_t; | ||
| 269 | #pragma pack() | ||
| 270 | |||
| 271 | /* Enhanced report hint mode: | ||
| 272 | * "0": enhanced features are never used | ||
| 273 | * "1": enhanced features are always used | ||
| 274 | * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it. | ||
| 275 | */ | ||
| 276 | typedef enum | ||
| 277 | { | ||
| 278 | SWITCH_ENHANCED_REPORT_HINT_OFF, | ||
| 279 | SWITCH_ENHANCED_REPORT_HINT_ON, | ||
| 280 | SWITCH_ENHANCED_REPORT_HINT_AUTO | ||
| 281 | } HIDAPI_Switch_EnhancedReportHint; | ||
| 282 | |||
| 283 | typedef struct | ||
| 284 | { | ||
| 285 | SDL_HIDAPI_Device *device; | ||
| 286 | SDL_Joystick *joystick; | ||
| 287 | bool m_bInputOnly; | ||
| 288 | bool m_bUseButtonLabels; | ||
| 289 | bool m_bPlayerLights; | ||
| 290 | int m_nPlayerIndex; | ||
| 291 | bool m_bSyncWrite; | ||
| 292 | int m_nMaxWriteAttempts; | ||
| 293 | ESwitchDeviceInfoControllerType m_eControllerType; | ||
| 294 | Uint8 m_nInitialInputMode; | ||
| 295 | Uint8 m_nCurrentInputMode; | ||
| 296 | Uint8 m_rgucMACAddress[6]; | ||
| 297 | Uint8 m_nCommandNumber; | ||
| 298 | HIDAPI_Switch_EnhancedReportHint m_eEnhancedReportHint; | ||
| 299 | bool m_bEnhancedMode; | ||
| 300 | bool m_bEnhancedModeAvailable; | ||
| 301 | SwitchCommonOutputPacket_t m_RumblePacket; | ||
| 302 | Uint8 m_rgucReadBuffer[k_unSwitchMaxOutputPacketLength]; | ||
| 303 | bool m_bRumbleActive; | ||
| 304 | Uint64 m_ulRumbleSent; | ||
| 305 | bool m_bRumblePending; | ||
| 306 | bool m_bRumbleZeroPending; | ||
| 307 | Uint32 m_unRumblePending; | ||
| 308 | bool m_bSensorsSupported; | ||
| 309 | bool m_bReportSensors; | ||
| 310 | bool m_bHasSensorData; | ||
| 311 | Uint64 m_ulLastInput; | ||
| 312 | Uint64 m_ulLastIMUReset; | ||
| 313 | Uint64 m_ulIMUSampleTimestampNS; | ||
| 314 | Uint32 m_unIMUSamples; | ||
| 315 | Uint64 m_ulIMUUpdateIntervalNS; | ||
| 316 | Uint64 m_ulTimestampNS; | ||
| 317 | bool m_bVerticalMode; | ||
| 318 | |||
| 319 | SwitchInputOnlyControllerStatePacket_t m_lastInputOnlyState; | ||
| 320 | SwitchSimpleStatePacket_t m_lastSimpleState; | ||
| 321 | SwitchStatePacket_t m_lastFullState; | ||
| 322 | |||
| 323 | struct StickCalibrationData | ||
| 324 | { | ||
| 325 | struct | ||
| 326 | { | ||
| 327 | Sint16 sCenter; | ||
| 328 | Sint16 sMin; | ||
| 329 | Sint16 sMax; | ||
| 330 | } axis[2]; | ||
| 331 | } m_StickCalData[2]; | ||
| 332 | |||
| 333 | struct StickExtents | ||
| 334 | { | ||
| 335 | struct | ||
| 336 | { | ||
| 337 | Sint16 sMin; | ||
| 338 | Sint16 sMax; | ||
| 339 | } axis[2]; | ||
| 340 | } m_StickExtents[2], m_SimpleStickExtents[2]; | ||
| 341 | |||
| 342 | struct IMUScaleData | ||
| 343 | { | ||
| 344 | float fAccelScaleX; | ||
| 345 | float fAccelScaleY; | ||
| 346 | float fAccelScaleZ; | ||
| 347 | |||
| 348 | float fGyroScaleX; | ||
| 349 | float fGyroScaleY; | ||
| 350 | float fGyroScaleZ; | ||
| 351 | } m_IMUScaleData; | ||
| 352 | } SDL_DriverSwitch_Context; | ||
| 353 | |||
| 354 | static int ReadInput(SDL_DriverSwitch_Context *ctx) | ||
| 355 | { | ||
| 356 | int result; | ||
| 357 | |||
| 358 | // Make sure we don't try to read at the same time a write is happening | ||
| 359 | if (SDL_GetAtomicInt(&ctx->device->rumble_pending) > 0) { | ||
| 360 | return 0; | ||
| 361 | } | ||
| 362 | |||
| 363 | result = SDL_hid_read_timeout(ctx->device->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0); | ||
| 364 | |||
| 365 | // See if we can guess the initial input mode | ||
| 366 | if (result > 0 && !ctx->m_bInputOnly && !ctx->m_nInitialInputMode) { | ||
| 367 | switch (ctx->m_rgucReadBuffer[0]) { | ||
| 368 | case k_eSwitchInputReportIDs_FullControllerState: | ||
| 369 | case k_eSwitchInputReportIDs_FullControllerAndMcuState: | ||
| 370 | case k_eSwitchInputReportIDs_SimpleControllerState: | ||
| 371 | ctx->m_nInitialInputMode = ctx->m_rgucReadBuffer[0]; | ||
| 372 | break; | ||
| 373 | default: | ||
| 374 | break; | ||
| 375 | } | ||
| 376 | } | ||
| 377 | return result; | ||
| 378 | } | ||
| 379 | |||
| 380 | static int WriteOutput(SDL_DriverSwitch_Context *ctx, const Uint8 *data, int size) | ||
| 381 | { | ||
| 382 | #ifdef SWITCH_SYNCHRONOUS_WRITES | ||
| 383 | return SDL_hid_write(ctx->device->dev, data, size); | ||
| 384 | #else | ||
| 385 | // Use the rumble thread for general asynchronous writes | ||
| 386 | if (!SDL_HIDAPI_LockRumble()) { | ||
| 387 | return -1; | ||
| 388 | } | ||
| 389 | return SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size); | ||
| 390 | #endif // SWITCH_SYNCHRONOUS_WRITES | ||
| 391 | } | ||
| 392 | |||
| 393 | static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID) | ||
| 394 | { | ||
| 395 | // Average response time for messages is ~30ms | ||
| 396 | Uint64 endTicks = SDL_GetTicks() + 100; | ||
| 397 | |||
| 398 | int nRead = 0; | ||
| 399 | while ((nRead = ReadInput(ctx)) != -1) { | ||
| 400 | if (nRead > 0) { | ||
| 401 | if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) { | ||
| 402 | SwitchSubcommandInputPacket_t *reply = (SwitchSubcommandInputPacket_t *)&ctx->m_rgucReadBuffer[1]; | ||
| 403 | if (reply->ucSubcommandID == expectedID && (reply->ucSubcommandAck & 0x80)) { | ||
| 404 | return reply; | ||
| 405 | } | ||
| 406 | } | ||
| 407 | } else { | ||
| 408 | SDL_Delay(1); | ||
| 409 | } | ||
| 410 | |||
| 411 | if (SDL_GetTicks() >= endTicks) { | ||
| 412 | break; | ||
| 413 | } | ||
| 414 | } | ||
| 415 | return NULL; | ||
| 416 | } | ||
| 417 | |||
| 418 | static bool ReadProprietaryReply(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs expectedID) | ||
| 419 | { | ||
| 420 | // Average response time for messages is ~30ms | ||
| 421 | Uint64 endTicks = SDL_GetTicks() + 100; | ||
| 422 | |||
| 423 | int nRead = 0; | ||
| 424 | while ((nRead = ReadInput(ctx)) != -1) { | ||
| 425 | if (nRead > 0) { | ||
| 426 | if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_CommandAck && ctx->m_rgucReadBuffer[1] == expectedID) { | ||
| 427 | return true; | ||
| 428 | } | ||
| 429 | } else { | ||
| 430 | SDL_Delay(1); | ||
| 431 | } | ||
| 432 | |||
| 433 | if (SDL_GetTicks() >= endTicks) { | ||
| 434 | break; | ||
| 435 | } | ||
| 436 | } | ||
| 437 | return false; | ||
| 438 | } | ||
| 439 | |||
| 440 | static void ConstructSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, const Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandOutputPacket_t *outPacket) | ||
| 441 | { | ||
| 442 | SDL_memset(outPacket, 0, sizeof(*outPacket)); | ||
| 443 | |||
| 444 | outPacket->commonData.ucPacketType = k_eSwitchOutputReportIDs_RumbleAndSubcommand; | ||
| 445 | outPacket->commonData.ucPacketNumber = ctx->m_nCommandNumber; | ||
| 446 | |||
| 447 | SDL_memcpy(outPacket->commonData.rumbleData, ctx->m_RumblePacket.rumbleData, sizeof(ctx->m_RumblePacket.rumbleData)); | ||
| 448 | |||
| 449 | outPacket->ucSubcommandID = ucCommandID; | ||
| 450 | if (pBuf) { | ||
| 451 | SDL_memcpy(outPacket->rgucSubcommandData, pBuf, ucLen); | ||
| 452 | } | ||
| 453 | |||
| 454 | ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF; | ||
| 455 | } | ||
| 456 | |||
| 457 | static bool WritePacket(SDL_DriverSwitch_Context *ctx, void *pBuf, Uint8 ucLen) | ||
| 458 | { | ||
| 459 | Uint8 rgucBuf[k_unSwitchMaxOutputPacketLength]; | ||
| 460 | const size_t unWriteSize = ctx->device->is_bluetooth ? k_unSwitchBluetoothPacketLength : k_unSwitchUSBPacketLength; | ||
| 461 | |||
| 462 | if (ucLen > k_unSwitchOutputPacketDataLength) { | ||
| 463 | return false; | ||
| 464 | } | ||
| 465 | |||
| 466 | if (ucLen < unWriteSize) { | ||
| 467 | SDL_memcpy(rgucBuf, pBuf, ucLen); | ||
| 468 | SDL_memset(rgucBuf + ucLen, 0, unWriteSize - ucLen); | ||
| 469 | pBuf = rgucBuf; | ||
| 470 | ucLen = (Uint8)unWriteSize; | ||
| 471 | } | ||
| 472 | if (ctx->m_bSyncWrite) { | ||
| 473 | return SDL_hid_write(ctx->device->dev, (Uint8 *)pBuf, ucLen) >= 0; | ||
| 474 | } else { | ||
| 475 | return WriteOutput(ctx, (Uint8 *)pBuf, ucLen) >= 0; | ||
| 476 | } | ||
| 477 | } | ||
| 478 | |||
| 479 | static bool WriteSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, const Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandInputPacket_t **ppReply) | ||
| 480 | { | ||
| 481 | SwitchSubcommandInputPacket_t *reply = NULL; | ||
| 482 | int nTries; | ||
| 483 | |||
| 484 | for (nTries = 1; !reply && nTries <= ctx->m_nMaxWriteAttempts; ++nTries) { | ||
| 485 | SwitchSubcommandOutputPacket_t commandPacket; | ||
| 486 | ConstructSubcommand(ctx, ucCommandID, pBuf, ucLen, &commandPacket); | ||
| 487 | |||
| 488 | if (!WritePacket(ctx, &commandPacket, sizeof(commandPacket))) { | ||
| 489 | continue; | ||
| 490 | } | ||
| 491 | |||
| 492 | reply = ReadSubcommandReply(ctx, ucCommandID); | ||
| 493 | } | ||
| 494 | |||
| 495 | if (ppReply) { | ||
| 496 | *ppReply = reply; | ||
| 497 | } | ||
| 498 | return reply != NULL; | ||
| 499 | } | ||
| 500 | |||
| 501 | static bool WriteProprietary(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs ucCommand, Uint8 *pBuf, Uint8 ucLen, bool waitForReply) | ||
| 502 | { | ||
| 503 | int nTries; | ||
| 504 | |||
| 505 | for (nTries = 1; nTries <= ctx->m_nMaxWriteAttempts; ++nTries) { | ||
| 506 | SwitchProprietaryOutputPacket_t packet; | ||
| 507 | |||
| 508 | if ((!pBuf && ucLen > 0) || ucLen > sizeof(packet.rgucProprietaryData)) { | ||
| 509 | return false; | ||
| 510 | } | ||
| 511 | |||
| 512 | SDL_zero(packet); | ||
| 513 | packet.ucPacketType = k_eSwitchOutputReportIDs_Proprietary; | ||
| 514 | packet.ucProprietaryID = ucCommand; | ||
| 515 | if (pBuf) { | ||
| 516 | SDL_memcpy(packet.rgucProprietaryData, pBuf, ucLen); | ||
| 517 | } | ||
| 518 | |||
| 519 | if (!WritePacket(ctx, &packet, sizeof(packet))) { | ||
| 520 | continue; | ||
| 521 | } | ||
| 522 | |||
| 523 | if (!waitForReply || ReadProprietaryReply(ctx, ucCommand)) { | ||
| 524 | // SDL_Log("Succeeded%s after %d tries", ctx->m_bSyncWrite ? " (sync)" : "", nTries); | ||
| 525 | return true; | ||
| 526 | } | ||
| 527 | } | ||
| 528 | // SDL_Log("Failed%s after %d tries", ctx->m_bSyncWrite ? " (sync)" : "", nTries); | ||
| 529 | return false; | ||
| 530 | } | ||
| 531 | |||
| 532 | static Uint8 EncodeRumbleHighAmplitude(Uint16 amplitude) | ||
| 533 | { | ||
| 534 | /* More information about these values can be found here: | ||
| 535 | * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md | ||
| 536 | */ | ||
| 537 | 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 } }; | ||
| 538 | int index = 0; | ||
| 539 | for (; index < 101; index++) { | ||
| 540 | if (amplitude <= hfa[index][0]) { | ||
| 541 | return (Uint8)hfa[index][1]; | ||
| 542 | } | ||
| 543 | } | ||
| 544 | return (Uint8)hfa[100][1]; | ||
| 545 | } | ||
| 546 | |||
| 547 | static Uint16 EncodeRumbleLowAmplitude(Uint16 amplitude) | ||
| 548 | { | ||
| 549 | /* More information about these values can be found here: | ||
| 550 | * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md | ||
| 551 | */ | ||
| 552 | 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 } }; | ||
| 553 | int index = 0; | ||
| 554 | for (; index < 101; index++) { | ||
| 555 | if (amplitude <= lfa[index][0]) { | ||
| 556 | return lfa[index][1]; | ||
| 557 | } | ||
| 558 | } | ||
| 559 | return lfa[100][1]; | ||
| 560 | } | ||
| 561 | |||
| 562 | static void SetNeutralRumble(SwitchRumbleData_t *pRumble) | ||
| 563 | { | ||
| 564 | pRumble->rgucData[0] = 0x00; | ||
| 565 | pRumble->rgucData[1] = 0x01; | ||
| 566 | pRumble->rgucData[2] = 0x40; | ||
| 567 | pRumble->rgucData[3] = 0x40; | ||
| 568 | } | ||
| 569 | |||
| 570 | static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp) | ||
| 571 | { | ||
| 572 | if (ucHighFreqAmp > 0 || usLowFreqAmp > 0) { | ||
| 573 | // High-band frequency and low-band amplitude are actually nine-bits each so they | ||
| 574 | // take a bit from the high-band amplitude and low-band frequency bytes respectively | ||
| 575 | pRumble->rgucData[0] = usHighFreq & 0xFF; | ||
| 576 | pRumble->rgucData[1] = ucHighFreqAmp | ((usHighFreq >> 8) & 0x01); | ||
| 577 | |||
| 578 | pRumble->rgucData[2] = ucLowFreq | ((usLowFreqAmp >> 8) & 0x80); | ||
| 579 | pRumble->rgucData[3] = usLowFreqAmp & 0xFF; | ||
| 580 | |||
| 581 | #ifdef DEBUG_RUMBLE | ||
| 582 | SDL_Log("Freq: %.2X %.2X %.2X, Amp: %.2X %.2X %.2X", | ||
| 583 | usHighFreq & 0xFF, ((usHighFreq >> 8) & 0x01), ucLowFreq, | ||
| 584 | ucHighFreqAmp, ((usLowFreqAmp >> 8) & 0x80), usLowFreqAmp & 0xFF); | ||
| 585 | #endif | ||
| 586 | } else { | ||
| 587 | SetNeutralRumble(pRumble); | ||
| 588 | } | ||
| 589 | } | ||
| 590 | |||
| 591 | static bool WriteRumble(SDL_DriverSwitch_Context *ctx) | ||
| 592 | { | ||
| 593 | /* Write into m_RumblePacket rather than a temporary buffer to allow the current rumble state | ||
| 594 | * to be retained for subsequent rumble or subcommand packets sent to the controller | ||
| 595 | */ | ||
| 596 | ctx->m_RumblePacket.ucPacketType = k_eSwitchOutputReportIDs_Rumble; | ||
| 597 | ctx->m_RumblePacket.ucPacketNumber = ctx->m_nCommandNumber; | ||
| 598 | ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF; | ||
| 599 | |||
| 600 | // Refresh the rumble state periodically | ||
| 601 | ctx->m_ulRumbleSent = SDL_GetTicks(); | ||
| 602 | |||
| 603 | return WritePacket(ctx, (Uint8 *)&ctx->m_RumblePacket, sizeof(ctx->m_RumblePacket)); | ||
| 604 | } | ||
| 605 | |||
| 606 | static ESwitchDeviceInfoControllerType CalculateControllerType(SDL_DriverSwitch_Context *ctx, ESwitchDeviceInfoControllerType eControllerType) | ||
| 607 | { | ||
| 608 | SDL_HIDAPI_Device *device = ctx->device; | ||
| 609 | |||
| 610 | // The N64 controller reports as a Pro controller over USB | ||
| 611 | if (eControllerType == k_eSwitchDeviceInfoControllerType_ProController && | ||
| 612 | device->product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER) { | ||
| 613 | eControllerType = k_eSwitchDeviceInfoControllerType_N64; | ||
| 614 | } | ||
| 615 | |||
| 616 | if (eControllerType == k_eSwitchDeviceInfoControllerType_Unknown) { | ||
| 617 | // This might be a Joy-Con that's missing from a charging grip slot | ||
| 618 | if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) { | ||
| 619 | if (device->interface_number == 1) { | ||
| 620 | eControllerType = k_eSwitchDeviceInfoControllerType_JoyConLeft; | ||
| 621 | } else { | ||
| 622 | eControllerType = k_eSwitchDeviceInfoControllerType_JoyConRight; | ||
| 623 | } | ||
| 624 | } | ||
| 625 | } | ||
| 626 | return eControllerType; | ||
| 627 | } | ||
| 628 | |||
| 629 | static bool BReadDeviceInfo(SDL_DriverSwitch_Context *ctx) | ||
| 630 | { | ||
| 631 | SwitchSubcommandInputPacket_t *reply = NULL; | ||
| 632 | |||
| 633 | if (ctx->device->is_bluetooth) { | ||
| 634 | if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_RequestDeviceInfo, NULL, 0, &reply)) { | ||
| 635 | // Byte 2: Controller ID (1=LJC, 2=RJC, 3=Pro) | ||
| 636 | ctx->m_eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)reply->deviceInfo.ucDeviceType); | ||
| 637 | |||
| 638 | // Bytes 4-9: MAC address (big-endian) | ||
| 639 | SDL_memcpy(ctx->m_rgucMACAddress, reply->deviceInfo.rgucMACAddress, sizeof(ctx->m_rgucMACAddress)); | ||
| 640 | |||
| 641 | return true; | ||
| 642 | } | ||
| 643 | } else { | ||
| 644 | if (WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Status, NULL, 0, true)) { | ||
| 645 | SwitchProprietaryStatusPacket_t *status = (SwitchProprietaryStatusPacket_t *)&ctx->m_rgucReadBuffer[0]; | ||
| 646 | size_t i; | ||
| 647 | |||
| 648 | ctx->m_eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)status->ucDeviceType); | ||
| 649 | |||
| 650 | for (i = 0; i < sizeof(ctx->m_rgucMACAddress); ++i) { | ||
| 651 | ctx->m_rgucMACAddress[i] = status->rgucMACAddress[sizeof(ctx->m_rgucMACAddress) - i - 1]; | ||
| 652 | } | ||
| 653 | |||
| 654 | return true; | ||
| 655 | } | ||
| 656 | } | ||
| 657 | return false; | ||
| 658 | } | ||
| 659 | |||
| 660 | static bool BTrySetupUSB(SDL_DriverSwitch_Context *ctx) | ||
| 661 | { | ||
| 662 | /* We have to send a connection handshake to the controller when communicating over USB | ||
| 663 | * before we're able to send it other commands. Luckily this command is not supported | ||
| 664 | * over Bluetooth, so we can use the controller's lack of response as a way to | ||
| 665 | * determine if the connection is over USB or Bluetooth | ||
| 666 | */ | ||
| 667 | if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, true)) { | ||
| 668 | return false; | ||
| 669 | } | ||
| 670 | if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_HighSpeed, NULL, 0, true)) { | ||
| 671 | // The 8BitDo M30 and SF30 Pro don't respond to this command, but otherwise work correctly | ||
| 672 | // return false; | ||
| 673 | } | ||
| 674 | if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, true)) { | ||
| 675 | // This fails on the right Joy-Con when plugged into the charging grip | ||
| 676 | // return false; | ||
| 677 | } | ||
| 678 | if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, false)) { | ||
| 679 | return false; | ||
| 680 | } | ||
| 681 | return true; | ||
| 682 | } | ||
| 683 | |||
| 684 | static bool SetVibrationEnabled(SDL_DriverSwitch_Context *ctx, Uint8 enabled) | ||
| 685 | { | ||
| 686 | return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_EnableVibration, &enabled, sizeof(enabled), NULL); | ||
| 687 | } | ||
| 688 | static bool SetInputMode(SDL_DriverSwitch_Context *ctx, Uint8 input_mode) | ||
| 689 | { | ||
| 690 | #ifdef FORCE_SIMPLE_REPORTS | ||
| 691 | input_mode = k_eSwitchInputReportIDs_SimpleControllerState; | ||
| 692 | #endif | ||
| 693 | #ifdef FORCE_FULL_REPORTS | ||
| 694 | input_mode = k_eSwitchInputReportIDs_FullControllerState; | ||
| 695 | #endif | ||
| 696 | |||
| 697 | if (input_mode == ctx->m_nCurrentInputMode) { | ||
| 698 | return true; | ||
| 699 | } else { | ||
| 700 | ctx->m_nCurrentInputMode = input_mode; | ||
| 701 | |||
| 702 | return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetInputReportMode, &input_mode, sizeof(input_mode), NULL); | ||
| 703 | } | ||
| 704 | } | ||
| 705 | |||
| 706 | static bool SetHomeLED(SDL_DriverSwitch_Context *ctx, Uint8 brightness) | ||
| 707 | { | ||
| 708 | Uint8 ucLedIntensity = 0; | ||
| 709 | Uint8 rgucBuffer[4]; | ||
| 710 | |||
| 711 | if (brightness > 0) { | ||
| 712 | if (brightness < 65) { | ||
| 713 | ucLedIntensity = (brightness + 5) / 10; | ||
| 714 | } else { | ||
| 715 | ucLedIntensity = (Uint8)SDL_ceilf(0xF * SDL_powf((float)brightness / 100.f, 2.13f)); | ||
| 716 | } | ||
| 717 | } | ||
| 718 | |||
| 719 | rgucBuffer[0] = (0x0 << 4) | 0x1; // 0 mini cycles (besides first), cycle duration 8ms | ||
| 720 | rgucBuffer[1] = ((ucLedIntensity & 0xF) << 4) | 0x0; // LED start intensity (0x0-0xF), 0 cycles (LED stays on at start intensity after first cycle) | ||
| 721 | rgucBuffer[2] = ((ucLedIntensity & 0xF) << 4) | 0x0; // First cycle LED intensity, 0x0 intensity for second cycle | ||
| 722 | rgucBuffer[3] = (0x0 << 4) | 0x0; // 8ms fade transition to first cycle, 8ms first cycle LED duration | ||
| 723 | |||
| 724 | return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetHomeLight, rgucBuffer, sizeof(rgucBuffer), NULL); | ||
| 725 | } | ||
| 726 | |||
| 727 | static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 728 | { | ||
| 729 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata; | ||
| 730 | |||
| 731 | if (hint && *hint) { | ||
| 732 | int value; | ||
| 733 | |||
| 734 | if (SDL_strchr(hint, '.') != NULL) { | ||
| 735 | value = (int)(100.0f * SDL_atof(hint)); | ||
| 736 | if (value > 255) { | ||
| 737 | value = 255; | ||
| 738 | } | ||
| 739 | } else if (SDL_GetStringBoolean(hint, true)) { | ||
| 740 | value = 100; | ||
| 741 | } else { | ||
| 742 | value = 0; | ||
| 743 | } | ||
| 744 | SetHomeLED(ctx, (Uint8)value); | ||
| 745 | } | ||
| 746 | } | ||
| 747 | |||
| 748 | static void UpdateSlotLED(SDL_DriverSwitch_Context *ctx) | ||
| 749 | { | ||
| 750 | if (!ctx->m_bInputOnly) { | ||
| 751 | Uint8 led_data = 0; | ||
| 752 | |||
| 753 | if (ctx->m_bPlayerLights && ctx->m_nPlayerIndex >= 0) { | ||
| 754 | led_data = (1 << (ctx->m_nPlayerIndex % 4)); | ||
| 755 | } | ||
| 756 | WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetPlayerLights, &led_data, sizeof(led_data), NULL); | ||
| 757 | } | ||
| 758 | } | ||
| 759 | |||
| 760 | static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 761 | { | ||
| 762 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata; | ||
| 763 | bool bPlayerLights = SDL_GetStringBoolean(hint, true); | ||
| 764 | |||
| 765 | if (bPlayerLights != ctx->m_bPlayerLights) { | ||
| 766 | ctx->m_bPlayerLights = bPlayerLights; | ||
| 767 | |||
| 768 | UpdateSlotLED(ctx); | ||
| 769 | HIDAPI_UpdateDeviceProperties(ctx->device); | ||
| 770 | } | ||
| 771 | } | ||
| 772 | |||
| 773 | static void GetInitialInputMode(SDL_DriverSwitch_Context *ctx) | ||
| 774 | { | ||
| 775 | if (!ctx->m_nInitialInputMode) { | ||
| 776 | // This will set the initial input mode if it can | ||
| 777 | ReadInput(ctx); | ||
| 778 | } | ||
| 779 | } | ||
| 780 | |||
| 781 | static Uint8 GetDefaultInputMode(SDL_DriverSwitch_Context *ctx) | ||
| 782 | { | ||
| 783 | Uint8 input_mode; | ||
| 784 | |||
| 785 | // Determine the desired input mode | ||
| 786 | if (ctx->m_nInitialInputMode) { | ||
| 787 | input_mode = ctx->m_nInitialInputMode; | ||
| 788 | } else { | ||
| 789 | if (ctx->device->is_bluetooth) { | ||
| 790 | input_mode = k_eSwitchInputReportIDs_SimpleControllerState; | ||
| 791 | } else { | ||
| 792 | input_mode = k_eSwitchInputReportIDs_FullControllerState; | ||
| 793 | } | ||
| 794 | } | ||
| 795 | |||
| 796 | switch (ctx->m_eEnhancedReportHint) { | ||
| 797 | case SWITCH_ENHANCED_REPORT_HINT_OFF: | ||
| 798 | input_mode = k_eSwitchInputReportIDs_SimpleControllerState; | ||
| 799 | break; | ||
| 800 | case SWITCH_ENHANCED_REPORT_HINT_ON: | ||
| 801 | if (input_mode == k_eSwitchInputReportIDs_SimpleControllerState) { | ||
| 802 | input_mode = k_eSwitchInputReportIDs_FullControllerState; | ||
| 803 | } | ||
| 804 | break; | ||
| 805 | case SWITCH_ENHANCED_REPORT_HINT_AUTO: | ||
| 806 | /* Joy-Con controllers switch their thumbsticks into D-pad mode in simple mode, | ||
| 807 | * so let's enable full controller state for them. | ||
| 808 | */ | ||
| 809 | if (ctx->device->vendor_id == USB_VENDOR_NINTENDO && | ||
| 810 | (ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT || | ||
| 811 | ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT)) { | ||
| 812 | input_mode = k_eSwitchInputReportIDs_FullControllerState; | ||
| 813 | } | ||
| 814 | break; | ||
| 815 | } | ||
| 816 | |||
| 817 | // Wired controllers break if they are put into simple controller state | ||
| 818 | if (input_mode == k_eSwitchInputReportIDs_SimpleControllerState && | ||
| 819 | !ctx->device->is_bluetooth) { | ||
| 820 | input_mode = k_eSwitchInputReportIDs_FullControllerState; | ||
| 821 | } | ||
| 822 | return input_mode; | ||
| 823 | } | ||
| 824 | |||
| 825 | static Uint8 GetSensorInputMode(SDL_DriverSwitch_Context *ctx) | ||
| 826 | { | ||
| 827 | Uint8 input_mode; | ||
| 828 | |||
| 829 | // Determine the desired input mode | ||
| 830 | if (!ctx->m_nInitialInputMode || | ||
| 831 | ctx->m_nInitialInputMode == k_eSwitchInputReportIDs_SimpleControllerState) { | ||
| 832 | input_mode = k_eSwitchInputReportIDs_FullControllerState; | ||
| 833 | } else { | ||
| 834 | input_mode = ctx->m_nInitialInputMode; | ||
| 835 | } | ||
| 836 | return input_mode; | ||
| 837 | } | ||
| 838 | |||
| 839 | static void UpdateInputMode(SDL_DriverSwitch_Context *ctx) | ||
| 840 | { | ||
| 841 | Uint8 input_mode; | ||
| 842 | |||
| 843 | if (ctx->m_bReportSensors) { | ||
| 844 | input_mode = GetSensorInputMode(ctx); | ||
| 845 | } else { | ||
| 846 | input_mode = GetDefaultInputMode(ctx); | ||
| 847 | } | ||
| 848 | SetInputMode(ctx, input_mode); | ||
| 849 | } | ||
| 850 | |||
| 851 | static void SetEnhancedModeAvailable(SDL_DriverSwitch_Context *ctx) | ||
| 852 | { | ||
| 853 | if (ctx->m_bEnhancedModeAvailable) { | ||
| 854 | return; | ||
| 855 | } | ||
| 856 | ctx->m_bEnhancedModeAvailable = true; | ||
| 857 | |||
| 858 | if (ctx->m_bSensorsSupported) { | ||
| 859 | // Use the right sensor in the combined Joy-Con pair | ||
| 860 | if (!ctx->device->parent || | ||
| 861 | ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { | ||
| 862 | SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 200.0f); | ||
| 863 | SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 200.0f); | ||
| 864 | } | ||
| 865 | if (ctx->device->parent && | ||
| 866 | ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { | ||
| 867 | SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO_L, 200.0f); | ||
| 868 | SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL_L, 200.0f); | ||
| 869 | } | ||
| 870 | if (ctx->device->parent && | ||
| 871 | ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { | ||
| 872 | SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO_R, 200.0f); | ||
| 873 | SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL_R, 200.0f); | ||
| 874 | } | ||
| 875 | } | ||
| 876 | } | ||
| 877 | |||
| 878 | static void SetEnhancedReportHint(SDL_DriverSwitch_Context *ctx, HIDAPI_Switch_EnhancedReportHint eEnhancedReportHint) | ||
| 879 | { | ||
| 880 | ctx->m_eEnhancedReportHint = eEnhancedReportHint; | ||
| 881 | |||
| 882 | switch (eEnhancedReportHint) { | ||
| 883 | case SWITCH_ENHANCED_REPORT_HINT_OFF: | ||
| 884 | ctx->m_bEnhancedMode = false; | ||
| 885 | break; | ||
| 886 | case SWITCH_ENHANCED_REPORT_HINT_ON: | ||
| 887 | SetEnhancedModeAvailable(ctx); | ||
| 888 | ctx->m_bEnhancedMode = true; | ||
| 889 | break; | ||
| 890 | case SWITCH_ENHANCED_REPORT_HINT_AUTO: | ||
| 891 | SetEnhancedModeAvailable(ctx); | ||
| 892 | break; | ||
| 893 | } | ||
| 894 | |||
| 895 | UpdateInputMode(ctx); | ||
| 896 | } | ||
| 897 | |||
| 898 | static void UpdateEnhancedModeOnEnhancedReport(SDL_DriverSwitch_Context *ctx) | ||
| 899 | { | ||
| 900 | if (ctx->m_eEnhancedReportHint == SWITCH_ENHANCED_REPORT_HINT_AUTO) { | ||
| 901 | SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON); | ||
| 902 | } | ||
| 903 | } | ||
| 904 | |||
| 905 | static void UpdateEnhancedModeOnApplicationUsage(SDL_DriverSwitch_Context *ctx) | ||
| 906 | { | ||
| 907 | if (ctx->m_eEnhancedReportHint == SWITCH_ENHANCED_REPORT_HINT_AUTO) { | ||
| 908 | SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON); | ||
| 909 | } | ||
| 910 | } | ||
| 911 | |||
| 912 | static void SDLCALL SDL_EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 913 | { | ||
| 914 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata; | ||
| 915 | |||
| 916 | if (hint && SDL_strcasecmp(hint, "auto") == 0) { | ||
| 917 | SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_AUTO); | ||
| 918 | } else if (SDL_GetStringBoolean(hint, true)) { | ||
| 919 | SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON); | ||
| 920 | } else { | ||
| 921 | SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_OFF); | ||
| 922 | } | ||
| 923 | } | ||
| 924 | |||
| 925 | static bool SetIMUEnabled(SDL_DriverSwitch_Context *ctx, bool enabled) | ||
| 926 | { | ||
| 927 | Uint8 imu_data = enabled ? 1 : 0; | ||
| 928 | return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_EnableIMU, &imu_data, sizeof(imu_data), NULL); | ||
| 929 | } | ||
| 930 | |||
| 931 | static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx) | ||
| 932 | { | ||
| 933 | Uint8 *pLeftStickCal; | ||
| 934 | Uint8 *pRightStickCal; | ||
| 935 | size_t stick, axis; | ||
| 936 | SwitchSubcommandInputPacket_t *user_reply = NULL; | ||
| 937 | SwitchSubcommandInputPacket_t *factory_reply = NULL; | ||
| 938 | SwitchSPIOpData_t readUserParams; | ||
| 939 | SwitchSPIOpData_t readFactoryParams; | ||
| 940 | |||
| 941 | // Read User Calibration Info | ||
| 942 | readUserParams.unAddress = k_unSPIStickUserCalibrationStartOffset; | ||
| 943 | readUserParams.ucLength = k_unSPIStickUserCalibrationLength; | ||
| 944 | |||
| 945 | // This isn't readable on all controllers, so ignore failure | ||
| 946 | WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readUserParams, sizeof(readUserParams), &user_reply); | ||
| 947 | |||
| 948 | // Read Factory Calibration Info | ||
| 949 | readFactoryParams.unAddress = k_unSPIStickFactoryCalibrationStartOffset; | ||
| 950 | readFactoryParams.ucLength = k_unSPIStickFactoryCalibrationLength; | ||
| 951 | |||
| 952 | const int MAX_ATTEMPTS = 3; | ||
| 953 | for (int attempt = 0; ; ++attempt) { | ||
| 954 | if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readFactoryParams, sizeof(readFactoryParams), &factory_reply)) { | ||
| 955 | return false; | ||
| 956 | } | ||
| 957 | |||
| 958 | if (factory_reply->stickFactoryCalibration.opData.unAddress == k_unSPIStickFactoryCalibrationStartOffset) { | ||
| 959 | // We successfully read the calibration data | ||
| 960 | break; | ||
| 961 | } | ||
| 962 | |||
| 963 | if (attempt == MAX_ATTEMPTS) { | ||
| 964 | return false; | ||
| 965 | } | ||
| 966 | } | ||
| 967 | |||
| 968 | // Automatically select the user calibration if magic bytes are set | ||
| 969 | if (user_reply && user_reply->stickUserCalibration.rgucLeftMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucLeftMagic[1] == 0xA1) { | ||
| 970 | pLeftStickCal = user_reply->stickUserCalibration.rgucLeftCalibration; | ||
| 971 | } else { | ||
| 972 | pLeftStickCal = factory_reply->stickFactoryCalibration.rgucLeftCalibration; | ||
| 973 | } | ||
| 974 | |||
| 975 | if (user_reply && user_reply->stickUserCalibration.rgucRightMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucRightMagic[1] == 0xA1) { | ||
| 976 | pRightStickCal = user_reply->stickUserCalibration.rgucRightCalibration; | ||
| 977 | } else { | ||
| 978 | pRightStickCal = factory_reply->stickFactoryCalibration.rgucRightCalibration; | ||
| 979 | } | ||
| 980 | |||
| 981 | /* Stick calibration values are 12-bits each and are packed by bit | ||
| 982 | * For whatever reason the fields are in a different order for each stick | ||
| 983 | * Left: X-Max, Y-Max, X-Center, Y-Center, X-Min, Y-Min | ||
| 984 | * Right: X-Center, Y-Center, X-Min, Y-Min, X-Max, Y-Max | ||
| 985 | */ | ||
| 986 | |||
| 987 | // Left stick | ||
| 988 | ctx->m_StickCalData[0].axis[0].sMax = ((pLeftStickCal[1] << 8) & 0xF00) | pLeftStickCal[0]; // X Axis max above center | ||
| 989 | ctx->m_StickCalData[0].axis[1].sMax = (pLeftStickCal[2] << 4) | (pLeftStickCal[1] >> 4); // Y Axis max above center | ||
| 990 | ctx->m_StickCalData[0].axis[0].sCenter = ((pLeftStickCal[4] << 8) & 0xF00) | pLeftStickCal[3]; // X Axis center | ||
| 991 | ctx->m_StickCalData[0].axis[1].sCenter = (pLeftStickCal[5] << 4) | (pLeftStickCal[4] >> 4); // Y Axis center | ||
| 992 | ctx->m_StickCalData[0].axis[0].sMin = ((pLeftStickCal[7] << 8) & 0xF00) | pLeftStickCal[6]; // X Axis min below center | ||
| 993 | ctx->m_StickCalData[0].axis[1].sMin = (pLeftStickCal[8] << 4) | (pLeftStickCal[7] >> 4); // Y Axis min below center | ||
| 994 | |||
| 995 | // Right stick | ||
| 996 | ctx->m_StickCalData[1].axis[0].sCenter = ((pRightStickCal[1] << 8) & 0xF00) | pRightStickCal[0]; // X Axis center | ||
| 997 | ctx->m_StickCalData[1].axis[1].sCenter = (pRightStickCal[2] << 4) | (pRightStickCal[1] >> 4); // Y Axis center | ||
| 998 | ctx->m_StickCalData[1].axis[0].sMin = ((pRightStickCal[4] << 8) & 0xF00) | pRightStickCal[3]; // X Axis min below center | ||
| 999 | ctx->m_StickCalData[1].axis[1].sMin = (pRightStickCal[5] << 4) | (pRightStickCal[4] >> 4); // Y Axis min below center | ||
| 1000 | ctx->m_StickCalData[1].axis[0].sMax = ((pRightStickCal[7] << 8) & 0xF00) | pRightStickCal[6]; // X Axis max above center | ||
| 1001 | ctx->m_StickCalData[1].axis[1].sMax = (pRightStickCal[8] << 4) | (pRightStickCal[7] >> 4); // Y Axis max above center | ||
| 1002 | |||
| 1003 | // Filter out any values that were uninitialized (0xFFF) in the SPI read | ||
| 1004 | for (stick = 0; stick < 2; ++stick) { | ||
| 1005 | for (axis = 0; axis < 2; ++axis) { | ||
| 1006 | if (ctx->m_StickCalData[stick].axis[axis].sCenter == 0xFFF) { | ||
| 1007 | ctx->m_StickCalData[stick].axis[axis].sCenter = 2048; | ||
| 1008 | } | ||
| 1009 | if (ctx->m_StickCalData[stick].axis[axis].sMax == 0xFFF) { | ||
| 1010 | ctx->m_StickCalData[stick].axis[axis].sMax = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sCenter * 0.7f); | ||
| 1011 | } | ||
| 1012 | if (ctx->m_StickCalData[stick].axis[axis].sMin == 0xFFF) { | ||
| 1013 | ctx->m_StickCalData[stick].axis[axis].sMin = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sCenter * 0.7f); | ||
| 1014 | } | ||
| 1015 | } | ||
| 1016 | } | ||
| 1017 | |||
| 1018 | for (stick = 0; stick < 2; ++stick) { | ||
| 1019 | for (axis = 0; axis < 2; ++axis) { | ||
| 1020 | ctx->m_StickExtents[stick].axis[axis].sMin = -(Sint16)(ctx->m_StickCalData[stick].axis[axis].sMin * 0.7f); | ||
| 1021 | ctx->m_StickExtents[stick].axis[axis].sMax = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sMax * 0.7f); | ||
| 1022 | } | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | for (stick = 0; stick < 2; ++stick) { | ||
| 1026 | for (axis = 0; axis < 2; ++axis) { | ||
| 1027 | ctx->m_SimpleStickExtents[stick].axis[axis].sMin = (Sint16)(SDL_MIN_SINT16 * 0.5f); | ||
| 1028 | ctx->m_SimpleStickExtents[stick].axis[axis].sMax = (Sint16)(SDL_MAX_SINT16 * 0.5f); | ||
| 1029 | } | ||
| 1030 | } | ||
| 1031 | |||
| 1032 | return true; | ||
| 1033 | } | ||
| 1034 | |||
| 1035 | static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx) | ||
| 1036 | { | ||
| 1037 | SwitchSubcommandInputPacket_t *reply = NULL; | ||
| 1038 | |||
| 1039 | // Read Calibration Info | ||
| 1040 | SwitchSPIOpData_t readParams; | ||
| 1041 | readParams.unAddress = k_unSPIIMUScaleStartOffset; | ||
| 1042 | readParams.ucLength = k_unSPIIMUScaleLength; | ||
| 1043 | |||
| 1044 | if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply)) { | ||
| 1045 | Uint8 *pIMUScale; | ||
| 1046 | Sint16 sAccelRawX, sAccelRawY, sAccelRawZ, sGyroRawX, sGyroRawY, sGyroRawZ; | ||
| 1047 | |||
| 1048 | // IMU scale gives us multipliers for converting raw values to real world values | ||
| 1049 | pIMUScale = reply->spiReadData.rgucReadData; | ||
| 1050 | |||
| 1051 | sAccelRawX = (pIMUScale[1] << 8) | pIMUScale[0]; | ||
| 1052 | sAccelRawY = (pIMUScale[3] << 8) | pIMUScale[2]; | ||
| 1053 | sAccelRawZ = (pIMUScale[5] << 8) | pIMUScale[4]; | ||
| 1054 | |||
| 1055 | sGyroRawX = (pIMUScale[13] << 8) | pIMUScale[12]; | ||
| 1056 | sGyroRawY = (pIMUScale[15] << 8) | pIMUScale[14]; | ||
| 1057 | sGyroRawZ = (pIMUScale[17] << 8) | pIMUScale[16]; | ||
| 1058 | |||
| 1059 | // Check for user calibration data. If it's present and set, it'll override the factory settings | ||
| 1060 | readParams.unAddress = k_unSPIIMUUserScaleStartOffset; | ||
| 1061 | readParams.ucLength = k_unSPIIMUUserScaleLength; | ||
| 1062 | if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply) && (pIMUScale[0] | pIMUScale[1] << 8) == 0xA1B2) { | ||
| 1063 | pIMUScale = reply->spiReadData.rgucReadData; | ||
| 1064 | |||
| 1065 | sAccelRawX = (pIMUScale[3] << 8) | pIMUScale[2]; | ||
| 1066 | sAccelRawY = (pIMUScale[5] << 8) | pIMUScale[4]; | ||
| 1067 | sAccelRawZ = (pIMUScale[7] << 8) | pIMUScale[6]; | ||
| 1068 | |||
| 1069 | sGyroRawX = (pIMUScale[15] << 8) | pIMUScale[14]; | ||
| 1070 | sGyroRawY = (pIMUScale[17] << 8) | pIMUScale[16]; | ||
| 1071 | sGyroRawZ = (pIMUScale[19] << 8) | pIMUScale[18]; | ||
| 1072 | } | ||
| 1073 | |||
| 1074 | // Accelerometer scale | ||
| 1075 | ctx->m_IMUScaleData.fAccelScaleX = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawX) * SDL_STANDARD_GRAVITY; | ||
| 1076 | ctx->m_IMUScaleData.fAccelScaleY = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawY) * SDL_STANDARD_GRAVITY; | ||
| 1077 | ctx->m_IMUScaleData.fAccelScaleZ = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawZ) * SDL_STANDARD_GRAVITY; | ||
| 1078 | |||
| 1079 | // Gyro scale | ||
| 1080 | ctx->m_IMUScaleData.fGyroScaleX = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawX) * SDL_PI_F / 180.0f; | ||
| 1081 | ctx->m_IMUScaleData.fGyroScaleY = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawY) * SDL_PI_F / 180.0f; | ||
| 1082 | ctx->m_IMUScaleData.fGyroScaleZ = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawZ) * SDL_PI_F / 180.0f; | ||
| 1083 | |||
| 1084 | } else { | ||
| 1085 | // Use default values | ||
| 1086 | const float accelScale = SDL_STANDARD_GRAVITY / SWITCH_ACCEL_SCALE; | ||
| 1087 | const float gyroScale = SDL_PI_F / 180.0f / SWITCH_GYRO_SCALE; | ||
| 1088 | |||
| 1089 | ctx->m_IMUScaleData.fAccelScaleX = accelScale; | ||
| 1090 | ctx->m_IMUScaleData.fAccelScaleY = accelScale; | ||
| 1091 | ctx->m_IMUScaleData.fAccelScaleZ = accelScale; | ||
| 1092 | |||
| 1093 | ctx->m_IMUScaleData.fGyroScaleX = gyroScale; | ||
| 1094 | ctx->m_IMUScaleData.fGyroScaleY = gyroScale; | ||
| 1095 | ctx->m_IMUScaleData.fGyroScaleZ = gyroScale; | ||
| 1096 | } | ||
| 1097 | return true; | ||
| 1098 | } | ||
| 1099 | |||
| 1100 | static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue) | ||
| 1101 | { | ||
| 1102 | sRawValue -= ctx->m_StickCalData[nStick].axis[nAxis].sCenter; | ||
| 1103 | |||
| 1104 | if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) { | ||
| 1105 | ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue; | ||
| 1106 | } | ||
| 1107 | if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) { | ||
| 1108 | ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue; | ||
| 1109 | } | ||
| 1110 | |||
| 1111 | 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); | ||
| 1112 | } | ||
| 1113 | |||
| 1114 | static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue) | ||
| 1115 | { | ||
| 1116 | // 0x8000 is the neutral value for all joystick axes | ||
| 1117 | const Uint16 usJoystickCenter = 0x8000; | ||
| 1118 | |||
| 1119 | sRawValue -= usJoystickCenter; | ||
| 1120 | |||
| 1121 | if (sRawValue > ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax) { | ||
| 1122 | ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax = sRawValue; | ||
| 1123 | } | ||
| 1124 | if (sRawValue < ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin) { | ||
| 1125 | ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin = sRawValue; | ||
| 1126 | } | ||
| 1127 | |||
| 1128 | 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); | ||
| 1129 | } | ||
| 1130 | |||
| 1131 | static Uint8 RemapButton(SDL_DriverSwitch_Context *ctx, Uint8 button) | ||
| 1132 | { | ||
| 1133 | if (ctx->m_bUseButtonLabels) { | ||
| 1134 | // Use button labels instead of positions, e.g. Nintendo Online Classic controllers | ||
| 1135 | switch (button) { | ||
| 1136 | case SDL_GAMEPAD_BUTTON_SOUTH: | ||
| 1137 | return SDL_GAMEPAD_BUTTON_EAST; | ||
| 1138 | case SDL_GAMEPAD_BUTTON_EAST: | ||
| 1139 | return SDL_GAMEPAD_BUTTON_SOUTH; | ||
| 1140 | case SDL_GAMEPAD_BUTTON_WEST: | ||
| 1141 | return SDL_GAMEPAD_BUTTON_NORTH; | ||
| 1142 | case SDL_GAMEPAD_BUTTON_NORTH: | ||
| 1143 | return SDL_GAMEPAD_BUTTON_WEST; | ||
| 1144 | default: | ||
| 1145 | break; | ||
| 1146 | } | ||
| 1147 | } | ||
| 1148 | return button; | ||
| 1149 | } | ||
| 1150 | |||
| 1151 | static int GetMaxWriteAttempts(SDL_HIDAPI_Device *device) | ||
| 1152 | { | ||
| 1153 | if (device->vendor_id == USB_VENDOR_NINTENDO && | ||
| 1154 | device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) { | ||
| 1155 | // This device is a little slow and we know we're always on USB | ||
| 1156 | return 20; | ||
| 1157 | } else { | ||
| 1158 | return 5; | ||
| 1159 | } | ||
| 1160 | } | ||
| 1161 | |||
| 1162 | static ESwitchDeviceInfoControllerType ReadJoyConControllerType(SDL_HIDAPI_Device *device) | ||
| 1163 | { | ||
| 1164 | ESwitchDeviceInfoControllerType eControllerType = k_eSwitchDeviceInfoControllerType_Unknown; | ||
| 1165 | const int MAX_ATTEMPTS = 1; // Don't try too long, in case this is a zombie Bluetooth controller | ||
| 1166 | int attempts = 0; | ||
| 1167 | |||
| 1168 | // Create enough of a context to read the controller type from the device | ||
| 1169 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 1170 | if (ctx) { | ||
| 1171 | ctx->device = device; | ||
| 1172 | ctx->m_bSyncWrite = true; | ||
| 1173 | ctx->m_nMaxWriteAttempts = GetMaxWriteAttempts(device); | ||
| 1174 | |||
| 1175 | for ( ; ; ) { | ||
| 1176 | ++attempts; | ||
| 1177 | if (device->is_bluetooth) { | ||
| 1178 | SwitchSubcommandInputPacket_t *reply = NULL; | ||
| 1179 | |||
| 1180 | if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_RequestDeviceInfo, NULL, 0, &reply)) { | ||
| 1181 | eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)reply->deviceInfo.ucDeviceType); | ||
| 1182 | } | ||
| 1183 | } else { | ||
| 1184 | if (WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Status, NULL, 0, true)) { | ||
| 1185 | SwitchProprietaryStatusPacket_t *status = (SwitchProprietaryStatusPacket_t *)&ctx->m_rgucReadBuffer[0]; | ||
| 1186 | |||
| 1187 | eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)status->ucDeviceType); | ||
| 1188 | } | ||
| 1189 | } | ||
| 1190 | if (eControllerType == k_eSwitchDeviceInfoControllerType_Unknown && attempts < MAX_ATTEMPTS) { | ||
| 1191 | // Wait a bit and try again | ||
| 1192 | SDL_Delay(100); | ||
| 1193 | continue; | ||
| 1194 | } | ||
| 1195 | break; | ||
| 1196 | } | ||
| 1197 | SDL_free(ctx); | ||
| 1198 | } | ||
| 1199 | return eControllerType; | ||
| 1200 | } | ||
| 1201 | |||
| 1202 | static bool HasHomeLED(SDL_DriverSwitch_Context *ctx) | ||
| 1203 | { | ||
| 1204 | Uint16 vendor_id = ctx->device->vendor_id; | ||
| 1205 | Uint16 product_id = ctx->device->product_id; | ||
| 1206 | |||
| 1207 | // The Power A Nintendo Switch Pro controllers don't have a Home LED | ||
| 1208 | if (vendor_id == 0 && product_id == 0) { | ||
| 1209 | return false; | ||
| 1210 | } | ||
| 1211 | |||
| 1212 | // HORI Wireless Switch Pad | ||
| 1213 | if (vendor_id == 0x0f0d && product_id == 0x00f6) { | ||
| 1214 | return false; | ||
| 1215 | } | ||
| 1216 | |||
| 1217 | // Third party controllers don't have a home LED and will shut off if we try to set it | ||
| 1218 | if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_Unknown || | ||
| 1219 | ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_LicProController) { | ||
| 1220 | return false; | ||
| 1221 | } | ||
| 1222 | |||
| 1223 | // The Nintendo Online classic controllers don't have a Home LED | ||
| 1224 | if (vendor_id == USB_VENDOR_NINTENDO && | ||
| 1225 | ctx->m_eControllerType > k_eSwitchDeviceInfoControllerType_ProController) { | ||
| 1226 | return false; | ||
| 1227 | } | ||
| 1228 | |||
| 1229 | return true; | ||
| 1230 | } | ||
| 1231 | |||
| 1232 | static bool AlwaysUsesLabels(Uint16 vendor_id, Uint16 product_id, ESwitchDeviceInfoControllerType eControllerType) | ||
| 1233 | { | ||
| 1234 | // Some controllers don't have a diamond button configuration, so should always use labels | ||
| 1235 | if (SDL_IsJoystickGameCube(vendor_id, product_id)) { | ||
| 1236 | return true; | ||
| 1237 | } | ||
| 1238 | switch (eControllerType) { | ||
| 1239 | case k_eSwitchDeviceInfoControllerType_HVCLeft: | ||
| 1240 | case k_eSwitchDeviceInfoControllerType_HVCRight: | ||
| 1241 | case k_eSwitchDeviceInfoControllerType_NESLeft: | ||
| 1242 | case k_eSwitchDeviceInfoControllerType_NESRight: | ||
| 1243 | case k_eSwitchDeviceInfoControllerType_N64: | ||
| 1244 | case k_eSwitchDeviceInfoControllerType_SEGA_Genesis: | ||
| 1245 | return true; | ||
| 1246 | default: | ||
| 1247 | return false; | ||
| 1248 | } | ||
| 1249 | } | ||
| 1250 | |||
| 1251 | static void HIDAPI_DriverNintendoClassic_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 1252 | { | ||
| 1253 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, callback, userdata); | ||
| 1254 | } | ||
| 1255 | |||
| 1256 | static void HIDAPI_DriverNintendoClassic_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 1257 | { | ||
| 1258 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, callback, userdata); | ||
| 1259 | } | ||
| 1260 | |||
| 1261 | static bool HIDAPI_DriverNintendoClassic_IsEnabled(void) | ||
| 1262 | { | ||
| 1263 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); | ||
| 1264 | } | ||
| 1265 | |||
| 1266 | 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) | ||
| 1267 | { | ||
| 1268 | if (vendor_id == USB_VENDOR_NINTENDO) { | ||
| 1269 | if (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) { | ||
| 1270 | if (SDL_strncmp(name, "NES Controller", 14) == 0 || | ||
| 1271 | SDL_strncmp(name, "HVC Controller", 14) == 0) { | ||
| 1272 | return true; | ||
| 1273 | } | ||
| 1274 | } | ||
| 1275 | |||
| 1276 | if (product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER) { | ||
| 1277 | return true; | ||
| 1278 | } | ||
| 1279 | |||
| 1280 | if (product_id == USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER) { | ||
| 1281 | return true; | ||
| 1282 | } | ||
| 1283 | |||
| 1284 | if (product_id == USB_PRODUCT_NINTENDO_SNES_CONTROLLER) { | ||
| 1285 | return true; | ||
| 1286 | } | ||
| 1287 | } | ||
| 1288 | |||
| 1289 | return false; | ||
| 1290 | } | ||
| 1291 | |||
| 1292 | static void HIDAPI_DriverJoyCons_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 1293 | { | ||
| 1294 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, callback, userdata); | ||
| 1295 | } | ||
| 1296 | |||
| 1297 | static void HIDAPI_DriverJoyCons_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 1298 | { | ||
| 1299 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, callback, userdata); | ||
| 1300 | } | ||
| 1301 | |||
| 1302 | static bool HIDAPI_DriverJoyCons_IsEnabled(void) | ||
| 1303 | { | ||
| 1304 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); | ||
| 1305 | } | ||
| 1306 | |||
| 1307 | 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) | ||
| 1308 | { | ||
| 1309 | if (vendor_id == USB_VENDOR_NINTENDO) { | ||
| 1310 | if (product_id == USB_PRODUCT_NINTENDO_SWITCH_PRO && device && device->dev) { | ||
| 1311 | // This might be a Kinvoca Joy-Con that reports VID/PID as a Switch Pro controller | ||
| 1312 | ESwitchDeviceInfoControllerType eControllerType = ReadJoyConControllerType(device); | ||
| 1313 | if (eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || | ||
| 1314 | eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { | ||
| 1315 | return true; | ||
| 1316 | } | ||
| 1317 | } | ||
| 1318 | |||
| 1319 | if (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT || | ||
| 1320 | product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT || | ||
| 1321 | product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) { | ||
| 1322 | return true; | ||
| 1323 | } | ||
| 1324 | } | ||
| 1325 | return false; | ||
| 1326 | } | ||
| 1327 | |||
| 1328 | static void HIDAPI_DriverSwitch_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 1329 | { | ||
| 1330 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, callback, userdata); | ||
| 1331 | } | ||
| 1332 | |||
| 1333 | static void HIDAPI_DriverSwitch_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 1334 | { | ||
| 1335 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, callback, userdata); | ||
| 1336 | } | ||
| 1337 | |||
| 1338 | static bool HIDAPI_DriverSwitch_IsEnabled(void) | ||
| 1339 | { | ||
| 1340 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); | ||
| 1341 | } | ||
| 1342 | |||
| 1343 | 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) | ||
| 1344 | { | ||
| 1345 | /* The HORI Wireless Switch Pad enumerates as a HID device when connected via USB | ||
| 1346 | with the same VID/PID as when connected over Bluetooth but doesn't actually | ||
| 1347 | support communication over USB. The most reliable way to block this without allowing the | ||
| 1348 | controller to continually attempt to reconnect is to filter it out by manufacturer/product string. | ||
| 1349 | Note that the controller does have a different product string when connected over Bluetooth. | ||
| 1350 | */ | ||
| 1351 | if (SDL_strcmp(name, "HORI Wireless Switch Pad") == 0) { | ||
| 1352 | return false; | ||
| 1353 | } | ||
| 1354 | |||
| 1355 | // If it's handled by another driver, it's not handled here | ||
| 1356 | if (HIDAPI_DriverNintendoClassic_IsSupportedDevice(device, name, type, vendor_id, product_id, version, interface_number, interface_class, interface_subclass, interface_protocol) || | ||
| 1357 | HIDAPI_DriverJoyCons_IsSupportedDevice(device, name, type, vendor_id, product_id, version, interface_number, interface_class, interface_subclass, interface_protocol)) { | ||
| 1358 | return false; | ||
| 1359 | } | ||
| 1360 | |||
| 1361 | return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO); | ||
| 1362 | } | ||
| 1363 | |||
| 1364 | static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) | ||
| 1365 | { | ||
| 1366 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; | ||
| 1367 | |||
| 1368 | if (ctx->m_bInputOnly) { | ||
| 1369 | if (SDL_IsJoystickGameCube(device->vendor_id, device->product_id)) { | ||
| 1370 | device->type = SDL_GAMEPAD_TYPE_STANDARD; | ||
| 1371 | } | ||
| 1372 | } else { | ||
| 1373 | char serial[18]; | ||
| 1374 | |||
| 1375 | switch (ctx->m_eControllerType) { | ||
| 1376 | case k_eSwitchDeviceInfoControllerType_JoyConLeft: | ||
| 1377 | HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (L)"); | ||
| 1378 | HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT); | ||
| 1379 | device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT; | ||
| 1380 | break; | ||
| 1381 | case k_eSwitchDeviceInfoControllerType_JoyConRight: | ||
| 1382 | HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (R)"); | ||
| 1383 | HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT); | ||
| 1384 | device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT; | ||
| 1385 | break; | ||
| 1386 | case k_eSwitchDeviceInfoControllerType_ProController: | ||
| 1387 | case k_eSwitchDeviceInfoControllerType_LicProController: | ||
| 1388 | HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller"); | ||
| 1389 | HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_PRO); | ||
| 1390 | device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; | ||
| 1391 | break; | ||
| 1392 | case k_eSwitchDeviceInfoControllerType_HVCLeft: | ||
| 1393 | HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (1)"); | ||
| 1394 | device->type = SDL_GAMEPAD_TYPE_STANDARD; | ||
| 1395 | break; | ||
| 1396 | case k_eSwitchDeviceInfoControllerType_HVCRight: | ||
| 1397 | HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (2)"); | ||
| 1398 | device->type = SDL_GAMEPAD_TYPE_STANDARD; | ||
| 1399 | break; | ||
| 1400 | case k_eSwitchDeviceInfoControllerType_NESLeft: | ||
| 1401 | HIDAPI_SetDeviceName(device, "Nintendo NES Controller (L)"); | ||
| 1402 | device->type = SDL_GAMEPAD_TYPE_STANDARD; | ||
| 1403 | break; | ||
| 1404 | case k_eSwitchDeviceInfoControllerType_NESRight: | ||
| 1405 | HIDAPI_SetDeviceName(device, "Nintendo NES Controller (R)"); | ||
| 1406 | device->type = SDL_GAMEPAD_TYPE_STANDARD; | ||
| 1407 | break; | ||
| 1408 | case k_eSwitchDeviceInfoControllerType_SNES: | ||
| 1409 | HIDAPI_SetDeviceName(device, "Nintendo SNES Controller"); | ||
| 1410 | HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SNES_CONTROLLER); | ||
| 1411 | device->type = SDL_GAMEPAD_TYPE_STANDARD; | ||
| 1412 | break; | ||
| 1413 | case k_eSwitchDeviceInfoControllerType_N64: | ||
| 1414 | HIDAPI_SetDeviceName(device, "Nintendo N64 Controller"); | ||
| 1415 | HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_N64_CONTROLLER); | ||
| 1416 | device->type = SDL_GAMEPAD_TYPE_STANDARD; | ||
| 1417 | break; | ||
| 1418 | case k_eSwitchDeviceInfoControllerType_SEGA_Genesis: | ||
| 1419 | HIDAPI_SetDeviceName(device, "Nintendo SEGA Genesis Controller"); | ||
| 1420 | HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER); | ||
| 1421 | device->type = SDL_GAMEPAD_TYPE_STANDARD; | ||
| 1422 | break; | ||
| 1423 | case k_eSwitchDeviceInfoControllerType_Unknown: | ||
| 1424 | // We couldn't read the device info for this controller, might not be fully compliant | ||
| 1425 | if (device->vendor_id == USB_VENDOR_NINTENDO) { | ||
| 1426 | switch (device->product_id) { | ||
| 1427 | case USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT: | ||
| 1428 | ctx->m_eControllerType = k_eSwitchDeviceInfoControllerType_JoyConLeft; | ||
| 1429 | HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (L)"); | ||
| 1430 | device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT; | ||
| 1431 | break; | ||
| 1432 | case USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT: | ||
| 1433 | ctx->m_eControllerType = k_eSwitchDeviceInfoControllerType_JoyConRight; | ||
| 1434 | HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (R)"); | ||
| 1435 | device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT; | ||
| 1436 | break; | ||
| 1437 | case USB_PRODUCT_NINTENDO_SWITCH_PRO: | ||
| 1438 | ctx->m_eControllerType = k_eSwitchDeviceInfoControllerType_ProController; | ||
| 1439 | HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller"); | ||
| 1440 | device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; | ||
| 1441 | break; | ||
| 1442 | default: | ||
| 1443 | break; | ||
| 1444 | } | ||
| 1445 | } | ||
| 1446 | return; | ||
| 1447 | default: | ||
| 1448 | device->type = SDL_GAMEPAD_TYPE_STANDARD; | ||
| 1449 | break; | ||
| 1450 | } | ||
| 1451 | device->guid.data[15] = ctx->m_eControllerType; | ||
| 1452 | |||
| 1453 | (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", | ||
| 1454 | ctx->m_rgucMACAddress[0], | ||
| 1455 | ctx->m_rgucMACAddress[1], | ||
| 1456 | ctx->m_rgucMACAddress[2], | ||
| 1457 | ctx->m_rgucMACAddress[3], | ||
| 1458 | ctx->m_rgucMACAddress[4], | ||
| 1459 | ctx->m_rgucMACAddress[5]); | ||
| 1460 | HIDAPI_SetDeviceSerial(device, serial); | ||
| 1461 | } | ||
| 1462 | } | ||
| 1463 | |||
| 1464 | static bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device) | ||
| 1465 | { | ||
| 1466 | SDL_DriverSwitch_Context *ctx; | ||
| 1467 | |||
| 1468 | ctx = (SDL_DriverSwitch_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 1469 | if (!ctx) { | ||
| 1470 | return false; | ||
| 1471 | } | ||
| 1472 | ctx->device = device; | ||
| 1473 | device->context = ctx; | ||
| 1474 | |||
| 1475 | ctx->m_nMaxWriteAttempts = GetMaxWriteAttempts(device); | ||
| 1476 | ctx->m_bSyncWrite = true; | ||
| 1477 | |||
| 1478 | // Find out whether or not we can send output reports | ||
| 1479 | ctx->m_bInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(device->vendor_id, device->product_id); | ||
| 1480 | if (!ctx->m_bInputOnly) { | ||
| 1481 | // Initialize rumble data, important for reading device info on the MOBAPAD M073 | ||
| 1482 | SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); | ||
| 1483 | SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); | ||
| 1484 | |||
| 1485 | BReadDeviceInfo(ctx); | ||
| 1486 | } | ||
| 1487 | UpdateDeviceIdentity(device); | ||
| 1488 | |||
| 1489 | // Prefer the USB device over the Bluetooth device | ||
| 1490 | if (device->is_bluetooth) { | ||
| 1491 | if (HIDAPI_HasConnectedUSBDevice(device->serial)) { | ||
| 1492 | return true; | ||
| 1493 | } | ||
| 1494 | } else { | ||
| 1495 | HIDAPI_DisconnectBluetoothDevice(device->serial); | ||
| 1496 | } | ||
| 1497 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 1498 | } | ||
| 1499 | |||
| 1500 | static int HIDAPI_DriverSwitch_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 1501 | { | ||
| 1502 | return -1; | ||
| 1503 | } | ||
| 1504 | |||
| 1505 | static void HIDAPI_DriverSwitch_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 1506 | { | ||
| 1507 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; | ||
| 1508 | |||
| 1509 | if (!ctx->joystick) { | ||
| 1510 | return; | ||
| 1511 | } | ||
| 1512 | |||
| 1513 | ctx->m_nPlayerIndex = player_index; | ||
| 1514 | |||
| 1515 | UpdateSlotLED(ctx); | ||
| 1516 | } | ||
| 1517 | |||
| 1518 | static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1519 | { | ||
| 1520 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; | ||
| 1521 | |||
| 1522 | SDL_AssertJoysticksLocked(); | ||
| 1523 | |||
| 1524 | ctx->joystick = joystick; | ||
| 1525 | |||
| 1526 | ctx->m_bSyncWrite = true; | ||
| 1527 | |||
| 1528 | if (!ctx->m_bInputOnly) { | ||
| 1529 | #ifdef SDL_PLATFORM_MACOS | ||
| 1530 | // Wait for the OS to finish its handshake with the controller | ||
| 1531 | SDL_Delay(250); | ||
| 1532 | #endif | ||
| 1533 | GetInitialInputMode(ctx); | ||
| 1534 | ctx->m_nCurrentInputMode = ctx->m_nInitialInputMode; | ||
| 1535 | |||
| 1536 | // Initialize rumble data | ||
| 1537 | SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); | ||
| 1538 | SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); | ||
| 1539 | |||
| 1540 | if (!device->is_bluetooth) { | ||
| 1541 | if (!BTrySetupUSB(ctx)) { | ||
| 1542 | SDL_SetError("Couldn't setup USB mode"); | ||
| 1543 | return false; | ||
| 1544 | } | ||
| 1545 | } | ||
| 1546 | |||
| 1547 | if (!LoadStickCalibration(ctx)) { | ||
| 1548 | SDL_SetError("Couldn't load stick calibration"); | ||
| 1549 | return false; | ||
| 1550 | } | ||
| 1551 | |||
| 1552 | if (ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_HVCLeft && | ||
| 1553 | ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_HVCRight && | ||
| 1554 | ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESLeft && | ||
| 1555 | ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESRight && | ||
| 1556 | ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SNES && | ||
| 1557 | ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_N64 && | ||
| 1558 | ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis) { | ||
| 1559 | if (LoadIMUCalibration(ctx)) { | ||
| 1560 | ctx->m_bSensorsSupported = true; | ||
| 1561 | } | ||
| 1562 | } | ||
| 1563 | |||
| 1564 | // Enable vibration | ||
| 1565 | SetVibrationEnabled(ctx, 1); | ||
| 1566 | |||
| 1567 | // Set desired input mode | ||
| 1568 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, | ||
| 1569 | SDL_EnhancedReportsChanged, ctx); | ||
| 1570 | |||
| 1571 | // Start sending USB reports | ||
| 1572 | if (!device->is_bluetooth) { | ||
| 1573 | // ForceUSB doesn't generate an ACK, so don't wait for a reply | ||
| 1574 | if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, false)) { | ||
| 1575 | SDL_SetError("Couldn't start USB reports"); | ||
| 1576 | return false; | ||
| 1577 | } | ||
| 1578 | } | ||
| 1579 | |||
| 1580 | // Set the LED state | ||
| 1581 | if (HasHomeLED(ctx)) { | ||
| 1582 | if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || | ||
| 1583 | ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { | ||
| 1584 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED, | ||
| 1585 | SDL_HomeLEDHintChanged, ctx); | ||
| 1586 | } else { | ||
| 1587 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, | ||
| 1588 | SDL_HomeLEDHintChanged, ctx); | ||
| 1589 | } | ||
| 1590 | } | ||
| 1591 | } | ||
| 1592 | |||
| 1593 | if (AlwaysUsesLabels(device->vendor_id, device->product_id, ctx->m_eControllerType)) { | ||
| 1594 | ctx->m_bUseButtonLabels = true; | ||
| 1595 | } | ||
| 1596 | |||
| 1597 | // Initialize player index (needed for setting LEDs) | ||
| 1598 | ctx->m_nPlayerIndex = SDL_GetJoystickPlayerIndex(joystick); | ||
| 1599 | ctx->m_bPlayerLights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, true); | ||
| 1600 | UpdateSlotLED(ctx); | ||
| 1601 | |||
| 1602 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, | ||
| 1603 | SDL_PlayerLEDHintChanged, ctx); | ||
| 1604 | |||
| 1605 | // Initialize the joystick capabilities | ||
| 1606 | joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH_BUTTONS; | ||
| 1607 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 1608 | joystick->nhats = 1; | ||
| 1609 | |||
| 1610 | // Set up for input | ||
| 1611 | ctx->m_bSyncWrite = false; | ||
| 1612 | ctx->m_ulLastIMUReset = ctx->m_ulLastInput = SDL_GetTicks(); | ||
| 1613 | ctx->m_ulIMUUpdateIntervalNS = SDL_MS_TO_NS(5); // Start off at 5 ms update rate | ||
| 1614 | |||
| 1615 | // Set up for vertical mode | ||
| 1616 | ctx->m_bVerticalMode = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false); | ||
| 1617 | |||
| 1618 | return true; | ||
| 1619 | } | ||
| 1620 | |||
| 1621 | static bool HIDAPI_DriverSwitch_ActuallyRumbleJoystick(SDL_DriverSwitch_Context *ctx, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 1622 | { | ||
| 1623 | /* Experimentally determined rumble values. These will only matter on some controllers as tested ones | ||
| 1624 | * seem to disregard these and just use any non-zero rumble values as a binary flag for constant rumble | ||
| 1625 | * | ||
| 1626 | * More information about these values can be found here: | ||
| 1627 | * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md | ||
| 1628 | */ | ||
| 1629 | const Uint16 k_usHighFreq = 0x0074; | ||
| 1630 | const Uint8 k_ucHighFreqAmp = EncodeRumbleHighAmplitude(high_frequency_rumble); | ||
| 1631 | const Uint8 k_ucLowFreq = 0x3D; | ||
| 1632 | const Uint16 k_usLowFreqAmp = EncodeRumbleLowAmplitude(low_frequency_rumble); | ||
| 1633 | |||
| 1634 | if (low_frequency_rumble || high_frequency_rumble) { | ||
| 1635 | EncodeRumble(&ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); | ||
| 1636 | EncodeRumble(&ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); | ||
| 1637 | } else { | ||
| 1638 | SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); | ||
| 1639 | SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); | ||
| 1640 | } | ||
| 1641 | |||
| 1642 | ctx->m_bRumbleActive = (low_frequency_rumble || high_frequency_rumble); | ||
| 1643 | |||
| 1644 | if (!WriteRumble(ctx)) { | ||
| 1645 | return SDL_SetError("Couldn't send rumble packet"); | ||
| 1646 | } | ||
| 1647 | return true; | ||
| 1648 | } | ||
| 1649 | |||
| 1650 | static bool HIDAPI_DriverSwitch_SendPendingRumble(SDL_DriverSwitch_Context *ctx) | ||
| 1651 | { | ||
| 1652 | if (SDL_GetTicks() < (ctx->m_ulRumbleSent + RUMBLE_WRITE_FREQUENCY_MS)) { | ||
| 1653 | return true; | ||
| 1654 | } | ||
| 1655 | |||
| 1656 | if (ctx->m_bRumblePending) { | ||
| 1657 | Uint16 low_frequency_rumble = (Uint16)(ctx->m_unRumblePending >> 16); | ||
| 1658 | Uint16 high_frequency_rumble = (Uint16)ctx->m_unRumblePending; | ||
| 1659 | |||
| 1660 | #ifdef DEBUG_RUMBLE | ||
| 1661 | SDL_Log("Sent pending rumble %d/%d, %d ms after previous rumble", low_frequency_rumble, high_frequency_rumble, SDL_GetTicks() - ctx->m_ulRumbleSent); | ||
| 1662 | #endif | ||
| 1663 | ctx->m_bRumblePending = false; | ||
| 1664 | ctx->m_unRumblePending = 0; | ||
| 1665 | |||
| 1666 | return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, low_frequency_rumble, high_frequency_rumble); | ||
| 1667 | } | ||
| 1668 | |||
| 1669 | if (ctx->m_bRumbleZeroPending) { | ||
| 1670 | ctx->m_bRumbleZeroPending = false; | ||
| 1671 | |||
| 1672 | #ifdef DEBUG_RUMBLE | ||
| 1673 | SDL_Log("Sent pending zero rumble, %d ms after previous rumble", SDL_GetTicks() - ctx->m_ulRumbleSent); | ||
| 1674 | #endif | ||
| 1675 | return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, 0, 0); | ||
| 1676 | } | ||
| 1677 | |||
| 1678 | return true; | ||
| 1679 | } | ||
| 1680 | |||
| 1681 | static bool HIDAPI_DriverSwitch_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 1682 | { | ||
| 1683 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; | ||
| 1684 | |||
| 1685 | if (ctx->m_bInputOnly) { | ||
| 1686 | return SDL_Unsupported(); | ||
| 1687 | } | ||
| 1688 | |||
| 1689 | if (device->parent) { | ||
| 1690 | if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { | ||
| 1691 | // Just handle low frequency rumble | ||
| 1692 | high_frequency_rumble = 0; | ||
| 1693 | } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { | ||
| 1694 | // Just handle high frequency rumble | ||
| 1695 | low_frequency_rumble = 0; | ||
| 1696 | } | ||
| 1697 | } | ||
| 1698 | |||
| 1699 | if (ctx->m_bRumblePending) { | ||
| 1700 | if (!HIDAPI_DriverSwitch_SendPendingRumble(ctx)) { | ||
| 1701 | return false; | ||
| 1702 | } | ||
| 1703 | } | ||
| 1704 | |||
| 1705 | if (SDL_GetTicks() < (ctx->m_ulRumbleSent + RUMBLE_WRITE_FREQUENCY_MS)) { | ||
| 1706 | if (low_frequency_rumble || high_frequency_rumble) { | ||
| 1707 | Uint32 unRumblePending = ((Uint32)low_frequency_rumble << 16) | high_frequency_rumble; | ||
| 1708 | |||
| 1709 | // Keep the highest rumble intensity in the given interval | ||
| 1710 | if (unRumblePending > ctx->m_unRumblePending) { | ||
| 1711 | ctx->m_unRumblePending = unRumblePending; | ||
| 1712 | } | ||
| 1713 | ctx->m_bRumblePending = true; | ||
| 1714 | ctx->m_bRumbleZeroPending = false; | ||
| 1715 | } else { | ||
| 1716 | // When rumble is complete, turn it off | ||
| 1717 | ctx->m_bRumbleZeroPending = true; | ||
| 1718 | } | ||
| 1719 | return true; | ||
| 1720 | } | ||
| 1721 | |||
| 1722 | #ifdef DEBUG_RUMBLE | ||
| 1723 | SDL_Log("Sent rumble %d/%d", low_frequency_rumble, high_frequency_rumble); | ||
| 1724 | #endif | ||
| 1725 | |||
| 1726 | return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, low_frequency_rumble, high_frequency_rumble); | ||
| 1727 | } | ||
| 1728 | |||
| 1729 | static bool HIDAPI_DriverSwitch_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 1730 | { | ||
| 1731 | return SDL_Unsupported(); | ||
| 1732 | } | ||
| 1733 | |||
| 1734 | static Uint32 HIDAPI_DriverSwitch_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1735 | { | ||
| 1736 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; | ||
| 1737 | Uint32 result = 0; | ||
| 1738 | |||
| 1739 | if (ctx->m_bPlayerLights && !ctx->m_bInputOnly) { | ||
| 1740 | result |= SDL_JOYSTICK_CAP_PLAYER_LED; | ||
| 1741 | } | ||
| 1742 | |||
| 1743 | if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_ProController && !ctx->m_bInputOnly) { | ||
| 1744 | // Doesn't have an RGB LED, so don't return SDL_JOYSTICK_CAP_RGB_LED here | ||
| 1745 | result |= SDL_JOYSTICK_CAP_RUMBLE; | ||
| 1746 | } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || | ||
| 1747 | ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { | ||
| 1748 | result |= SDL_JOYSTICK_CAP_RUMBLE; | ||
| 1749 | } | ||
| 1750 | return result; | ||
| 1751 | } | ||
| 1752 | |||
| 1753 | static bool HIDAPI_DriverSwitch_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 1754 | { | ||
| 1755 | return SDL_Unsupported(); | ||
| 1756 | } | ||
| 1757 | |||
| 1758 | static bool HIDAPI_DriverSwitch_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 1759 | { | ||
| 1760 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; | ||
| 1761 | |||
| 1762 | if (size == sizeof(SwitchCommonOutputPacket_t)) { | ||
| 1763 | const SwitchCommonOutputPacket_t *packet = (SwitchCommonOutputPacket_t *)data; | ||
| 1764 | |||
| 1765 | if (packet->ucPacketType != k_eSwitchOutputReportIDs_Rumble) { | ||
| 1766 | return SDL_SetError("Unknown Nintendo Switch Pro effect type"); | ||
| 1767 | } | ||
| 1768 | |||
| 1769 | SDL_copyp(&ctx->m_RumblePacket.rumbleData[0], &packet->rumbleData[0]); | ||
| 1770 | SDL_copyp(&ctx->m_RumblePacket.rumbleData[1], &packet->rumbleData[1]); | ||
| 1771 | if (!WriteRumble(ctx)) { | ||
| 1772 | return false; | ||
| 1773 | } | ||
| 1774 | |||
| 1775 | // This overwrites any internal rumble | ||
| 1776 | ctx->m_bRumblePending = false; | ||
| 1777 | ctx->m_bRumbleZeroPending = false; | ||
| 1778 | return true; | ||
| 1779 | } else if (size >= 2 && size <= 256) { | ||
| 1780 | const Uint8 *payload = (const Uint8 *)data; | ||
| 1781 | ESwitchSubcommandIDs cmd = (ESwitchSubcommandIDs)payload[0]; | ||
| 1782 | |||
| 1783 | if (cmd == k_eSwitchSubcommandIDs_SetInputReportMode && !device->is_bluetooth) { | ||
| 1784 | // Going into simple mode over USB disables input reports, so don't do that | ||
| 1785 | return true; | ||
| 1786 | } | ||
| 1787 | if (cmd == k_eSwitchSubcommandIDs_SetHomeLight && !HasHomeLED(ctx)) { | ||
| 1788 | // Setting the home LED when it's not supported can cause the controller to reset | ||
| 1789 | return true; | ||
| 1790 | } | ||
| 1791 | |||
| 1792 | if (!WriteSubcommand(ctx, cmd, &payload[1], (Uint8)(size - 1), NULL)) { | ||
| 1793 | return false; | ||
| 1794 | } | ||
| 1795 | return true; | ||
| 1796 | } | ||
| 1797 | return SDL_Unsupported(); | ||
| 1798 | } | ||
| 1799 | |||
| 1800 | static bool HIDAPI_DriverSwitch_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 1801 | { | ||
| 1802 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; | ||
| 1803 | |||
| 1804 | UpdateEnhancedModeOnApplicationUsage(ctx); | ||
| 1805 | |||
| 1806 | if (!ctx->m_bSensorsSupported || (enabled && !ctx->m_bEnhancedMode)) { | ||
| 1807 | return SDL_Unsupported(); | ||
| 1808 | } | ||
| 1809 | |||
| 1810 | ctx->m_bReportSensors = enabled; | ||
| 1811 | ctx->m_unIMUSamples = 0; | ||
| 1812 | ctx->m_ulIMUSampleTimestampNS = SDL_GetTicksNS(); | ||
| 1813 | |||
| 1814 | UpdateInputMode(ctx); | ||
| 1815 | SetIMUEnabled(ctx, enabled); | ||
| 1816 | |||
| 1817 | return true; | ||
| 1818 | } | ||
| 1819 | |||
| 1820 | static void HandleInputOnlyControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchInputOnlyControllerStatePacket_t *packet) | ||
| 1821 | { | ||
| 1822 | Sint16 axis; | ||
| 1823 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 1824 | |||
| 1825 | if (packet->rgucButtons[0] != ctx->m_lastInputOnlyState.rgucButtons[0]) { | ||
| 1826 | Uint8 data = packet->rgucButtons[0]; | ||
| 1827 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x02) != 0)); | ||
| 1828 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x04) != 0)); | ||
| 1829 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x01) != 0)); | ||
| 1830 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0)); | ||
| 1831 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0)); | ||
| 1832 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0)); | ||
| 1833 | } | ||
| 1834 | |||
| 1835 | if (packet->rgucButtons[1] != ctx->m_lastInputOnlyState.rgucButtons[1]) { | ||
| 1836 | Uint8 data = packet->rgucButtons[1]; | ||
| 1837 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0)); | ||
| 1838 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); | ||
| 1839 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0)); | ||
| 1840 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x08) != 0)); | ||
| 1841 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); | ||
| 1842 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0)); | ||
| 1843 | } | ||
| 1844 | |||
| 1845 | if (packet->ucStickHat != ctx->m_lastInputOnlyState.ucStickHat) { | ||
| 1846 | Uint8 hat; | ||
| 1847 | |||
| 1848 | switch (packet->ucStickHat) { | ||
| 1849 | case 0: | ||
| 1850 | hat = SDL_HAT_UP; | ||
| 1851 | break; | ||
| 1852 | case 1: | ||
| 1853 | hat = SDL_HAT_RIGHTUP; | ||
| 1854 | break; | ||
| 1855 | case 2: | ||
| 1856 | hat = SDL_HAT_RIGHT; | ||
| 1857 | break; | ||
| 1858 | case 3: | ||
| 1859 | hat = SDL_HAT_RIGHTDOWN; | ||
| 1860 | break; | ||
| 1861 | case 4: | ||
| 1862 | hat = SDL_HAT_DOWN; | ||
| 1863 | break; | ||
| 1864 | case 5: | ||
| 1865 | hat = SDL_HAT_LEFTDOWN; | ||
| 1866 | break; | ||
| 1867 | case 6: | ||
| 1868 | hat = SDL_HAT_LEFT; | ||
| 1869 | break; | ||
| 1870 | case 7: | ||
| 1871 | hat = SDL_HAT_LEFTUP; | ||
| 1872 | break; | ||
| 1873 | default: | ||
| 1874 | hat = SDL_HAT_CENTERED; | ||
| 1875 | break; | ||
| 1876 | } | ||
| 1877 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 1878 | } | ||
| 1879 | |||
| 1880 | axis = (packet->rgucButtons[0] & 0x40) ? 32767 : -32768; | ||
| 1881 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 1882 | |||
| 1883 | axis = (packet->rgucButtons[0] & 0x80) ? 32767 : -32768; | ||
| 1884 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 1885 | |||
| 1886 | if (packet->rgucJoystickLeft[0] != ctx->m_lastInputOnlyState.rgucJoystickLeft[0]) { | ||
| 1887 | axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickLeft[0], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16); | ||
| 1888 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 1889 | } | ||
| 1890 | |||
| 1891 | if (packet->rgucJoystickLeft[1] != ctx->m_lastInputOnlyState.rgucJoystickLeft[1]) { | ||
| 1892 | axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickLeft[1], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16); | ||
| 1893 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 1894 | } | ||
| 1895 | |||
| 1896 | if (packet->rgucJoystickRight[0] != ctx->m_lastInputOnlyState.rgucJoystickRight[0]) { | ||
| 1897 | axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickRight[0], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16); | ||
| 1898 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 1899 | } | ||
| 1900 | |||
| 1901 | if (packet->rgucJoystickRight[1] != ctx->m_lastInputOnlyState.rgucJoystickRight[1]) { | ||
| 1902 | axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickRight[1], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16); | ||
| 1903 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 1904 | } | ||
| 1905 | |||
| 1906 | ctx->m_lastInputOnlyState = *packet; | ||
| 1907 | } | ||
| 1908 | |||
| 1909 | static void HandleCombinedSimpleControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet) | ||
| 1910 | { | ||
| 1911 | if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { | ||
| 1912 | Uint8 data = packet->rgucButtons[0]; | ||
| 1913 | Uint8 hat = 0; | ||
| 1914 | |||
| 1915 | if (data & 0x01) { | ||
| 1916 | hat |= SDL_HAT_LEFT; | ||
| 1917 | } | ||
| 1918 | if (data & 0x02) { | ||
| 1919 | hat |= SDL_HAT_DOWN; | ||
| 1920 | } | ||
| 1921 | if (data & 0x04) { | ||
| 1922 | hat |= SDL_HAT_UP; | ||
| 1923 | } | ||
| 1924 | if (data & 0x08) { | ||
| 1925 | hat |= SDL_HAT_RIGHT; | ||
| 1926 | } | ||
| 1927 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 1928 | |||
| 1929 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x10) != 0)); | ||
| 1930 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x20) != 0)); | ||
| 1931 | } | ||
| 1932 | |||
| 1933 | if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) { | ||
| 1934 | Uint8 data = packet->rgucButtons[1]; | ||
| 1935 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0)); | ||
| 1936 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0)); | ||
| 1937 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0)); | ||
| 1938 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x40) != 0)); | ||
| 1939 | } | ||
| 1940 | |||
| 1941 | Sint16 axis = (packet->rgucButtons[1] & 0x80) ? 32767 : -32768; | ||
| 1942 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 1943 | |||
| 1944 | if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) { | ||
| 1945 | switch (packet->ucStickHat) { | ||
| 1946 | case 0: | ||
| 1947 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); | ||
| 1948 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); | ||
| 1949 | break; | ||
| 1950 | case 1: | ||
| 1951 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); | ||
| 1952 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); | ||
| 1953 | break; | ||
| 1954 | case 2: | ||
| 1955 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); | ||
| 1956 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); | ||
| 1957 | break; | ||
| 1958 | case 3: | ||
| 1959 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); | ||
| 1960 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); | ||
| 1961 | break; | ||
| 1962 | case 4: | ||
| 1963 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); | ||
| 1964 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); | ||
| 1965 | break; | ||
| 1966 | case 5: | ||
| 1967 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); | ||
| 1968 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); | ||
| 1969 | break; | ||
| 1970 | case 6: | ||
| 1971 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); | ||
| 1972 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); | ||
| 1973 | break; | ||
| 1974 | case 7: | ||
| 1975 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); | ||
| 1976 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); | ||
| 1977 | break; | ||
| 1978 | default: | ||
| 1979 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); | ||
| 1980 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); | ||
| 1981 | break; | ||
| 1982 | } | ||
| 1983 | } | ||
| 1984 | } | ||
| 1985 | |||
| 1986 | static void HandleCombinedSimpleControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet) | ||
| 1987 | { | ||
| 1988 | if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { | ||
| 1989 | Uint8 data = packet->rgucButtons[0]; | ||
| 1990 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x01) != 0)); | ||
| 1991 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x02) != 0)); | ||
| 1992 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x04) != 0)); | ||
| 1993 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x08) != 0)); | ||
| 1994 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x10) != 0)); | ||
| 1995 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x20) != 0)); | ||
| 1996 | } | ||
| 1997 | |||
| 1998 | if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) { | ||
| 1999 | Uint8 data = packet->rgucButtons[1]; | ||
| 2000 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); | ||
| 2001 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x08) != 0)); | ||
| 2002 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); | ||
| 2003 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x40) != 0)); | ||
| 2004 | } | ||
| 2005 | |||
| 2006 | Sint16 axis = (packet->rgucButtons[1] & 0x80) ? 32767 : -32768; | ||
| 2007 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 2008 | |||
| 2009 | if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) { | ||
| 2010 | switch (packet->ucStickHat) { | ||
| 2011 | case 0: | ||
| 2012 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MIN); | ||
| 2013 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, 0); | ||
| 2014 | break; | ||
| 2015 | case 1: | ||
| 2016 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MIN); | ||
| 2017 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MIN); | ||
| 2018 | break; | ||
| 2019 | case 2: | ||
| 2020 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, 0); | ||
| 2021 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MIN); | ||
| 2022 | break; | ||
| 2023 | case 3: | ||
| 2024 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MAX); | ||
| 2025 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MIN); | ||
| 2026 | break; | ||
| 2027 | case 4: | ||
| 2028 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MAX); | ||
| 2029 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, 0); | ||
| 2030 | break; | ||
| 2031 | case 5: | ||
| 2032 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MAX); | ||
| 2033 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MAX); | ||
| 2034 | break; | ||
| 2035 | case 6: | ||
| 2036 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, 0); | ||
| 2037 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MAX); | ||
| 2038 | break; | ||
| 2039 | case 7: | ||
| 2040 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MIN); | ||
| 2041 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MAX); | ||
| 2042 | break; | ||
| 2043 | default: | ||
| 2044 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, 0); | ||
| 2045 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, 0); | ||
| 2046 | break; | ||
| 2047 | } | ||
| 2048 | } | ||
| 2049 | } | ||
| 2050 | |||
| 2051 | static void HandleMiniSimpleControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet) | ||
| 2052 | { | ||
| 2053 | if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { | ||
| 2054 | Uint8 data = packet->rgucButtons[0]; | ||
| 2055 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x01) != 0)); | ||
| 2056 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0)); | ||
| 2057 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0)); | ||
| 2058 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0)); | ||
| 2059 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0)); | ||
| 2060 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0)); | ||
| 2061 | } | ||
| 2062 | |||
| 2063 | if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) { | ||
| 2064 | Uint8 data = packet->rgucButtons[1]; | ||
| 2065 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x01) != 0)); | ||
| 2066 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0)); | ||
| 2067 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x20) != 0)); | ||
| 2068 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x40) != 0)); | ||
| 2069 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x80) != 0)); | ||
| 2070 | } | ||
| 2071 | |||
| 2072 | if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) { | ||
| 2073 | switch (packet->ucStickHat) { | ||
| 2074 | case 0: | ||
| 2075 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); | ||
| 2076 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); | ||
| 2077 | break; | ||
| 2078 | case 1: | ||
| 2079 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); | ||
| 2080 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); | ||
| 2081 | break; | ||
| 2082 | case 2: | ||
| 2083 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); | ||
| 2084 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); | ||
| 2085 | break; | ||
| 2086 | case 3: | ||
| 2087 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); | ||
| 2088 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); | ||
| 2089 | break; | ||
| 2090 | case 4: | ||
| 2091 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); | ||
| 2092 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); | ||
| 2093 | break; | ||
| 2094 | case 5: | ||
| 2095 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); | ||
| 2096 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); | ||
| 2097 | break; | ||
| 2098 | case 6: | ||
| 2099 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); | ||
| 2100 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); | ||
| 2101 | break; | ||
| 2102 | case 7: | ||
| 2103 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); | ||
| 2104 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); | ||
| 2105 | break; | ||
| 2106 | default: | ||
| 2107 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); | ||
| 2108 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); | ||
| 2109 | break; | ||
| 2110 | } | ||
| 2111 | } | ||
| 2112 | } | ||
| 2113 | |||
| 2114 | static void HandleMiniSimpleControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet) | ||
| 2115 | { | ||
| 2116 | if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { | ||
| 2117 | Uint8 data = packet->rgucButtons[0]; | ||
| 2118 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x01) != 0)); | ||
| 2119 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0)); | ||
| 2120 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0)); | ||
| 2121 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0)); | ||
| 2122 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0)); | ||
| 2123 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0)); | ||
| 2124 | } | ||
| 2125 | |||
| 2126 | if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) { | ||
| 2127 | Uint8 data = packet->rgucButtons[1]; | ||
| 2128 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); | ||
| 2129 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0)); | ||
| 2130 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); | ||
| 2131 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0)); | ||
| 2132 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x40) != 0)); | ||
| 2133 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x80) != 0)); | ||
| 2134 | } | ||
| 2135 | |||
| 2136 | if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) { | ||
| 2137 | switch (packet->ucStickHat) { | ||
| 2138 | case 0: | ||
| 2139 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); | ||
| 2140 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); | ||
| 2141 | break; | ||
| 2142 | case 1: | ||
| 2143 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); | ||
| 2144 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); | ||
| 2145 | break; | ||
| 2146 | case 2: | ||
| 2147 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); | ||
| 2148 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); | ||
| 2149 | break; | ||
| 2150 | case 3: | ||
| 2151 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX); | ||
| 2152 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); | ||
| 2153 | break; | ||
| 2154 | case 4: | ||
| 2155 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); | ||
| 2156 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); | ||
| 2157 | break; | ||
| 2158 | case 5: | ||
| 2159 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); | ||
| 2160 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX); | ||
| 2161 | break; | ||
| 2162 | case 6: | ||
| 2163 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); | ||
| 2164 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); | ||
| 2165 | break; | ||
| 2166 | case 7: | ||
| 2167 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN); | ||
| 2168 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN); | ||
| 2169 | break; | ||
| 2170 | default: | ||
| 2171 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0); | ||
| 2172 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0); | ||
| 2173 | break; | ||
| 2174 | } | ||
| 2175 | } | ||
| 2176 | } | ||
| 2177 | |||
| 2178 | static void HandleSimpleControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet) | ||
| 2179 | { | ||
| 2180 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 2181 | |||
| 2182 | if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { | ||
| 2183 | if (ctx->device->parent || ctx->m_bVerticalMode) { | ||
| 2184 | HandleCombinedSimpleControllerStateL(timestamp, joystick, ctx, packet); | ||
| 2185 | } else { | ||
| 2186 | HandleMiniSimpleControllerStateL(timestamp, joystick, ctx, packet); | ||
| 2187 | } | ||
| 2188 | } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { | ||
| 2189 | if (ctx->device->parent || ctx->m_bVerticalMode) { | ||
| 2190 | HandleCombinedSimpleControllerStateR(timestamp, joystick, ctx, packet); | ||
| 2191 | } else { | ||
| 2192 | HandleMiniSimpleControllerStateR(timestamp, joystick, ctx, packet); | ||
| 2193 | } | ||
| 2194 | } else { | ||
| 2195 | Sint16 axis; | ||
| 2196 | |||
| 2197 | if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { | ||
| 2198 | Uint8 data = packet->rgucButtons[0]; | ||
| 2199 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x01) != 0)); | ||
| 2200 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0)); | ||
| 2201 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0)); | ||
| 2202 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0)); | ||
| 2203 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0)); | ||
| 2204 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0)); | ||
| 2205 | } | ||
| 2206 | |||
| 2207 | if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) { | ||
| 2208 | Uint8 data = packet->rgucButtons[1]; | ||
| 2209 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0)); | ||
| 2210 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); | ||
| 2211 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0)); | ||
| 2212 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x08) != 0)); | ||
| 2213 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); | ||
| 2214 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0)); | ||
| 2215 | } | ||
| 2216 | |||
| 2217 | if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) { | ||
| 2218 | Uint8 hat; | ||
| 2219 | |||
| 2220 | switch (packet->ucStickHat) { | ||
| 2221 | case 0: | ||
| 2222 | hat = SDL_HAT_UP; | ||
| 2223 | break; | ||
| 2224 | case 1: | ||
| 2225 | hat = SDL_HAT_RIGHTUP; | ||
| 2226 | break; | ||
| 2227 | case 2: | ||
| 2228 | hat = SDL_HAT_RIGHT; | ||
| 2229 | break; | ||
| 2230 | case 3: | ||
| 2231 | hat = SDL_HAT_RIGHTDOWN; | ||
| 2232 | break; | ||
| 2233 | case 4: | ||
| 2234 | hat = SDL_HAT_DOWN; | ||
| 2235 | break; | ||
| 2236 | case 5: | ||
| 2237 | hat = SDL_HAT_LEFTDOWN; | ||
| 2238 | break; | ||
| 2239 | case 6: | ||
| 2240 | hat = SDL_HAT_LEFT; | ||
| 2241 | break; | ||
| 2242 | case 7: | ||
| 2243 | hat = SDL_HAT_LEFTUP; | ||
| 2244 | break; | ||
| 2245 | default: | ||
| 2246 | hat = SDL_HAT_CENTERED; | ||
| 2247 | break; | ||
| 2248 | } | ||
| 2249 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 2250 | } | ||
| 2251 | |||
| 2252 | axis = (packet->rgucButtons[0] & 0x40) ? 32767 : -32768; | ||
| 2253 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 2254 | |||
| 2255 | axis = ((packet->rgucButtons[0] & 0x80) || (packet->rgucButtons[1] & 0x80)) ? 32767 : -32768; | ||
| 2256 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 2257 | |||
| 2258 | axis = ApplySimpleStickCalibration(ctx, 0, 0, packet->sJoystickLeft[0]); | ||
| 2259 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 2260 | |||
| 2261 | axis = ApplySimpleStickCalibration(ctx, 0, 1, packet->sJoystickLeft[1]); | ||
| 2262 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 2263 | |||
| 2264 | axis = ApplySimpleStickCalibration(ctx, 1, 0, packet->sJoystickRight[0]); | ||
| 2265 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 2266 | |||
| 2267 | axis = ApplySimpleStickCalibration(ctx, 1, 1, packet->sJoystickRight[1]); | ||
| 2268 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 2269 | } | ||
| 2270 | |||
| 2271 | ctx->m_lastSimpleState = *packet; | ||
| 2272 | } | ||
| 2273 | |||
| 2274 | static void SendSensorUpdate(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SDL_SensorType type, Uint64 sensor_timestamp, const Sint16 *values) | ||
| 2275 | { | ||
| 2276 | float data[3]; | ||
| 2277 | |||
| 2278 | /* Note the order of components has been shuffled to match PlayStation controllers, | ||
| 2279 | * since that's our de facto standard from already supporting those controllers, and | ||
| 2280 | * users will want consistent axis mappings across devices. | ||
| 2281 | */ | ||
| 2282 | if (type == SDL_SENSOR_GYRO || type == SDL_SENSOR_GYRO_L || type == SDL_SENSOR_GYRO_R) { | ||
| 2283 | data[0] = -(ctx->m_IMUScaleData.fGyroScaleY * (float)values[1]); | ||
| 2284 | data[1] = ctx->m_IMUScaleData.fGyroScaleZ * (float)values[2]; | ||
| 2285 | data[2] = -(ctx->m_IMUScaleData.fGyroScaleX * (float)values[0]); | ||
| 2286 | } else { | ||
| 2287 | data[0] = -(ctx->m_IMUScaleData.fAccelScaleY * (float)values[1]); | ||
| 2288 | data[1] = ctx->m_IMUScaleData.fAccelScaleZ * (float)values[2]; | ||
| 2289 | data[2] = -(ctx->m_IMUScaleData.fAccelScaleX * (float)values[0]); | ||
| 2290 | } | ||
| 2291 | |||
| 2292 | // Right Joy-Con flips some axes, so let's flip them back for consistency | ||
| 2293 | if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { | ||
| 2294 | data[0] = -data[0]; | ||
| 2295 | data[1] = -data[1]; | ||
| 2296 | } | ||
| 2297 | |||
| 2298 | if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft && | ||
| 2299 | !ctx->device->parent && !ctx->m_bVerticalMode) { | ||
| 2300 | // Mini-gamepad mode, swap some axes around | ||
| 2301 | float tmp = data[2]; | ||
| 2302 | data[2] = -data[0]; | ||
| 2303 | data[0] = tmp; | ||
| 2304 | } | ||
| 2305 | |||
| 2306 | if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight && | ||
| 2307 | !ctx->device->parent && !ctx->m_bVerticalMode) { | ||
| 2308 | // Mini-gamepad mode, swap some axes around | ||
| 2309 | float tmp = data[2]; | ||
| 2310 | data[2] = data[0]; | ||
| 2311 | data[0] = -tmp; | ||
| 2312 | } | ||
| 2313 | |||
| 2314 | SDL_SendJoystickSensor(timestamp, joystick, type, sensor_timestamp, data, 3); | ||
| 2315 | } | ||
| 2316 | |||
| 2317 | static void HandleCombinedControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) | ||
| 2318 | { | ||
| 2319 | Sint16 axis; | ||
| 2320 | |||
| 2321 | if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { | ||
| 2322 | Uint8 data = packet->controllerState.rgucButtons[1]; | ||
| 2323 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0)); | ||
| 2324 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0)); | ||
| 2325 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0)); | ||
| 2326 | } | ||
| 2327 | |||
| 2328 | if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) { | ||
| 2329 | Uint8 data = packet->controllerState.rgucButtons[2]; | ||
| 2330 | Uint8 hat = 0; | ||
| 2331 | |||
| 2332 | if (data & 0x01) { | ||
| 2333 | hat |= SDL_HAT_DOWN; | ||
| 2334 | } | ||
| 2335 | if (data & 0x02) { | ||
| 2336 | hat |= SDL_HAT_UP; | ||
| 2337 | } | ||
| 2338 | if (data & 0x04) { | ||
| 2339 | hat |= SDL_HAT_RIGHT; | ||
| 2340 | } | ||
| 2341 | if (data & 0x08) { | ||
| 2342 | hat |= SDL_HAT_LEFT; | ||
| 2343 | } | ||
| 2344 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 2345 | |||
| 2346 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x10) != 0)); | ||
| 2347 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x20) != 0)); | ||
| 2348 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x40) != 0)); | ||
| 2349 | axis = (data & 0x80) ? 32767 : -32768; | ||
| 2350 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 2351 | } | ||
| 2352 | |||
| 2353 | axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8); | ||
| 2354 | axis = ApplyStickCalibration(ctx, 0, 0, axis); | ||
| 2355 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 2356 | |||
| 2357 | axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4); | ||
| 2358 | axis = ApplyStickCalibration(ctx, 0, 1, axis); | ||
| 2359 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); | ||
| 2360 | } | ||
| 2361 | |||
| 2362 | static void HandleCombinedControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) | ||
| 2363 | { | ||
| 2364 | Sint16 axis; | ||
| 2365 | |||
| 2366 | if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { | ||
| 2367 | Uint8 data = packet->controllerState.rgucButtons[0]; | ||
| 2368 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x04) != 0)); | ||
| 2369 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x08) != 0)); | ||
| 2370 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x01) != 0)); | ||
| 2371 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x02) != 0)); | ||
| 2372 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x10) != 0)); | ||
| 2373 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x20) != 0)); | ||
| 2374 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x40) != 0)); | ||
| 2375 | axis = (data & 0x80) ? 32767 : -32768; | ||
| 2376 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 2377 | } | ||
| 2378 | |||
| 2379 | if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { | ||
| 2380 | Uint8 data = packet->controllerState.rgucButtons[1]; | ||
| 2381 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); | ||
| 2382 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x04) != 0)); | ||
| 2383 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); | ||
| 2384 | } | ||
| 2385 | |||
| 2386 | axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8); | ||
| 2387 | axis = ApplyStickCalibration(ctx, 1, 0, axis); | ||
| 2388 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 2389 | |||
| 2390 | axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4); | ||
| 2391 | axis = ApplyStickCalibration(ctx, 1, 1, axis); | ||
| 2392 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); | ||
| 2393 | } | ||
| 2394 | |||
| 2395 | static void HandleMiniControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) | ||
| 2396 | { | ||
| 2397 | Sint16 axis; | ||
| 2398 | |||
| 2399 | if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { | ||
| 2400 | Uint8 data = packet->controllerState.rgucButtons[1]; | ||
| 2401 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x01) != 0)); | ||
| 2402 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0)); | ||
| 2403 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x20) != 0)); | ||
| 2404 | } | ||
| 2405 | |||
| 2406 | if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) { | ||
| 2407 | Uint8 data = packet->controllerState.rgucButtons[2]; | ||
| 2408 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x08) != 0)); | ||
| 2409 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x01) != 0)); | ||
| 2410 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x02) != 0)); | ||
| 2411 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x04) != 0)); | ||
| 2412 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x10) != 0)); | ||
| 2413 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x20) != 0)); | ||
| 2414 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x40) != 0)); | ||
| 2415 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x80) != 0)); | ||
| 2416 | } | ||
| 2417 | |||
| 2418 | axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8); | ||
| 2419 | axis = ApplyStickCalibration(ctx, 0, 0, axis); | ||
| 2420 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); | ||
| 2421 | |||
| 2422 | axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4); | ||
| 2423 | axis = ApplyStickCalibration(ctx, 0, 1, axis); | ||
| 2424 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, ~axis); | ||
| 2425 | } | ||
| 2426 | |||
| 2427 | static void HandleMiniControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) | ||
| 2428 | { | ||
| 2429 | Sint16 axis; | ||
| 2430 | |||
| 2431 | if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { | ||
| 2432 | Uint8 data = packet->controllerState.rgucButtons[0]; | ||
| 2433 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x08) != 0)); | ||
| 2434 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0)); | ||
| 2435 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0)); | ||
| 2436 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x01) != 0)); | ||
| 2437 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x10) != 0)); | ||
| 2438 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x20) != 0)); | ||
| 2439 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x40) != 0)); | ||
| 2440 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x80) != 0)); | ||
| 2441 | } | ||
| 2442 | |||
| 2443 | if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { | ||
| 2444 | Uint8 data = packet->controllerState.rgucButtons[1]; | ||
| 2445 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); | ||
| 2446 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0)); | ||
| 2447 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); | ||
| 2448 | } | ||
| 2449 | |||
| 2450 | axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8); | ||
| 2451 | axis = ApplyStickCalibration(ctx, 1, 0, axis); | ||
| 2452 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 2453 | |||
| 2454 | axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4); | ||
| 2455 | axis = ApplyStickCalibration(ctx, 1, 1, axis); | ||
| 2456 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 2457 | } | ||
| 2458 | |||
| 2459 | 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 | ||
| 2460 | { | ||
| 2461 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 2462 | |||
| 2463 | if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { | ||
| 2464 | if (ctx->device->parent || ctx->m_bVerticalMode) { | ||
| 2465 | HandleCombinedControllerStateL(timestamp, joystick, ctx, packet); | ||
| 2466 | } else { | ||
| 2467 | HandleMiniControllerStateL(timestamp, joystick, ctx, packet); | ||
| 2468 | } | ||
| 2469 | } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { | ||
| 2470 | if (ctx->device->parent || ctx->m_bVerticalMode) { | ||
| 2471 | HandleCombinedControllerStateR(timestamp, joystick, ctx, packet); | ||
| 2472 | } else { | ||
| 2473 | HandleMiniControllerStateR(timestamp, joystick, ctx, packet); | ||
| 2474 | } | ||
| 2475 | } else { | ||
| 2476 | Sint16 axis; | ||
| 2477 | |||
| 2478 | if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { | ||
| 2479 | Uint8 data = packet->controllerState.rgucButtons[0]; | ||
| 2480 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x04) != 0)); | ||
| 2481 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x08) != 0)); | ||
| 2482 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x01) != 0)); | ||
| 2483 | SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x02) != 0)); | ||
| 2484 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x40) != 0)); | ||
| 2485 | } | ||
| 2486 | |||
| 2487 | if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { | ||
| 2488 | Uint8 data = packet->controllerState.rgucButtons[1]; | ||
| 2489 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0)); | ||
| 2490 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0)); | ||
| 2491 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x04) != 0)); | ||
| 2492 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0)); | ||
| 2493 | |||
| 2494 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0)); | ||
| 2495 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0)); | ||
| 2496 | } | ||
| 2497 | |||
| 2498 | if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) { | ||
| 2499 | Uint8 data = packet->controllerState.rgucButtons[2]; | ||
| 2500 | Uint8 hat = 0; | ||
| 2501 | |||
| 2502 | if (data & 0x01) { | ||
| 2503 | hat |= SDL_HAT_DOWN; | ||
| 2504 | } | ||
| 2505 | if (data & 0x02) { | ||
| 2506 | hat |= SDL_HAT_UP; | ||
| 2507 | } | ||
| 2508 | if (data & 0x04) { | ||
| 2509 | hat |= SDL_HAT_RIGHT; | ||
| 2510 | } | ||
| 2511 | if (data & 0x08) { | ||
| 2512 | hat |= SDL_HAT_LEFT; | ||
| 2513 | } | ||
| 2514 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 2515 | |||
| 2516 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x40) != 0)); | ||
| 2517 | } | ||
| 2518 | |||
| 2519 | axis = (packet->controllerState.rgucButtons[0] & 0x80) ? 32767 : -32768; | ||
| 2520 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 2521 | |||
| 2522 | axis = (packet->controllerState.rgucButtons[2] & 0x80) ? 32767 : -32768; | ||
| 2523 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 2524 | |||
| 2525 | axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8); | ||
| 2526 | axis = ApplyStickCalibration(ctx, 0, 0, axis); | ||
| 2527 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 2528 | |||
| 2529 | axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4); | ||
| 2530 | axis = ApplyStickCalibration(ctx, 0, 1, axis); | ||
| 2531 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); | ||
| 2532 | |||
| 2533 | axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8); | ||
| 2534 | axis = ApplyStickCalibration(ctx, 1, 0, axis); | ||
| 2535 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 2536 | |||
| 2537 | axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4); | ||
| 2538 | axis = ApplyStickCalibration(ctx, 1, 1, axis); | ||
| 2539 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); | ||
| 2540 | } | ||
| 2541 | |||
| 2542 | /* High nibble of battery/connection byte is battery level, low nibble is connection status (always 0 on 8BitDo Pro 2) | ||
| 2543 | * LSB of connection nibble is USB/Switch connection status | ||
| 2544 | * LSB of the battery nibble is used to report charging. | ||
| 2545 | * The battery level is reported from 0(empty)-8(full) | ||
| 2546 | */ | ||
| 2547 | SDL_PowerState state; | ||
| 2548 | int charging = (packet->controllerState.ucBatteryAndConnection & 0x10); | ||
| 2549 | int level = (packet->controllerState.ucBatteryAndConnection & 0xE0) >> 4; | ||
| 2550 | int percent = (int)SDL_roundf((level / 8.0f) * 100.0f); | ||
| 2551 | |||
| 2552 | if (charging) { | ||
| 2553 | if (level == 8) { | ||
| 2554 | state = SDL_POWERSTATE_CHARGED; | ||
| 2555 | } else { | ||
| 2556 | state = SDL_POWERSTATE_CHARGING; | ||
| 2557 | } | ||
| 2558 | } else { | ||
| 2559 | state = SDL_POWERSTATE_ON_BATTERY; | ||
| 2560 | } | ||
| 2561 | SDL_SendJoystickPowerInfo(joystick, state, percent); | ||
| 2562 | |||
| 2563 | if (ctx->m_bReportSensors) { | ||
| 2564 | bool bHasSensorData = (packet->imuState[0].sAccelZ != 0 || | ||
| 2565 | packet->imuState[0].sAccelY != 0 || | ||
| 2566 | packet->imuState[0].sAccelX != 0); | ||
| 2567 | if (bHasSensorData) { | ||
| 2568 | const Uint32 IMU_UPDATE_RATE_SAMPLE_FREQUENCY = 1000; | ||
| 2569 | Uint64 sensor_timestamp[3]; | ||
| 2570 | |||
| 2571 | ctx->m_bHasSensorData = true; | ||
| 2572 | |||
| 2573 | // We got three IMU samples, calculate the IMU update rate and timestamps | ||
| 2574 | ctx->m_unIMUSamples += 3; | ||
| 2575 | if (ctx->m_unIMUSamples >= IMU_UPDATE_RATE_SAMPLE_FREQUENCY) { | ||
| 2576 | Uint64 now = SDL_GetTicksNS(); | ||
| 2577 | Uint64 elapsed = (now - ctx->m_ulIMUSampleTimestampNS); | ||
| 2578 | |||
| 2579 | if (elapsed > 0) { | ||
| 2580 | ctx->m_ulIMUUpdateIntervalNS = elapsed / ctx->m_unIMUSamples; | ||
| 2581 | } | ||
| 2582 | ctx->m_unIMUSamples = 0; | ||
| 2583 | ctx->m_ulIMUSampleTimestampNS = now; | ||
| 2584 | } | ||
| 2585 | |||
| 2586 | ctx->m_ulTimestampNS += ctx->m_ulIMUUpdateIntervalNS; | ||
| 2587 | sensor_timestamp[0] = ctx->m_ulTimestampNS; | ||
| 2588 | ctx->m_ulTimestampNS += ctx->m_ulIMUUpdateIntervalNS; | ||
| 2589 | sensor_timestamp[1] = ctx->m_ulTimestampNS; | ||
| 2590 | ctx->m_ulTimestampNS += ctx->m_ulIMUUpdateIntervalNS; | ||
| 2591 | sensor_timestamp[2] = ctx->m_ulTimestampNS; | ||
| 2592 | |||
| 2593 | if (!ctx->device->parent || | ||
| 2594 | ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { | ||
| 2595 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[0], &packet->imuState[2].sGyroX); | ||
| 2596 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[0], &packet->imuState[2].sAccelX); | ||
| 2597 | |||
| 2598 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[1], &packet->imuState[1].sGyroX); | ||
| 2599 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[1], &packet->imuState[1].sAccelX); | ||
| 2600 | |||
| 2601 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[2], &packet->imuState[0].sGyroX); | ||
| 2602 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[2], &packet->imuState[0].sAccelX); | ||
| 2603 | } | ||
| 2604 | |||
| 2605 | if (ctx->device->parent && | ||
| 2606 | ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { | ||
| 2607 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[0], &packet->imuState[2].sGyroX); | ||
| 2608 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[0], &packet->imuState[2].sAccelX); | ||
| 2609 | |||
| 2610 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[1], &packet->imuState[1].sGyroX); | ||
| 2611 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[1], &packet->imuState[1].sAccelX); | ||
| 2612 | |||
| 2613 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[2], &packet->imuState[0].sGyroX); | ||
| 2614 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[2], &packet->imuState[0].sAccelX); | ||
| 2615 | } | ||
| 2616 | if (ctx->device->parent && | ||
| 2617 | ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { | ||
| 2618 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[0], &packet->imuState[2].sGyroX); | ||
| 2619 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[0], &packet->imuState[2].sAccelX); | ||
| 2620 | |||
| 2621 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[1], &packet->imuState[1].sGyroX); | ||
| 2622 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[1], &packet->imuState[1].sAccelX); | ||
| 2623 | |||
| 2624 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[2], &packet->imuState[0].sGyroX); | ||
| 2625 | SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[2], &packet->imuState[0].sAccelX); | ||
| 2626 | } | ||
| 2627 | |||
| 2628 | } else if (ctx->m_bHasSensorData) { | ||
| 2629 | // Uh oh, someone turned off the IMU? | ||
| 2630 | const int IMU_RESET_DELAY_MS = 3000; | ||
| 2631 | Uint64 now = SDL_GetTicks(); | ||
| 2632 | |||
| 2633 | if (now >= (ctx->m_ulLastIMUReset + IMU_RESET_DELAY_MS)) { | ||
| 2634 | SDL_HIDAPI_Device *device = ctx->device; | ||
| 2635 | |||
| 2636 | if (device->updating) { | ||
| 2637 | SDL_UnlockMutex(device->dev_lock); | ||
| 2638 | } | ||
| 2639 | |||
| 2640 | SetIMUEnabled(ctx, true); | ||
| 2641 | |||
| 2642 | if (device->updating) { | ||
| 2643 | SDL_LockMutex(device->dev_lock); | ||
| 2644 | } | ||
| 2645 | ctx->m_ulLastIMUReset = now; | ||
| 2646 | } | ||
| 2647 | |||
| 2648 | } else { | ||
| 2649 | // We have never gotten IMU data, probably not supported on this device | ||
| 2650 | } | ||
| 2651 | } | ||
| 2652 | |||
| 2653 | ctx->m_lastFullState = *packet; | ||
| 2654 | } | ||
| 2655 | |||
| 2656 | static bool HIDAPI_DriverSwitch_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 2657 | { | ||
| 2658 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; | ||
| 2659 | SDL_Joystick *joystick = NULL; | ||
| 2660 | int size; | ||
| 2661 | int packet_count = 0; | ||
| 2662 | Uint64 now = SDL_GetTicks(); | ||
| 2663 | |||
| 2664 | if (device->num_joysticks > 0) { | ||
| 2665 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 2666 | } | ||
| 2667 | |||
| 2668 | while ((size = ReadInput(ctx)) > 0) { | ||
| 2669 | #ifdef DEBUG_SWITCH_PROTOCOL | ||
| 2670 | HIDAPI_DumpPacket("Nintendo Switch packet: size = %d", ctx->m_rgucReadBuffer, size); | ||
| 2671 | #endif | ||
| 2672 | ++packet_count; | ||
| 2673 | ctx->m_ulLastInput = now; | ||
| 2674 | |||
| 2675 | if (!joystick) { | ||
| 2676 | continue; | ||
| 2677 | } | ||
| 2678 | |||
| 2679 | if (ctx->m_bInputOnly) { | ||
| 2680 | HandleInputOnlyControllerState(joystick, ctx, (SwitchInputOnlyControllerStatePacket_t *)&ctx->m_rgucReadBuffer[0]); | ||
| 2681 | } else { | ||
| 2682 | if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) { | ||
| 2683 | continue; | ||
| 2684 | } | ||
| 2685 | |||
| 2686 | ctx->m_nCurrentInputMode = ctx->m_rgucReadBuffer[0]; | ||
| 2687 | |||
| 2688 | switch (ctx->m_rgucReadBuffer[0]) { | ||
| 2689 | case k_eSwitchInputReportIDs_SimpleControllerState: | ||
| 2690 | HandleSimpleControllerState(joystick, ctx, (SwitchSimpleStatePacket_t *)&ctx->m_rgucReadBuffer[1]); | ||
| 2691 | break; | ||
| 2692 | case k_eSwitchInputReportIDs_FullControllerState: | ||
| 2693 | case k_eSwitchInputReportIDs_FullControllerAndMcuState: | ||
| 2694 | // This is the extended report, we can enable sensors now in auto mode | ||
| 2695 | UpdateEnhancedModeOnEnhancedReport(ctx); | ||
| 2696 | |||
| 2697 | HandleFullControllerState(joystick, ctx, (SwitchStatePacket_t *)&ctx->m_rgucReadBuffer[1]); | ||
| 2698 | break; | ||
| 2699 | default: | ||
| 2700 | break; | ||
| 2701 | } | ||
| 2702 | } | ||
| 2703 | } | ||
| 2704 | |||
| 2705 | if (joystick) { | ||
| 2706 | if (packet_count == 0) { | ||
| 2707 | if (!ctx->m_bInputOnly && !device->is_bluetooth && | ||
| 2708 | ctx->device->product_id != USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) { | ||
| 2709 | const int INPUT_WAIT_TIMEOUT_MS = 100; | ||
| 2710 | if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) { | ||
| 2711 | // Steam may have put the controller back into non-reporting mode | ||
| 2712 | bool wasSyncWrite = ctx->m_bSyncWrite; | ||
| 2713 | |||
| 2714 | ctx->m_bSyncWrite = true; | ||
| 2715 | WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, false); | ||
| 2716 | ctx->m_bSyncWrite = wasSyncWrite; | ||
| 2717 | } | ||
| 2718 | } else if (device->is_bluetooth && | ||
| 2719 | ctx->m_nCurrentInputMode != k_eSwitchInputReportIDs_SimpleControllerState) { | ||
| 2720 | const int INPUT_WAIT_TIMEOUT_MS = 3000; | ||
| 2721 | if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) { | ||
| 2722 | // Bluetooth may have disconnected, try reopening the controller | ||
| 2723 | size = -1; | ||
| 2724 | } | ||
| 2725 | } | ||
| 2726 | } | ||
| 2727 | |||
| 2728 | if (ctx->m_bRumblePending || ctx->m_bRumbleZeroPending) { | ||
| 2729 | HIDAPI_DriverSwitch_SendPendingRumble(ctx); | ||
| 2730 | } else if (ctx->m_bRumbleActive && | ||
| 2731 | now >= (ctx->m_ulRumbleSent + RUMBLE_REFRESH_FREQUENCY_MS)) { | ||
| 2732 | #ifdef DEBUG_RUMBLE | ||
| 2733 | SDL_Log("Sent continuing rumble, %d ms after previous rumble", now - ctx->m_ulRumbleSent); | ||
| 2734 | #endif | ||
| 2735 | WriteRumble(ctx); | ||
| 2736 | } | ||
| 2737 | } | ||
| 2738 | |||
| 2739 | // Reconnect the Bluetooth device once the USB device is gone | ||
| 2740 | if (device->num_joysticks == 0 && device->is_bluetooth && packet_count > 0 && | ||
| 2741 | !device->parent && | ||
| 2742 | !HIDAPI_HasConnectedUSBDevice(device->serial)) { | ||
| 2743 | HIDAPI_JoystickConnected(device, NULL); | ||
| 2744 | } | ||
| 2745 | |||
| 2746 | if (size < 0 && device->num_joysticks > 0) { | ||
| 2747 | // Read error, device is disconnected | ||
| 2748 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 2749 | } | ||
| 2750 | return (size >= 0); | ||
| 2751 | } | ||
| 2752 | |||
| 2753 | static void HIDAPI_DriverSwitch_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 2754 | { | ||
| 2755 | SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; | ||
| 2756 | |||
| 2757 | if (!ctx->m_bInputOnly) { | ||
| 2758 | // Restore simple input mode for other applications | ||
| 2759 | if (!ctx->m_nInitialInputMode || | ||
| 2760 | ctx->m_nInitialInputMode == k_eSwitchInputReportIDs_SimpleControllerState) { | ||
| 2761 | SetInputMode(ctx, k_eSwitchInputReportIDs_SimpleControllerState); | ||
| 2762 | } | ||
| 2763 | } | ||
| 2764 | |||
| 2765 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, | ||
| 2766 | SDL_EnhancedReportsChanged, ctx); | ||
| 2767 | |||
| 2768 | if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || | ||
| 2769 | ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { | ||
| 2770 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED, | ||
| 2771 | SDL_HomeLEDHintChanged, ctx); | ||
| 2772 | } else { | ||
| 2773 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, | ||
| 2774 | SDL_HomeLEDHintChanged, ctx); | ||
| 2775 | } | ||
| 2776 | |||
| 2777 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, | ||
| 2778 | SDL_PlayerLEDHintChanged, ctx); | ||
| 2779 | |||
| 2780 | ctx->joystick = NULL; | ||
| 2781 | |||
| 2782 | ctx->m_bReportSensors = false; | ||
| 2783 | ctx->m_bEnhancedMode = false; | ||
| 2784 | ctx->m_bEnhancedModeAvailable = false; | ||
| 2785 | } | ||
| 2786 | |||
| 2787 | static void HIDAPI_DriverSwitch_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 2788 | { | ||
| 2789 | } | ||
| 2790 | |||
| 2791 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic = { | ||
| 2792 | SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, | ||
| 2793 | true, | ||
| 2794 | HIDAPI_DriverNintendoClassic_RegisterHints, | ||
| 2795 | HIDAPI_DriverNintendoClassic_UnregisterHints, | ||
| 2796 | HIDAPI_DriverNintendoClassic_IsEnabled, | ||
| 2797 | HIDAPI_DriverNintendoClassic_IsSupportedDevice, | ||
| 2798 | HIDAPI_DriverSwitch_InitDevice, | ||
| 2799 | HIDAPI_DriverSwitch_GetDevicePlayerIndex, | ||
| 2800 | HIDAPI_DriverSwitch_SetDevicePlayerIndex, | ||
| 2801 | HIDAPI_DriverSwitch_UpdateDevice, | ||
| 2802 | HIDAPI_DriverSwitch_OpenJoystick, | ||
| 2803 | HIDAPI_DriverSwitch_RumbleJoystick, | ||
| 2804 | HIDAPI_DriverSwitch_RumbleJoystickTriggers, | ||
| 2805 | HIDAPI_DriverSwitch_GetJoystickCapabilities, | ||
| 2806 | HIDAPI_DriverSwitch_SetJoystickLED, | ||
| 2807 | HIDAPI_DriverSwitch_SendJoystickEffect, | ||
| 2808 | HIDAPI_DriverSwitch_SetJoystickSensorsEnabled, | ||
| 2809 | HIDAPI_DriverSwitch_CloseJoystick, | ||
| 2810 | HIDAPI_DriverSwitch_FreeDevice, | ||
| 2811 | }; | ||
| 2812 | |||
| 2813 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons = { | ||
| 2814 | SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, | ||
| 2815 | true, | ||
| 2816 | HIDAPI_DriverJoyCons_RegisterHints, | ||
| 2817 | HIDAPI_DriverJoyCons_UnregisterHints, | ||
| 2818 | HIDAPI_DriverJoyCons_IsEnabled, | ||
| 2819 | HIDAPI_DriverJoyCons_IsSupportedDevice, | ||
| 2820 | HIDAPI_DriverSwitch_InitDevice, | ||
| 2821 | HIDAPI_DriverSwitch_GetDevicePlayerIndex, | ||
| 2822 | HIDAPI_DriverSwitch_SetDevicePlayerIndex, | ||
| 2823 | HIDAPI_DriverSwitch_UpdateDevice, | ||
| 2824 | HIDAPI_DriverSwitch_OpenJoystick, | ||
| 2825 | HIDAPI_DriverSwitch_RumbleJoystick, | ||
| 2826 | HIDAPI_DriverSwitch_RumbleJoystickTriggers, | ||
| 2827 | HIDAPI_DriverSwitch_GetJoystickCapabilities, | ||
| 2828 | HIDAPI_DriverSwitch_SetJoystickLED, | ||
| 2829 | HIDAPI_DriverSwitch_SendJoystickEffect, | ||
| 2830 | HIDAPI_DriverSwitch_SetJoystickSensorsEnabled, | ||
| 2831 | HIDAPI_DriverSwitch_CloseJoystick, | ||
| 2832 | HIDAPI_DriverSwitch_FreeDevice, | ||
| 2833 | }; | ||
| 2834 | |||
| 2835 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch = { | ||
| 2836 | SDL_HINT_JOYSTICK_HIDAPI_SWITCH, | ||
| 2837 | true, | ||
| 2838 | HIDAPI_DriverSwitch_RegisterHints, | ||
| 2839 | HIDAPI_DriverSwitch_UnregisterHints, | ||
| 2840 | HIDAPI_DriverSwitch_IsEnabled, | ||
| 2841 | HIDAPI_DriverSwitch_IsSupportedDevice, | ||
| 2842 | HIDAPI_DriverSwitch_InitDevice, | ||
| 2843 | HIDAPI_DriverSwitch_GetDevicePlayerIndex, | ||
| 2844 | HIDAPI_DriverSwitch_SetDevicePlayerIndex, | ||
| 2845 | HIDAPI_DriverSwitch_UpdateDevice, | ||
| 2846 | HIDAPI_DriverSwitch_OpenJoystick, | ||
| 2847 | HIDAPI_DriverSwitch_RumbleJoystick, | ||
| 2848 | HIDAPI_DriverSwitch_RumbleJoystickTriggers, | ||
| 2849 | HIDAPI_DriverSwitch_GetJoystickCapabilities, | ||
| 2850 | HIDAPI_DriverSwitch_SetJoystickLED, | ||
| 2851 | HIDAPI_DriverSwitch_SendJoystickEffect, | ||
| 2852 | HIDAPI_DriverSwitch_SetJoystickSensorsEnabled, | ||
| 2853 | HIDAPI_DriverSwitch_CloseJoystick, | ||
| 2854 | HIDAPI_DriverSwitch_FreeDevice, | ||
| 2855 | }; | ||
| 2856 | |||
| 2857 | #endif // SDL_JOYSTICK_HIDAPI_SWITCH | ||
| 2858 | |||
| 2859 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../../SDL_hints_c.h" | ||
| 26 | #include "../SDL_sysjoystick.h" | ||
| 27 | #include "SDL_hidapijoystick_c.h" | ||
| 28 | #include "SDL_hidapi_rumble.h" | ||
| 29 | #include "SDL_hidapi_nintendo.h" | ||
| 30 | |||
| 31 | #ifdef SDL_JOYSTICK_HIDAPI_WII | ||
| 32 | |||
| 33 | // Define this if you want to log all packets from the controller | ||
| 34 | // #define DEBUG_WII_PROTOCOL | ||
| 35 | |||
| 36 | #define ENABLE_CONTINUOUS_REPORTING true | ||
| 37 | |||
| 38 | #define INPUT_WAIT_TIMEOUT_MS (3 * 1000) | ||
| 39 | #define MOTION_PLUS_UPDATE_TIME_MS (8 * 1000) | ||
| 40 | #define STATUS_UPDATE_TIME_MS (15 * 60 * 1000) | ||
| 41 | |||
| 42 | #define WII_EXTENSION_NONE 0x2E2E | ||
| 43 | #define WII_EXTENSION_UNINITIALIZED 0xFFFF | ||
| 44 | #define WII_EXTENSION_NUNCHUK 0x0000 | ||
| 45 | #define WII_EXTENSION_GAMEPAD 0x0101 | ||
| 46 | #define WII_EXTENSION_WIIUPRO 0x0120 | ||
| 47 | #define WII_EXTENSION_MOTIONPLUS_MASK 0xF0FF | ||
| 48 | #define WII_EXTENSION_MOTIONPLUS_ID 0x0005 | ||
| 49 | |||
| 50 | #define WII_MOTIONPLUS_MODE_NONE 0x00 | ||
| 51 | #define WII_MOTIONPLUS_MODE_STANDARD 0x04 | ||
| 52 | #define WII_MOTIONPLUS_MODE_NUNCHUK 0x05 | ||
| 53 | #define WII_MOTIONPLUS_MODE_GAMEPAD 0x07 | ||
| 54 | |||
| 55 | typedef enum | ||
| 56 | { | ||
| 57 | k_eWiiInputReportIDs_Status = 0x20, | ||
| 58 | k_eWiiInputReportIDs_ReadMemory = 0x21, | ||
| 59 | k_eWiiInputReportIDs_Acknowledge = 0x22, | ||
| 60 | k_eWiiInputReportIDs_ButtonData0 = 0x30, | ||
| 61 | k_eWiiInputReportIDs_ButtonData1 = 0x31, | ||
| 62 | k_eWiiInputReportIDs_ButtonData2 = 0x32, | ||
| 63 | k_eWiiInputReportIDs_ButtonData3 = 0x33, | ||
| 64 | k_eWiiInputReportIDs_ButtonData4 = 0x34, | ||
| 65 | k_eWiiInputReportIDs_ButtonData5 = 0x35, | ||
| 66 | k_eWiiInputReportIDs_ButtonData6 = 0x36, | ||
| 67 | k_eWiiInputReportIDs_ButtonData7 = 0x37, | ||
| 68 | k_eWiiInputReportIDs_ButtonDataD = 0x3D, | ||
| 69 | k_eWiiInputReportIDs_ButtonDataE = 0x3E, | ||
| 70 | k_eWiiInputReportIDs_ButtonDataF = 0x3F, | ||
| 71 | } EWiiInputReportIDs; | ||
| 72 | |||
| 73 | typedef enum | ||
| 74 | { | ||
| 75 | k_eWiiOutputReportIDs_Rumble = 0x10, | ||
| 76 | k_eWiiOutputReportIDs_LEDs = 0x11, | ||
| 77 | k_eWiiOutputReportIDs_DataReportingMode = 0x12, | ||
| 78 | k_eWiiOutputReportIDs_IRCameraEnable = 0x13, | ||
| 79 | k_eWiiOutputReportIDs_SpeakerEnable = 0x14, | ||
| 80 | k_eWiiOutputReportIDs_StatusRequest = 0x15, | ||
| 81 | k_eWiiOutputReportIDs_WriteMemory = 0x16, | ||
| 82 | k_eWiiOutputReportIDs_ReadMemory = 0x17, | ||
| 83 | k_eWiiOutputReportIDs_SpeakerData = 0x18, | ||
| 84 | k_eWiiOutputReportIDs_SpeakerMute = 0x19, | ||
| 85 | k_eWiiOutputReportIDs_IRCameraEnable2 = 0x1a, | ||
| 86 | } EWiiOutputReportIDs; | ||
| 87 | |||
| 88 | typedef enum | ||
| 89 | { | ||
| 90 | k_eWiiPlayerLEDs_P1 = 0x10, | ||
| 91 | k_eWiiPlayerLEDs_P2 = 0x20, | ||
| 92 | k_eWiiPlayerLEDs_P3 = 0x40, | ||
| 93 | k_eWiiPlayerLEDs_P4 = 0x80, | ||
| 94 | } EWiiPlayerLEDs; | ||
| 95 | |||
| 96 | typedef enum | ||
| 97 | { | ||
| 98 | k_eWiiCommunicationState_None, // No special communications happening | ||
| 99 | k_eWiiCommunicationState_CheckMotionPlusStage1, // Sent standard extension identify request | ||
| 100 | k_eWiiCommunicationState_CheckMotionPlusStage2, // Sent Motion Plus extension identify request | ||
| 101 | } EWiiCommunicationState; | ||
| 102 | |||
| 103 | typedef enum | ||
| 104 | { | ||
| 105 | k_eWiiButtons_A = SDL_GAMEPAD_BUTTON_MISC1, | ||
| 106 | k_eWiiButtons_B, | ||
| 107 | k_eWiiButtons_One, | ||
| 108 | k_eWiiButtons_Two, | ||
| 109 | k_eWiiButtons_Plus, | ||
| 110 | k_eWiiButtons_Minus, | ||
| 111 | k_eWiiButtons_Home, | ||
| 112 | k_eWiiButtons_DPad_Up, | ||
| 113 | k_eWiiButtons_DPad_Down, | ||
| 114 | k_eWiiButtons_DPad_Left, | ||
| 115 | k_eWiiButtons_DPad_Right, | ||
| 116 | k_eWiiButtons_Max | ||
| 117 | } EWiiButtons; | ||
| 118 | |||
| 119 | #define k_unWiiPacketDataLength 22 | ||
| 120 | |||
| 121 | typedef struct | ||
| 122 | { | ||
| 123 | Uint8 rgucBaseButtons[2]; | ||
| 124 | Uint8 rgucAccelerometer[3]; | ||
| 125 | Uint8 rgucExtension[21]; | ||
| 126 | bool hasBaseButtons; | ||
| 127 | bool hasAccelerometer; | ||
| 128 | Uint8 ucNExtensionBytes; | ||
| 129 | } WiiButtonData; | ||
| 130 | |||
| 131 | typedef struct | ||
| 132 | { | ||
| 133 | Uint16 min; | ||
| 134 | Uint16 max; | ||
| 135 | Uint16 center; | ||
| 136 | Uint16 deadzone; | ||
| 137 | } StickCalibrationData; | ||
| 138 | |||
| 139 | typedef struct | ||
| 140 | { | ||
| 141 | SDL_HIDAPI_Device *device; | ||
| 142 | SDL_Joystick *joystick; | ||
| 143 | Uint64 timestamp; | ||
| 144 | EWiiCommunicationState m_eCommState; | ||
| 145 | EWiiExtensionControllerType m_eExtensionControllerType; | ||
| 146 | bool m_bPlayerLights; | ||
| 147 | int m_nPlayerIndex; | ||
| 148 | bool m_bRumbleActive; | ||
| 149 | bool m_bMotionPlusPresent; | ||
| 150 | Uint8 m_ucMotionPlusMode; | ||
| 151 | bool m_bReportSensors; | ||
| 152 | Uint8 m_rgucReadBuffer[k_unWiiPacketDataLength]; | ||
| 153 | Uint64 m_ulLastInput; | ||
| 154 | Uint64 m_ulLastStatus; | ||
| 155 | Uint64 m_ulNextMotionPlusCheck; | ||
| 156 | bool m_bDisconnected; | ||
| 157 | |||
| 158 | StickCalibrationData m_StickCalibrationData[6]; | ||
| 159 | } SDL_DriverWii_Context; | ||
| 160 | |||
| 161 | static void HIDAPI_DriverWii_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 162 | { | ||
| 163 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII, callback, userdata); | ||
| 164 | } | ||
| 165 | |||
| 166 | static void HIDAPI_DriverWii_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 167 | { | ||
| 168 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII, callback, userdata); | ||
| 169 | } | ||
| 170 | |||
| 171 | static bool HIDAPI_DriverWii_IsEnabled(void) | ||
| 172 | { | ||
| 173 | #if 1 // This doesn't work with the dolphinbar, so don't enable by default right now | ||
| 174 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII, false); | ||
| 175 | #else | ||
| 176 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII, | ||
| 177 | SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, | ||
| 178 | SDL_HIDAPI_DEFAULT)); | ||
| 179 | #endif | ||
| 180 | } | ||
| 181 | |||
| 182 | 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) | ||
| 183 | { | ||
| 184 | if (vendor_id == USB_VENDOR_NINTENDO && | ||
| 185 | (product_id == USB_PRODUCT_NINTENDO_WII_REMOTE || | ||
| 186 | product_id == USB_PRODUCT_NINTENDO_WII_REMOTE2)) { | ||
| 187 | return true; | ||
| 188 | } | ||
| 189 | return false; | ||
| 190 | } | ||
| 191 | |||
| 192 | static int ReadInput(SDL_DriverWii_Context *ctx) | ||
| 193 | { | ||
| 194 | int size; | ||
| 195 | |||
| 196 | // Make sure we don't try to read at the same time a write is happening | ||
| 197 | if (SDL_GetAtomicInt(&ctx->device->rumble_pending) > 0) { | ||
| 198 | return 0; | ||
| 199 | } | ||
| 200 | |||
| 201 | size = SDL_hid_read_timeout(ctx->device->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0); | ||
| 202 | #ifdef DEBUG_WII_PROTOCOL | ||
| 203 | if (size > 0) { | ||
| 204 | HIDAPI_DumpPacket("Wii packet: size = %d", ctx->m_rgucReadBuffer, size); | ||
| 205 | } | ||
| 206 | #endif | ||
| 207 | return size; | ||
| 208 | } | ||
| 209 | |||
| 210 | static bool WriteOutput(SDL_DriverWii_Context *ctx, const Uint8 *data, int size, bool sync) | ||
| 211 | { | ||
| 212 | #ifdef DEBUG_WII_PROTOCOL | ||
| 213 | if (size > 0) { | ||
| 214 | HIDAPI_DumpPacket("Wii write packet: size = %d", data, size); | ||
| 215 | } | ||
| 216 | #endif | ||
| 217 | if (sync) { | ||
| 218 | return SDL_hid_write(ctx->device->dev, data, size) >= 0; | ||
| 219 | } else { | ||
| 220 | // Use the rumble thread for general asynchronous writes | ||
| 221 | if (!SDL_HIDAPI_LockRumble()) { | ||
| 222 | return false; | ||
| 223 | } | ||
| 224 | return SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size) >= 0; | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | static bool ReadInputSync(SDL_DriverWii_Context *ctx, EWiiInputReportIDs expectedID, bool (*isMine)(const Uint8 *)) | ||
| 229 | { | ||
| 230 | Uint64 endTicks = SDL_GetTicks() + 250; // Seeing successful reads after about 200 ms | ||
| 231 | |||
| 232 | int nRead = 0; | ||
| 233 | while ((nRead = ReadInput(ctx)) != -1) { | ||
| 234 | if (nRead > 0) { | ||
| 235 | if (ctx->m_rgucReadBuffer[0] == expectedID && (!isMine || isMine(ctx->m_rgucReadBuffer))) { | ||
| 236 | return true; | ||
| 237 | } | ||
| 238 | } else { | ||
| 239 | if (SDL_GetTicks() >= endTicks) { | ||
| 240 | break; | ||
| 241 | } | ||
| 242 | SDL_Delay(1); | ||
| 243 | } | ||
| 244 | } | ||
| 245 | SDL_SetError("Read timed out"); | ||
| 246 | return false; | ||
| 247 | } | ||
| 248 | |||
| 249 | static bool IsWriteMemoryResponse(const Uint8 *data) | ||
| 250 | { | ||
| 251 | return data[3] == k_eWiiOutputReportIDs_WriteMemory; | ||
| 252 | } | ||
| 253 | |||
| 254 | static bool WriteRegister(SDL_DriverWii_Context *ctx, Uint32 address, const Uint8 *data, int size, bool sync) | ||
| 255 | { | ||
| 256 | Uint8 writeRequest[k_unWiiPacketDataLength]; | ||
| 257 | |||
| 258 | SDL_zeroa(writeRequest); | ||
| 259 | writeRequest[0] = k_eWiiOutputReportIDs_WriteMemory; | ||
| 260 | writeRequest[1] = (Uint8)(0x04 | (Uint8)ctx->m_bRumbleActive); | ||
| 261 | writeRequest[2] = (address >> 16) & 0xff; | ||
| 262 | writeRequest[3] = (address >> 8) & 0xff; | ||
| 263 | writeRequest[4] = address & 0xff; | ||
| 264 | writeRequest[5] = (Uint8)size; | ||
| 265 | SDL_assert(size > 0 && size <= 16); | ||
| 266 | SDL_memcpy(writeRequest + 6, data, size); | ||
| 267 | |||
| 268 | if (!WriteOutput(ctx, writeRequest, sizeof(writeRequest), sync)) { | ||
| 269 | return false; | ||
| 270 | } | ||
| 271 | if (sync) { | ||
| 272 | // Wait for response | ||
| 273 | if (!ReadInputSync(ctx, k_eWiiInputReportIDs_Acknowledge, IsWriteMemoryResponse)) { | ||
| 274 | return false; | ||
| 275 | } | ||
| 276 | if (ctx->m_rgucReadBuffer[4]) { | ||
| 277 | SDL_SetError("Write memory failed: %u", ctx->m_rgucReadBuffer[4]); | ||
| 278 | return false; | ||
| 279 | } | ||
| 280 | } | ||
| 281 | return true; | ||
| 282 | } | ||
| 283 | |||
| 284 | static bool ReadRegister(SDL_DriverWii_Context *ctx, Uint32 address, int size, bool sync) | ||
| 285 | { | ||
| 286 | Uint8 readRequest[7]; | ||
| 287 | |||
| 288 | readRequest[0] = k_eWiiOutputReportIDs_ReadMemory; | ||
| 289 | readRequest[1] = (Uint8)(0x04 | (Uint8)ctx->m_bRumbleActive); | ||
| 290 | readRequest[2] = (address >> 16) & 0xff; | ||
| 291 | readRequest[3] = (address >> 8) & 0xff; | ||
| 292 | readRequest[4] = address & 0xff; | ||
| 293 | readRequest[5] = (size >> 8) & 0xff; | ||
| 294 | readRequest[6] = size & 0xff; | ||
| 295 | |||
| 296 | SDL_assert(size > 0 && size <= 0xffff); | ||
| 297 | |||
| 298 | if (!WriteOutput(ctx, readRequest, sizeof(readRequest), sync)) { | ||
| 299 | return false; | ||
| 300 | } | ||
| 301 | if (sync) { | ||
| 302 | SDL_assert(size <= 16); // Only waiting for one packet is supported right now | ||
| 303 | // Wait for response | ||
| 304 | if (!ReadInputSync(ctx, k_eWiiInputReportIDs_ReadMemory, NULL)) { | ||
| 305 | return false; | ||
| 306 | } | ||
| 307 | } | ||
| 308 | return true; | ||
| 309 | } | ||
| 310 | |||
| 311 | static bool SendExtensionIdentify(SDL_DriverWii_Context *ctx, bool sync) | ||
| 312 | { | ||
| 313 | return ReadRegister(ctx, 0xA400FE, 2, sync); | ||
| 314 | } | ||
| 315 | |||
| 316 | static bool ParseExtensionIdentifyResponse(SDL_DriverWii_Context *ctx, Uint16 *extension) | ||
| 317 | { | ||
| 318 | int i; | ||
| 319 | |||
| 320 | if (ctx->m_rgucReadBuffer[0] != k_eWiiInputReportIDs_ReadMemory) { | ||
| 321 | SDL_SetError("Unexpected extension response type"); | ||
| 322 | return false; | ||
| 323 | } | ||
| 324 | |||
| 325 | if (ctx->m_rgucReadBuffer[4] != 0x00 || ctx->m_rgucReadBuffer[5] != 0xFE) { | ||
| 326 | SDL_SetError("Unexpected extension response address"); | ||
| 327 | return false; | ||
| 328 | } | ||
| 329 | |||
| 330 | if (ctx->m_rgucReadBuffer[3] != 0x10) { | ||
| 331 | Uint8 error = (ctx->m_rgucReadBuffer[3] & 0xF); | ||
| 332 | |||
| 333 | if (error == 7) { | ||
| 334 | // The extension memory isn't mapped | ||
| 335 | *extension = WII_EXTENSION_NONE; | ||
| 336 | return true; | ||
| 337 | } | ||
| 338 | |||
| 339 | if (error) { | ||
| 340 | SDL_SetError("Failed to read extension type: %u", error); | ||
| 341 | } else { | ||
| 342 | SDL_SetError("Unexpected read length when reading extension type: %d", (ctx->m_rgucReadBuffer[3] >> 4) + 1); | ||
| 343 | } | ||
| 344 | return false; | ||
| 345 | } | ||
| 346 | |||
| 347 | *extension = 0; | ||
| 348 | for (i = 6; i < 8; i++) { | ||
| 349 | *extension = *extension << 8 | ctx->m_rgucReadBuffer[i]; | ||
| 350 | } | ||
| 351 | return true; | ||
| 352 | } | ||
| 353 | |||
| 354 | static EWiiExtensionControllerType GetExtensionType(Uint16 extension_id) | ||
| 355 | { | ||
| 356 | switch (extension_id) { | ||
| 357 | case WII_EXTENSION_NONE: | ||
| 358 | return k_eWiiExtensionControllerType_None; | ||
| 359 | case WII_EXTENSION_NUNCHUK: | ||
| 360 | return k_eWiiExtensionControllerType_Nunchuk; | ||
| 361 | case WII_EXTENSION_GAMEPAD: | ||
| 362 | return k_eWiiExtensionControllerType_Gamepad; | ||
| 363 | case WII_EXTENSION_WIIUPRO: | ||
| 364 | return k_eWiiExtensionControllerType_WiiUPro; | ||
| 365 | default: | ||
| 366 | return k_eWiiExtensionControllerType_Unknown; | ||
| 367 | } | ||
| 368 | } | ||
| 369 | |||
| 370 | static bool SendExtensionReset(SDL_DriverWii_Context *ctx, bool sync) | ||
| 371 | { | ||
| 372 | bool result = true; | ||
| 373 | { | ||
| 374 | Uint8 data = 0x55; | ||
| 375 | result = result && WriteRegister(ctx, 0xA400F0, &data, sizeof(data), sync); | ||
| 376 | } | ||
| 377 | // This write will fail if there is no extension connected, that's fine | ||
| 378 | { | ||
| 379 | Uint8 data = 0x00; | ||
| 380 | (void)WriteRegister(ctx, 0xA400FB, &data, sizeof(data), sync); | ||
| 381 | } | ||
| 382 | return result; | ||
| 383 | } | ||
| 384 | |||
| 385 | static bool GetMotionPlusState(SDL_DriverWii_Context *ctx, bool *connected, Uint8 *mode) | ||
| 386 | { | ||
| 387 | Uint16 extension; | ||
| 388 | |||
| 389 | if (connected) { | ||
| 390 | *connected = false; | ||
| 391 | } | ||
| 392 | if (mode) { | ||
| 393 | *mode = 0; | ||
| 394 | } | ||
| 395 | |||
| 396 | if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) { | ||
| 397 | // The Wii U Pro controller never has the Motion Plus extension | ||
| 398 | return true; | ||
| 399 | } | ||
| 400 | |||
| 401 | if (SendExtensionIdentify(ctx, true) && | ||
| 402 | ParseExtensionIdentifyResponse(ctx, &extension)) { | ||
| 403 | if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) { | ||
| 404 | // Motion Plus is currently active | ||
| 405 | if (connected) { | ||
| 406 | *connected = true; | ||
| 407 | } | ||
| 408 | if (mode) { | ||
| 409 | *mode = (extension >> 8); | ||
| 410 | } | ||
| 411 | return true; | ||
| 412 | } | ||
| 413 | } | ||
| 414 | |||
| 415 | if (ReadRegister(ctx, 0xA600FE, 2, true) && | ||
| 416 | ParseExtensionIdentifyResponse(ctx, &extension)) { | ||
| 417 | if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) { | ||
| 418 | // Motion Plus is currently connected | ||
| 419 | if (connected) { | ||
| 420 | *connected = true; | ||
| 421 | } | ||
| 422 | } | ||
| 423 | return true; | ||
| 424 | } | ||
| 425 | |||
| 426 | // Failed to read the register or parse the response | ||
| 427 | return false; | ||
| 428 | } | ||
| 429 | |||
| 430 | static bool NeedsPeriodicMotionPlusCheck(SDL_DriverWii_Context *ctx, bool status_update) | ||
| 431 | { | ||
| 432 | if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) { | ||
| 433 | // The Wii U Pro controller never has the Motion Plus extension | ||
| 434 | return false; | ||
| 435 | } | ||
| 436 | |||
| 437 | if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE && !status_update) { | ||
| 438 | // We'll get a status update when Motion Plus is disconnected | ||
| 439 | return false; | ||
| 440 | } | ||
| 441 | |||
| 442 | return true; | ||
| 443 | } | ||
| 444 | |||
| 445 | static void SchedulePeriodicMotionPlusCheck(SDL_DriverWii_Context *ctx) | ||
| 446 | { | ||
| 447 | ctx->m_ulNextMotionPlusCheck = SDL_GetTicks() + MOTION_PLUS_UPDATE_TIME_MS; | ||
| 448 | } | ||
| 449 | |||
| 450 | static void CheckMotionPlusConnection(SDL_DriverWii_Context *ctx) | ||
| 451 | { | ||
| 452 | SendExtensionIdentify(ctx, false); | ||
| 453 | |||
| 454 | ctx->m_eCommState = k_eWiiCommunicationState_CheckMotionPlusStage1; | ||
| 455 | } | ||
| 456 | |||
| 457 | static void ActivateMotionPlusWithMode(SDL_DriverWii_Context *ctx, Uint8 mode) | ||
| 458 | { | ||
| 459 | #ifdef SDL_PLATFORM_LINUX | ||
| 460 | /* Linux drivers maintain a lot of state around the Motion Plus | ||
| 461 | * extension, so don't mess with it here. | ||
| 462 | */ | ||
| 463 | #else | ||
| 464 | WriteRegister(ctx, 0xA600FE, &mode, sizeof(mode), true); | ||
| 465 | |||
| 466 | ctx->m_ucMotionPlusMode = mode; | ||
| 467 | #endif // LINUX | ||
| 468 | } | ||
| 469 | |||
| 470 | static void ActivateMotionPlus(SDL_DriverWii_Context *ctx) | ||
| 471 | { | ||
| 472 | Uint8 mode = WII_MOTIONPLUS_MODE_STANDARD; | ||
| 473 | |||
| 474 | // Pick the pass-through mode based on the connected controller | ||
| 475 | if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) { | ||
| 476 | mode = WII_MOTIONPLUS_MODE_NUNCHUK; | ||
| 477 | } else if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Gamepad) { | ||
| 478 | mode = WII_MOTIONPLUS_MODE_GAMEPAD; | ||
| 479 | } | ||
| 480 | ActivateMotionPlusWithMode(ctx, mode); | ||
| 481 | } | ||
| 482 | |||
| 483 | static void DeactivateMotionPlus(SDL_DriverWii_Context *ctx) | ||
| 484 | { | ||
| 485 | Uint8 data = 0x55; | ||
| 486 | WriteRegister(ctx, 0xA400F0, &data, sizeof(data), true); | ||
| 487 | |||
| 488 | // Wait for the deactivation status message | ||
| 489 | ReadInputSync(ctx, k_eWiiInputReportIDs_Status, NULL); | ||
| 490 | |||
| 491 | ctx->m_ucMotionPlusMode = WII_MOTIONPLUS_MODE_NONE; | ||
| 492 | } | ||
| 493 | |||
| 494 | static void UpdatePowerLevelWii(SDL_Joystick *joystick, Uint8 batteryLevelByte) | ||
| 495 | { | ||
| 496 | int percent; | ||
| 497 | if (batteryLevelByte > 178) { | ||
| 498 | percent = 100; | ||
| 499 | } else if (batteryLevelByte > 51) { | ||
| 500 | percent = 70; | ||
| 501 | } else if (batteryLevelByte > 13) { | ||
| 502 | percent = 20; | ||
| 503 | } else { | ||
| 504 | percent = 5; | ||
| 505 | } | ||
| 506 | SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent); | ||
| 507 | } | ||
| 508 | |||
| 509 | static void UpdatePowerLevelWiiU(SDL_Joystick *joystick, Uint8 extensionBatteryByte) | ||
| 510 | { | ||
| 511 | bool charging = !(extensionBatteryByte & 0x08); | ||
| 512 | bool pluggedIn = !(extensionBatteryByte & 0x04); | ||
| 513 | Uint8 batteryLevel = extensionBatteryByte >> 4; | ||
| 514 | |||
| 515 | if (pluggedIn) { | ||
| 516 | joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; | ||
| 517 | } else { | ||
| 518 | joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; | ||
| 519 | } | ||
| 520 | |||
| 521 | /* Not sure if all Wii U Pro controllers act like this, but on mine | ||
| 522 | * 4, 3, and 2 are held for about 20 hours each | ||
| 523 | * 1 is held for about 6 hours | ||
| 524 | * 0 is held for about 2 hours | ||
| 525 | * No value above 4 has been observed. | ||
| 526 | */ | ||
| 527 | SDL_PowerState state; | ||
| 528 | int percent; | ||
| 529 | if (charging) { | ||
| 530 | state = SDL_POWERSTATE_CHARGING; | ||
| 531 | } else if (pluggedIn) { | ||
| 532 | state = SDL_POWERSTATE_CHARGED; | ||
| 533 | } else { | ||
| 534 | state = SDL_POWERSTATE_ON_BATTERY; | ||
| 535 | } | ||
| 536 | if (batteryLevel >= 4) { | ||
| 537 | percent = 100; | ||
| 538 | } else if (batteryLevel == 3) { | ||
| 539 | percent = 70; | ||
| 540 | } else if (batteryLevel == 2) { | ||
| 541 | percent = 40; | ||
| 542 | } else if (batteryLevel == 1) { | ||
| 543 | percent = 10; | ||
| 544 | } else { | ||
| 545 | percent = 3; | ||
| 546 | } | ||
| 547 | SDL_SendJoystickPowerInfo(joystick, state, percent); | ||
| 548 | } | ||
| 549 | |||
| 550 | static EWiiInputReportIDs GetButtonPacketType(SDL_DriverWii_Context *ctx) | ||
| 551 | { | ||
| 552 | switch (ctx->m_eExtensionControllerType) { | ||
| 553 | case k_eWiiExtensionControllerType_WiiUPro: | ||
| 554 | return k_eWiiInputReportIDs_ButtonDataD; | ||
| 555 | case k_eWiiExtensionControllerType_Nunchuk: | ||
| 556 | case k_eWiiExtensionControllerType_Gamepad: | ||
| 557 | if (ctx->m_bReportSensors) { | ||
| 558 | return k_eWiiInputReportIDs_ButtonData5; | ||
| 559 | } else { | ||
| 560 | return k_eWiiInputReportIDs_ButtonData2; | ||
| 561 | } | ||
| 562 | default: | ||
| 563 | if (ctx->m_bReportSensors) { | ||
| 564 | return k_eWiiInputReportIDs_ButtonData5; | ||
| 565 | } else { | ||
| 566 | return k_eWiiInputReportIDs_ButtonData0; | ||
| 567 | } | ||
| 568 | } | ||
| 569 | } | ||
| 570 | |||
| 571 | static bool RequestButtonPacketType(SDL_DriverWii_Context *ctx, EWiiInputReportIDs type) | ||
| 572 | { | ||
| 573 | Uint8 data[3]; | ||
| 574 | Uint8 tt = (Uint8)ctx->m_bRumbleActive; | ||
| 575 | |||
| 576 | // Continuous reporting off, tt & 4 == 0 | ||
| 577 | if (ENABLE_CONTINUOUS_REPORTING) { | ||
| 578 | tt |= 4; | ||
| 579 | } | ||
| 580 | |||
| 581 | data[0] = k_eWiiOutputReportIDs_DataReportingMode; | ||
| 582 | data[1] = tt; | ||
| 583 | data[2] = type; | ||
| 584 | return WriteOutput(ctx, data, sizeof(data), false); | ||
| 585 | } | ||
| 586 | |||
| 587 | static void ResetButtonPacketType(SDL_DriverWii_Context *ctx) | ||
| 588 | { | ||
| 589 | RequestButtonPacketType(ctx, GetButtonPacketType(ctx)); | ||
| 590 | } | ||
| 591 | |||
| 592 | static void InitStickCalibrationData(SDL_DriverWii_Context *ctx) | ||
| 593 | { | ||
| 594 | int i; | ||
| 595 | switch (ctx->m_eExtensionControllerType) { | ||
| 596 | case k_eWiiExtensionControllerType_WiiUPro: | ||
| 597 | for (i = 0; i < 4; i++) { | ||
| 598 | ctx->m_StickCalibrationData[i].min = 1000; | ||
| 599 | ctx->m_StickCalibrationData[i].max = 3000; | ||
| 600 | ctx->m_StickCalibrationData[i].center = 0; | ||
| 601 | ctx->m_StickCalibrationData[i].deadzone = 100; | ||
| 602 | } | ||
| 603 | break; | ||
| 604 | case k_eWiiExtensionControllerType_Gamepad: | ||
| 605 | for (i = 0; i < 4; i++) { | ||
| 606 | ctx->m_StickCalibrationData[i].min = i < 2 ? 9 : 5; | ||
| 607 | ctx->m_StickCalibrationData[i].max = i < 2 ? 54 : 26; | ||
| 608 | ctx->m_StickCalibrationData[i].center = 0; | ||
| 609 | ctx->m_StickCalibrationData[i].deadzone = i < 2 ? 4 : 2; | ||
| 610 | } | ||
| 611 | break; | ||
| 612 | case k_eWiiExtensionControllerType_Nunchuk: | ||
| 613 | for (i = 0; i < 2; i++) { | ||
| 614 | ctx->m_StickCalibrationData[i].min = 40; | ||
| 615 | ctx->m_StickCalibrationData[i].max = 215; | ||
| 616 | ctx->m_StickCalibrationData[i].center = 0; | ||
| 617 | ctx->m_StickCalibrationData[i].deadzone = 10; | ||
| 618 | } | ||
| 619 | break; | ||
| 620 | default: | ||
| 621 | break; | ||
| 622 | } | ||
| 623 | } | ||
| 624 | |||
| 625 | static void InitializeExtension(SDL_DriverWii_Context *ctx) | ||
| 626 | { | ||
| 627 | SendExtensionReset(ctx, true); | ||
| 628 | InitStickCalibrationData(ctx); | ||
| 629 | ResetButtonPacketType(ctx); | ||
| 630 | } | ||
| 631 | |||
| 632 | static void UpdateSlotLED(SDL_DriverWii_Context *ctx) | ||
| 633 | { | ||
| 634 | Uint8 leds; | ||
| 635 | Uint8 data[2]; | ||
| 636 | |||
| 637 | // The lowest bit needs to have the rumble status | ||
| 638 | leds = (Uint8)ctx->m_bRumbleActive; | ||
| 639 | |||
| 640 | if (ctx->m_bPlayerLights) { | ||
| 641 | // Use the same LED codes as Smash 8-player for 5-7 | ||
| 642 | if (ctx->m_nPlayerIndex == 0 || ctx->m_nPlayerIndex > 3) { | ||
| 643 | leds |= k_eWiiPlayerLEDs_P1; | ||
| 644 | } | ||
| 645 | if (ctx->m_nPlayerIndex == 1 || ctx->m_nPlayerIndex == 4) { | ||
| 646 | leds |= k_eWiiPlayerLEDs_P2; | ||
| 647 | } | ||
| 648 | if (ctx->m_nPlayerIndex == 2 || ctx->m_nPlayerIndex == 5) { | ||
| 649 | leds |= k_eWiiPlayerLEDs_P3; | ||
| 650 | } | ||
| 651 | if (ctx->m_nPlayerIndex == 3 || ctx->m_nPlayerIndex == 6) { | ||
| 652 | leds |= k_eWiiPlayerLEDs_P4; | ||
| 653 | } | ||
| 654 | // Turn on all lights for other player indexes | ||
| 655 | if (ctx->m_nPlayerIndex < 0 || ctx->m_nPlayerIndex > 6) { | ||
| 656 | leds |= k_eWiiPlayerLEDs_P1 | k_eWiiPlayerLEDs_P2 | k_eWiiPlayerLEDs_P3 | k_eWiiPlayerLEDs_P4; | ||
| 657 | } | ||
| 658 | } | ||
| 659 | |||
| 660 | data[0] = k_eWiiOutputReportIDs_LEDs; | ||
| 661 | data[1] = leds; | ||
| 662 | WriteOutput(ctx, data, sizeof(data), false); | ||
| 663 | } | ||
| 664 | |||
| 665 | static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 666 | { | ||
| 667 | SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)userdata; | ||
| 668 | bool bPlayerLights = SDL_GetStringBoolean(hint, true); | ||
| 669 | |||
| 670 | if (bPlayerLights != ctx->m_bPlayerLights) { | ||
| 671 | ctx->m_bPlayerLights = bPlayerLights; | ||
| 672 | |||
| 673 | UpdateSlotLED(ctx); | ||
| 674 | } | ||
| 675 | } | ||
| 676 | |||
| 677 | static EWiiExtensionControllerType ReadExtensionControllerType(SDL_HIDAPI_Device *device) | ||
| 678 | { | ||
| 679 | SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; | ||
| 680 | EWiiExtensionControllerType eExtensionControllerType = k_eWiiExtensionControllerType_Unknown; | ||
| 681 | const int MAX_ATTEMPTS = 20; | ||
| 682 | int attempts = 0; | ||
| 683 | |||
| 684 | // Create enough of a context to read the controller type from the device | ||
| 685 | for (attempts = 0; attempts < MAX_ATTEMPTS; ++attempts) { | ||
| 686 | Uint16 extension; | ||
| 687 | if (SendExtensionIdentify(ctx, true) && | ||
| 688 | ParseExtensionIdentifyResponse(ctx, &extension)) { | ||
| 689 | Uint8 motion_plus_mode = 0; | ||
| 690 | if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) { | ||
| 691 | motion_plus_mode = (Uint8)(extension >> 8); | ||
| 692 | } | ||
| 693 | if (motion_plus_mode || extension == WII_EXTENSION_UNINITIALIZED) { | ||
| 694 | SendExtensionReset(ctx, true); | ||
| 695 | if (SendExtensionIdentify(ctx, true)) { | ||
| 696 | ParseExtensionIdentifyResponse(ctx, &extension); | ||
| 697 | } | ||
| 698 | } | ||
| 699 | |||
| 700 | eExtensionControllerType = GetExtensionType(extension); | ||
| 701 | |||
| 702 | // Reset the Motion Plus controller if needed | ||
| 703 | if (motion_plus_mode) { | ||
| 704 | ActivateMotionPlusWithMode(ctx, motion_plus_mode); | ||
| 705 | } | ||
| 706 | break; | ||
| 707 | } | ||
| 708 | } | ||
| 709 | return eExtensionControllerType; | ||
| 710 | } | ||
| 711 | |||
| 712 | static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) | ||
| 713 | { | ||
| 714 | SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; | ||
| 715 | |||
| 716 | switch (ctx->m_eExtensionControllerType) { | ||
| 717 | case k_eWiiExtensionControllerType_None: | ||
| 718 | HIDAPI_SetDeviceName(device, "Nintendo Wii Remote"); | ||
| 719 | break; | ||
| 720 | case k_eWiiExtensionControllerType_Nunchuk: | ||
| 721 | HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Nunchuk"); | ||
| 722 | break; | ||
| 723 | case k_eWiiExtensionControllerType_Gamepad: | ||
| 724 | HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Classic Controller"); | ||
| 725 | break; | ||
| 726 | case k_eWiiExtensionControllerType_WiiUPro: | ||
| 727 | HIDAPI_SetDeviceName(device, "Nintendo Wii U Pro Controller"); | ||
| 728 | break; | ||
| 729 | default: | ||
| 730 | HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Unknown Extension"); | ||
| 731 | break; | ||
| 732 | } | ||
| 733 | device->guid.data[15] = ctx->m_eExtensionControllerType; | ||
| 734 | } | ||
| 735 | |||
| 736 | static bool HIDAPI_DriverWii_InitDevice(SDL_HIDAPI_Device *device) | ||
| 737 | { | ||
| 738 | SDL_DriverWii_Context *ctx; | ||
| 739 | |||
| 740 | ctx = (SDL_DriverWii_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 741 | if (!ctx) { | ||
| 742 | return false; | ||
| 743 | } | ||
| 744 | ctx->device = device; | ||
| 745 | device->context = ctx; | ||
| 746 | |||
| 747 | if (device->vendor_id == USB_VENDOR_NINTENDO) { | ||
| 748 | ctx->m_eExtensionControllerType = ReadExtensionControllerType(device); | ||
| 749 | |||
| 750 | UpdateDeviceIdentity(device); | ||
| 751 | } | ||
| 752 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 753 | } | ||
| 754 | |||
| 755 | static int HIDAPI_DriverWii_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 756 | { | ||
| 757 | return -1; | ||
| 758 | } | ||
| 759 | |||
| 760 | static void HIDAPI_DriverWii_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 761 | { | ||
| 762 | SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; | ||
| 763 | |||
| 764 | if (!ctx->joystick) { | ||
| 765 | return; | ||
| 766 | } | ||
| 767 | |||
| 768 | ctx->m_nPlayerIndex = player_index; | ||
| 769 | |||
| 770 | UpdateSlotLED(ctx); | ||
| 771 | } | ||
| 772 | |||
| 773 | static bool HIDAPI_DriverWii_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 774 | { | ||
| 775 | SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; | ||
| 776 | |||
| 777 | SDL_AssertJoysticksLocked(); | ||
| 778 | |||
| 779 | ctx->joystick = joystick; | ||
| 780 | |||
| 781 | InitializeExtension(ctx); | ||
| 782 | |||
| 783 | GetMotionPlusState(ctx, &ctx->m_bMotionPlusPresent, &ctx->m_ucMotionPlusMode); | ||
| 784 | |||
| 785 | if (NeedsPeriodicMotionPlusCheck(ctx, false)) { | ||
| 786 | SchedulePeriodicMotionPlusCheck(ctx); | ||
| 787 | } | ||
| 788 | |||
| 789 | if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_None || | ||
| 790 | ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) { | ||
| 791 | SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f); | ||
| 792 | if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) { | ||
| 793 | SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_L, 100.0f); | ||
| 794 | } | ||
| 795 | |||
| 796 | if (ctx->m_bMotionPlusPresent) { | ||
| 797 | SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 100.0f); | ||
| 798 | } | ||
| 799 | } | ||
| 800 | |||
| 801 | // Initialize player index (needed for setting LEDs) | ||
| 802 | ctx->m_nPlayerIndex = SDL_GetJoystickPlayerIndex(joystick); | ||
| 803 | ctx->m_bPlayerLights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED, true); | ||
| 804 | UpdateSlotLED(ctx); | ||
| 805 | |||
| 806 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED, | ||
| 807 | SDL_PlayerLEDHintChanged, ctx); | ||
| 808 | |||
| 809 | // Initialize the joystick capabilities | ||
| 810 | if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) { | ||
| 811 | joystick->nbuttons = 15; | ||
| 812 | } else { | ||
| 813 | // Maximum is Classic Controller + Wiimote | ||
| 814 | joystick->nbuttons = k_eWiiButtons_Max; | ||
| 815 | } | ||
| 816 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 817 | |||
| 818 | ctx->m_ulLastInput = SDL_GetTicks(); | ||
| 819 | |||
| 820 | return true; | ||
| 821 | } | ||
| 822 | |||
| 823 | static bool HIDAPI_DriverWii_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 824 | { | ||
| 825 | SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; | ||
| 826 | bool active = (low_frequency_rumble || high_frequency_rumble); | ||
| 827 | |||
| 828 | if (active != ctx->m_bRumbleActive) { | ||
| 829 | Uint8 data[2]; | ||
| 830 | |||
| 831 | data[0] = k_eWiiOutputReportIDs_Rumble; | ||
| 832 | data[1] = (Uint8)active; | ||
| 833 | WriteOutput(ctx, data, sizeof(data), false); | ||
| 834 | |||
| 835 | ctx->m_bRumbleActive = active; | ||
| 836 | } | ||
| 837 | return true; | ||
| 838 | } | ||
| 839 | |||
| 840 | static bool HIDAPI_DriverWii_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 841 | { | ||
| 842 | return SDL_Unsupported(); | ||
| 843 | } | ||
| 844 | |||
| 845 | static Uint32 HIDAPI_DriverWii_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 846 | { | ||
| 847 | return SDL_JOYSTICK_CAP_RUMBLE; | ||
| 848 | } | ||
| 849 | |||
| 850 | static bool HIDAPI_DriverWii_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 851 | { | ||
| 852 | return SDL_Unsupported(); | ||
| 853 | } | ||
| 854 | |||
| 855 | static bool HIDAPI_DriverWii_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 856 | { | ||
| 857 | return SDL_Unsupported(); | ||
| 858 | } | ||
| 859 | |||
| 860 | static bool HIDAPI_DriverWii_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 861 | { | ||
| 862 | SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; | ||
| 863 | |||
| 864 | if (enabled != ctx->m_bReportSensors) { | ||
| 865 | ctx->m_bReportSensors = enabled; | ||
| 866 | |||
| 867 | if (ctx->m_bMotionPlusPresent) { | ||
| 868 | if (enabled) { | ||
| 869 | ActivateMotionPlus(ctx); | ||
| 870 | } else { | ||
| 871 | DeactivateMotionPlus(ctx); | ||
| 872 | } | ||
| 873 | } | ||
| 874 | |||
| 875 | ResetButtonPacketType(ctx); | ||
| 876 | } | ||
| 877 | return true; | ||
| 878 | } | ||
| 879 | |||
| 880 | static void PostStickCalibrated(Uint64 timestamp, SDL_Joystick *joystick, StickCalibrationData *calibration, Uint8 axis, Uint16 data) | ||
| 881 | { | ||
| 882 | Sint16 value = 0; | ||
| 883 | if (!calibration->center) { | ||
| 884 | // Center on first read | ||
| 885 | calibration->center = data; | ||
| 886 | return; | ||
| 887 | } | ||
| 888 | if (data < calibration->min) { | ||
| 889 | calibration->min = data; | ||
| 890 | } | ||
| 891 | if (data > calibration->max) { | ||
| 892 | calibration->max = data; | ||
| 893 | } | ||
| 894 | if (data < calibration->center - calibration->deadzone) { | ||
| 895 | Uint16 zero = calibration->center - calibration->deadzone; | ||
| 896 | Uint16 range = zero - calibration->min; | ||
| 897 | Uint16 distance = zero - data; | ||
| 898 | float fvalue = (float)distance / (float)range; | ||
| 899 | value = (Sint16)(fvalue * SDL_JOYSTICK_AXIS_MIN); | ||
| 900 | } else if (data > calibration->center + calibration->deadzone) { | ||
| 901 | Uint16 zero = calibration->center + calibration->deadzone; | ||
| 902 | Uint16 range = calibration->max - zero; | ||
| 903 | Uint16 distance = data - zero; | ||
| 904 | float fvalue = (float)distance / (float)range; | ||
| 905 | value = (Sint16)(fvalue * SDL_JOYSTICK_AXIS_MAX); | ||
| 906 | } | ||
| 907 | if (axis == SDL_GAMEPAD_AXIS_LEFTY || axis == SDL_GAMEPAD_AXIS_RIGHTY) { | ||
| 908 | if (value) { | ||
| 909 | value = ~value; | ||
| 910 | } | ||
| 911 | } | ||
| 912 | SDL_SendJoystickAxis(timestamp, joystick, axis, value); | ||
| 913 | } | ||
| 914 | |||
| 915 | /* Send button data to SDL | ||
| 916 | *`defs` is a mapping for each bit to which button it represents. 0xFF indicates an unused bit | ||
| 917 | *`data` is the button data from the controller | ||
| 918 | *`size` is the number of bytes in `data` and the number of arrays of 8 mappings in `defs` | ||
| 919 | *`on` is the joystick value to be sent if a bit is on | ||
| 920 | *`off` is the joystick value to be sent if a bit is off | ||
| 921 | */ | ||
| 922 | static void PostPackedButtonData(Uint64 timestamp, SDL_Joystick *joystick, const Uint8 defs[][8], const Uint8 *data, int size, bool on, bool off) | ||
| 923 | { | ||
| 924 | int i, j; | ||
| 925 | |||
| 926 | for (i = 0; i < size; i++) { | ||
| 927 | for (j = 0; j < 8; j++) { | ||
| 928 | Uint8 button = defs[i][j]; | ||
| 929 | if (button != 0xFF) { | ||
| 930 | bool down = (data[i] >> j) & 1 ? on : off; | ||
| 931 | SDL_SendJoystickButton(timestamp, joystick, button, down); | ||
| 932 | } | ||
| 933 | } | ||
| 934 | } | ||
| 935 | } | ||
| 936 | |||
| 937 | static const Uint8 GAMEPAD_BUTTON_DEFS[3][8] = { | ||
| 938 | { | ||
| 939 | 0xFF /* Unused */, | ||
| 940 | SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, | ||
| 941 | SDL_GAMEPAD_BUTTON_START, | ||
| 942 | SDL_GAMEPAD_BUTTON_GUIDE, | ||
| 943 | SDL_GAMEPAD_BUTTON_BACK, | ||
| 944 | SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, | ||
| 945 | SDL_GAMEPAD_BUTTON_DPAD_DOWN, | ||
| 946 | SDL_GAMEPAD_BUTTON_DPAD_RIGHT, | ||
| 947 | }, | ||
| 948 | { | ||
| 949 | SDL_GAMEPAD_BUTTON_DPAD_UP, | ||
| 950 | SDL_GAMEPAD_BUTTON_DPAD_LEFT, | ||
| 951 | 0xFF /* ZR */, | ||
| 952 | SDL_GAMEPAD_BUTTON_NORTH, | ||
| 953 | SDL_GAMEPAD_BUTTON_EAST, | ||
| 954 | SDL_GAMEPAD_BUTTON_WEST, | ||
| 955 | SDL_GAMEPAD_BUTTON_SOUTH, | ||
| 956 | 0xFF /*ZL*/, | ||
| 957 | }, | ||
| 958 | { | ||
| 959 | SDL_GAMEPAD_BUTTON_RIGHT_STICK, | ||
| 960 | SDL_GAMEPAD_BUTTON_LEFT_STICK, | ||
| 961 | 0xFF /* Charging */, | ||
| 962 | 0xFF /* Plugged In */, | ||
| 963 | 0xFF /* Unused */, | ||
| 964 | 0xFF /* Unused */, | ||
| 965 | 0xFF /* Unused */, | ||
| 966 | 0xFF /* Unused */, | ||
| 967 | } | ||
| 968 | }; | ||
| 969 | |||
| 970 | static const Uint8 MP_GAMEPAD_BUTTON_DEFS[3][8] = { | ||
| 971 | { | ||
| 972 | 0xFF /* Unused */, | ||
| 973 | SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, | ||
| 974 | SDL_GAMEPAD_BUTTON_START, | ||
| 975 | SDL_GAMEPAD_BUTTON_GUIDE, | ||
| 976 | SDL_GAMEPAD_BUTTON_BACK, | ||
| 977 | SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, | ||
| 978 | SDL_GAMEPAD_BUTTON_DPAD_DOWN, | ||
| 979 | SDL_GAMEPAD_BUTTON_DPAD_RIGHT, | ||
| 980 | }, | ||
| 981 | { | ||
| 982 | 0xFF /* Motion Plus data */, | ||
| 983 | 0xFF /* Motion Plus data */, | ||
| 984 | 0xFF /* ZR */, | ||
| 985 | SDL_GAMEPAD_BUTTON_NORTH, | ||
| 986 | SDL_GAMEPAD_BUTTON_EAST, | ||
| 987 | SDL_GAMEPAD_BUTTON_WEST, | ||
| 988 | SDL_GAMEPAD_BUTTON_SOUTH, | ||
| 989 | 0xFF /*ZL*/, | ||
| 990 | }, | ||
| 991 | { | ||
| 992 | SDL_GAMEPAD_BUTTON_RIGHT_STICK, | ||
| 993 | SDL_GAMEPAD_BUTTON_LEFT_STICK, | ||
| 994 | 0xFF /* Charging */, | ||
| 995 | 0xFF /* Plugged In */, | ||
| 996 | 0xFF /* Unused */, | ||
| 997 | 0xFF /* Unused */, | ||
| 998 | 0xFF /* Unused */, | ||
| 999 | 0xFF /* Unused */, | ||
| 1000 | } | ||
| 1001 | }; | ||
| 1002 | |||
| 1003 | static const Uint8 MP_FIXUP_DPAD_BUTTON_DEFS[2][8] = { | ||
| 1004 | { | ||
| 1005 | SDL_GAMEPAD_BUTTON_DPAD_UP, | ||
| 1006 | 0xFF, | ||
| 1007 | 0xFF, | ||
| 1008 | 0xFF, | ||
| 1009 | 0xFF, | ||
| 1010 | 0xFF, | ||
| 1011 | 0xFF, | ||
| 1012 | 0xFF, | ||
| 1013 | }, | ||
| 1014 | { | ||
| 1015 | SDL_GAMEPAD_BUTTON_DPAD_LEFT, | ||
| 1016 | 0xFF, | ||
| 1017 | 0xFF, | ||
| 1018 | 0xFF, | ||
| 1019 | 0xFF, | ||
| 1020 | 0xFF, | ||
| 1021 | 0xFF, | ||
| 1022 | 0xFF, | ||
| 1023 | } | ||
| 1024 | }; | ||
| 1025 | |||
| 1026 | static void HandleWiiUProButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) | ||
| 1027 | { | ||
| 1028 | static const Uint8 axes[] = { SDL_GAMEPAD_AXIS_LEFTX, SDL_GAMEPAD_AXIS_RIGHTX, SDL_GAMEPAD_AXIS_LEFTY, SDL_GAMEPAD_AXIS_RIGHTY }; | ||
| 1029 | const Uint8(*buttons)[8] = GAMEPAD_BUTTON_DEFS; | ||
| 1030 | Uint8 zl, zr; | ||
| 1031 | int i; | ||
| 1032 | |||
| 1033 | if (data->ucNExtensionBytes < 11) { | ||
| 1034 | return; | ||
| 1035 | } | ||
| 1036 | |||
| 1037 | // Buttons | ||
| 1038 | PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucExtension + 8, 3, false, true); | ||
| 1039 | |||
| 1040 | // Triggers | ||
| 1041 | zl = data->rgucExtension[9] & 0x80; | ||
| 1042 | zr = data->rgucExtension[9] & 0x04; | ||
| 1043 | SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, zl ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX); | ||
| 1044 | SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, zr ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX); | ||
| 1045 | |||
| 1046 | // Sticks | ||
| 1047 | for (i = 0; i < 4; i++) { | ||
| 1048 | Uint16 value = data->rgucExtension[i * 2] | (data->rgucExtension[i * 2 + 1] << 8); | ||
| 1049 | PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[i], axes[i], value); | ||
| 1050 | } | ||
| 1051 | |||
| 1052 | // Power | ||
| 1053 | UpdatePowerLevelWiiU(joystick, data->rgucExtension[10]); | ||
| 1054 | } | ||
| 1055 | |||
| 1056 | static void HandleGamepadControllerButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) | ||
| 1057 | { | ||
| 1058 | const Uint8(*buttons)[8] = (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) ? MP_GAMEPAD_BUTTON_DEFS : GAMEPAD_BUTTON_DEFS; | ||
| 1059 | Uint8 lx, ly, rx, ry, zl, zr; | ||
| 1060 | |||
| 1061 | if (data->ucNExtensionBytes < 6) { | ||
| 1062 | return; | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | // Buttons | ||
| 1066 | PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucExtension + 4, 2, false, true); | ||
| 1067 | if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) { | ||
| 1068 | PostPackedButtonData(ctx->timestamp, joystick, MP_FIXUP_DPAD_BUTTON_DEFS, data->rgucExtension, 2, false, true); | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | // Triggers | ||
| 1072 | zl = data->rgucExtension[5] & 0x80; | ||
| 1073 | zr = data->rgucExtension[5] & 0x04; | ||
| 1074 | SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, zl ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX); | ||
| 1075 | SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, zr ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX); | ||
| 1076 | |||
| 1077 | // Sticks | ||
| 1078 | if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) { | ||
| 1079 | lx = data->rgucExtension[0] & 0x3E; | ||
| 1080 | ly = data->rgucExtension[1] & 0x3E; | ||
| 1081 | } else { | ||
| 1082 | lx = data->rgucExtension[0] & 0x3F; | ||
| 1083 | ly = data->rgucExtension[1] & 0x3F; | ||
| 1084 | } | ||
| 1085 | rx = (data->rgucExtension[2] >> 7) | ((data->rgucExtension[1] >> 5) & 0x06) | ((data->rgucExtension[0] >> 3) & 0x18); | ||
| 1086 | ry = data->rgucExtension[2] & 0x1F; | ||
| 1087 | PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[0], SDL_GAMEPAD_AXIS_LEFTX, lx); | ||
| 1088 | PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[1], SDL_GAMEPAD_AXIS_LEFTY, ly); | ||
| 1089 | PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[2], SDL_GAMEPAD_AXIS_RIGHTX, rx); | ||
| 1090 | PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[3], SDL_GAMEPAD_AXIS_RIGHTY, ry); | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | static void HandleWiiRemoteButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) | ||
| 1094 | { | ||
| 1095 | static const Uint8 buttons[2][8] = { | ||
| 1096 | { | ||
| 1097 | k_eWiiButtons_DPad_Left, | ||
| 1098 | k_eWiiButtons_DPad_Right, | ||
| 1099 | k_eWiiButtons_DPad_Down, | ||
| 1100 | k_eWiiButtons_DPad_Up, | ||
| 1101 | k_eWiiButtons_Plus, | ||
| 1102 | 0xFF /* Unused */, | ||
| 1103 | 0xFF /* Unused */, | ||
| 1104 | 0xFF /* Unused */, | ||
| 1105 | }, | ||
| 1106 | { | ||
| 1107 | k_eWiiButtons_Two, | ||
| 1108 | k_eWiiButtons_One, | ||
| 1109 | k_eWiiButtons_B, | ||
| 1110 | k_eWiiButtons_A, | ||
| 1111 | k_eWiiButtons_Minus, | ||
| 1112 | 0xFF /* Unused */, | ||
| 1113 | 0xFF /* Unused */, | ||
| 1114 | k_eWiiButtons_Home, | ||
| 1115 | } | ||
| 1116 | }; | ||
| 1117 | if (data->hasBaseButtons) { | ||
| 1118 | PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucBaseButtons, 2, true, false); | ||
| 1119 | } | ||
| 1120 | } | ||
| 1121 | |||
| 1122 | static void HandleWiiRemoteButtonDataAsMainController(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) | ||
| 1123 | { | ||
| 1124 | /* Wii remote maps really badly to a normal controller | ||
| 1125 | * Mapped 1 and 2 as X and Y | ||
| 1126 | * Not going to attempt positional mapping | ||
| 1127 | */ | ||
| 1128 | static const Uint8 buttons[2][8] = { | ||
| 1129 | { | ||
| 1130 | SDL_GAMEPAD_BUTTON_DPAD_LEFT, | ||
| 1131 | SDL_GAMEPAD_BUTTON_DPAD_RIGHT, | ||
| 1132 | SDL_GAMEPAD_BUTTON_DPAD_DOWN, | ||
| 1133 | SDL_GAMEPAD_BUTTON_DPAD_UP, | ||
| 1134 | SDL_GAMEPAD_BUTTON_START, | ||
| 1135 | 0xFF /* Unused */, | ||
| 1136 | 0xFF /* Unused */, | ||
| 1137 | 0xFF /* Unused */, | ||
| 1138 | }, | ||
| 1139 | { | ||
| 1140 | SDL_GAMEPAD_BUTTON_NORTH, | ||
| 1141 | SDL_GAMEPAD_BUTTON_WEST, | ||
| 1142 | SDL_GAMEPAD_BUTTON_SOUTH, | ||
| 1143 | SDL_GAMEPAD_BUTTON_EAST, | ||
| 1144 | SDL_GAMEPAD_BUTTON_BACK, | ||
| 1145 | 0xFF /* Unused */, | ||
| 1146 | 0xFF /* Unused */, | ||
| 1147 | SDL_GAMEPAD_BUTTON_GUIDE, | ||
| 1148 | } | ||
| 1149 | }; | ||
| 1150 | if (data->hasBaseButtons) { | ||
| 1151 | PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucBaseButtons, 2, true, false); | ||
| 1152 | } | ||
| 1153 | } | ||
| 1154 | |||
| 1155 | static void HandleNunchuckButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) | ||
| 1156 | { | ||
| 1157 | bool c_button, z_button; | ||
| 1158 | |||
| 1159 | if (data->ucNExtensionBytes < 6) { | ||
| 1160 | return; | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_NUNCHUK) { | ||
| 1164 | c_button = (data->rgucExtension[5] & 0x08) ? false : true; | ||
| 1165 | z_button = (data->rgucExtension[5] & 0x04) ? false : true; | ||
| 1166 | } else { | ||
| 1167 | c_button = (data->rgucExtension[5] & 0x02) ? false : true; | ||
| 1168 | z_button = (data->rgucExtension[5] & 0x01) ? false : true; | ||
| 1169 | } | ||
| 1170 | SDL_SendJoystickButton(ctx->timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, c_button); | ||
| 1171 | SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, z_button ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN); | ||
| 1172 | PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[0], SDL_GAMEPAD_AXIS_LEFTX, data->rgucExtension[0]); | ||
| 1173 | PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[1], SDL_GAMEPAD_AXIS_LEFTY, data->rgucExtension[1]); | ||
| 1174 | |||
| 1175 | if (ctx->m_bReportSensors) { | ||
| 1176 | const float ACCEL_RES_PER_G = 200.0f; | ||
| 1177 | Sint16 x, y, z; | ||
| 1178 | float values[3]; | ||
| 1179 | |||
| 1180 | x = (data->rgucExtension[2] << 2); | ||
| 1181 | y = (data->rgucExtension[3] << 2); | ||
| 1182 | z = (data->rgucExtension[4] << 2); | ||
| 1183 | |||
| 1184 | if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_NUNCHUK) { | ||
| 1185 | x |= ((data->rgucExtension[5] >> 3) & 0x02); | ||
| 1186 | y |= ((data->rgucExtension[5] >> 4) & 0x02); | ||
| 1187 | z &= ~0x04; | ||
| 1188 | z |= ((data->rgucExtension[5] >> 5) & 0x06); | ||
| 1189 | } else { | ||
| 1190 | x |= ((data->rgucExtension[5] >> 2) & 0x03); | ||
| 1191 | y |= ((data->rgucExtension[5] >> 4) & 0x03); | ||
| 1192 | z |= ((data->rgucExtension[5] >> 6) & 0x03); | ||
| 1193 | } | ||
| 1194 | |||
| 1195 | x -= 0x200; | ||
| 1196 | y -= 0x200; | ||
| 1197 | z -= 0x200; | ||
| 1198 | |||
| 1199 | values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; | ||
| 1200 | values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; | ||
| 1201 | values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; | ||
| 1202 | SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_ACCEL_L, ctx->timestamp, values, 3); | ||
| 1203 | } | ||
| 1204 | } | ||
| 1205 | |||
| 1206 | static void HandleMotionPlusData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) | ||
| 1207 | { | ||
| 1208 | if (ctx->m_bReportSensors) { | ||
| 1209 | const float GYRO_RES_PER_DEGREE = 8192.0f; | ||
| 1210 | int x, y, z; | ||
| 1211 | float values[3]; | ||
| 1212 | |||
| 1213 | x = (data->rgucExtension[0] | ((data->rgucExtension[3] << 6) & 0xFF00)) - 8192; | ||
| 1214 | y = (data->rgucExtension[1] | ((data->rgucExtension[4] << 6) & 0xFF00)) - 8192; | ||
| 1215 | z = (data->rgucExtension[2] | ((data->rgucExtension[5] << 6) & 0xFF00)) - 8192; | ||
| 1216 | |||
| 1217 | if (data->rgucExtension[3] & 0x02) { | ||
| 1218 | // Slow rotation rate: 8192/440 units per deg/s | ||
| 1219 | x *= 440; | ||
| 1220 | } else { | ||
| 1221 | // Fast rotation rate: 8192/2000 units per deg/s | ||
| 1222 | x *= 2000; | ||
| 1223 | } | ||
| 1224 | if (data->rgucExtension[4] & 0x02) { | ||
| 1225 | // Slow rotation rate: 8192/440 units per deg/s | ||
| 1226 | y *= 440; | ||
| 1227 | } else { | ||
| 1228 | // Fast rotation rate: 8192/2000 units per deg/s | ||
| 1229 | y *= 2000; | ||
| 1230 | } | ||
| 1231 | if (data->rgucExtension[3] & 0x01) { | ||
| 1232 | // Slow rotation rate: 8192/440 units per deg/s | ||
| 1233 | z *= 440; | ||
| 1234 | } else { | ||
| 1235 | // Fast rotation rate: 8192/2000 units per deg/s | ||
| 1236 | z *= 2000; | ||
| 1237 | } | ||
| 1238 | |||
| 1239 | values[0] = -((float)z / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f; | ||
| 1240 | values[1] = ((float)x / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f; | ||
| 1241 | values[2] = ((float)y / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f; | ||
| 1242 | SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_GYRO, ctx->timestamp, values, 3); | ||
| 1243 | } | ||
| 1244 | } | ||
| 1245 | |||
| 1246 | static void HandleWiiRemoteAccelData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data) | ||
| 1247 | { | ||
| 1248 | const float ACCEL_RES_PER_G = 100.0f; | ||
| 1249 | Sint16 x, y, z; | ||
| 1250 | float values[3]; | ||
| 1251 | |||
| 1252 | if (!ctx->m_bReportSensors) { | ||
| 1253 | return; | ||
| 1254 | } | ||
| 1255 | |||
| 1256 | x = ((data->rgucAccelerometer[0] << 2) | ((data->rgucBaseButtons[0] >> 5) & 0x03)) - 0x200; | ||
| 1257 | y = ((data->rgucAccelerometer[1] << 2) | ((data->rgucBaseButtons[1] >> 4) & 0x02)) - 0x200; | ||
| 1258 | z = ((data->rgucAccelerometer[2] << 2) | ((data->rgucBaseButtons[1] >> 5) & 0x02)) - 0x200; | ||
| 1259 | |||
| 1260 | values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; | ||
| 1261 | values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; | ||
| 1262 | values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; | ||
| 1263 | SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_ACCEL, ctx->timestamp, values, 3); | ||
| 1264 | } | ||
| 1265 | |||
| 1266 | static void HandleButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, WiiButtonData *data) | ||
| 1267 | { | ||
| 1268 | if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) { | ||
| 1269 | HandleWiiUProButtonData(ctx, joystick, data); | ||
| 1270 | return; | ||
| 1271 | } | ||
| 1272 | |||
| 1273 | if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE && | ||
| 1274 | data->ucNExtensionBytes > 5) { | ||
| 1275 | if (data->rgucExtension[5] & 0x01) { | ||
| 1276 | // The data is invalid, possibly during a hotplug | ||
| 1277 | return; | ||
| 1278 | } | ||
| 1279 | |||
| 1280 | if (data->rgucExtension[4] & 0x01) { | ||
| 1281 | if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_None) { | ||
| 1282 | // Something was plugged into the extension port, reinitialize to get new state | ||
| 1283 | ctx->m_bDisconnected = true; | ||
| 1284 | } | ||
| 1285 | } else { | ||
| 1286 | if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_None) { | ||
| 1287 | // Something was removed from the extension port, reinitialize to get new state | ||
| 1288 | ctx->m_bDisconnected = true; | ||
| 1289 | } | ||
| 1290 | } | ||
| 1291 | |||
| 1292 | if (data->rgucExtension[5] & 0x02) { | ||
| 1293 | HandleMotionPlusData(ctx, joystick, data); | ||
| 1294 | |||
| 1295 | // The extension data is consumed | ||
| 1296 | data->ucNExtensionBytes = 0; | ||
| 1297 | } | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | HandleWiiRemoteButtonData(ctx, joystick, data); | ||
| 1301 | switch (ctx->m_eExtensionControllerType) { | ||
| 1302 | case k_eWiiExtensionControllerType_Nunchuk: | ||
| 1303 | HandleNunchuckButtonData(ctx, joystick, data); | ||
| 1304 | SDL_FALLTHROUGH; | ||
| 1305 | case k_eWiiExtensionControllerType_None: | ||
| 1306 | HandleWiiRemoteButtonDataAsMainController(ctx, joystick, data); | ||
| 1307 | break; | ||
| 1308 | case k_eWiiExtensionControllerType_Gamepad: | ||
| 1309 | HandleGamepadControllerButtonData(ctx, joystick, data); | ||
| 1310 | break; | ||
| 1311 | default: | ||
| 1312 | break; | ||
| 1313 | } | ||
| 1314 | HandleWiiRemoteAccelData(ctx, joystick, data); | ||
| 1315 | } | ||
| 1316 | |||
| 1317 | static void GetBaseButtons(WiiButtonData *dst, const Uint8 *src) | ||
| 1318 | { | ||
| 1319 | SDL_memcpy(dst->rgucBaseButtons, src, 2); | ||
| 1320 | dst->hasBaseButtons = true; | ||
| 1321 | } | ||
| 1322 | |||
| 1323 | static void GetAccelerometer(WiiButtonData *dst, const Uint8 *src) | ||
| 1324 | { | ||
| 1325 | SDL_memcpy(dst->rgucAccelerometer, src, 3); | ||
| 1326 | dst->hasAccelerometer = true; | ||
| 1327 | } | ||
| 1328 | |||
| 1329 | static void GetExtensionData(WiiButtonData *dst, const Uint8 *src, int size) | ||
| 1330 | { | ||
| 1331 | bool valid_data = false; | ||
| 1332 | int i; | ||
| 1333 | |||
| 1334 | if (size > sizeof(dst->rgucExtension)) { | ||
| 1335 | size = sizeof(dst->rgucExtension); | ||
| 1336 | } | ||
| 1337 | |||
| 1338 | for (i = 0; i < size; ++i) { | ||
| 1339 | if (src[i] != 0xFF) { | ||
| 1340 | valid_data = true; | ||
| 1341 | break; | ||
| 1342 | } | ||
| 1343 | } | ||
| 1344 | if (valid_data) { | ||
| 1345 | SDL_memcpy(dst->rgucExtension, src, size); | ||
| 1346 | dst->ucNExtensionBytes = (Uint8)size; | ||
| 1347 | } | ||
| 1348 | } | ||
| 1349 | |||
| 1350 | static void HandleStatus(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick) | ||
| 1351 | { | ||
| 1352 | bool hadExtension = ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_None; | ||
| 1353 | bool hasExtension = (ctx->m_rgucReadBuffer[3] & 2) ? true : false; | ||
| 1354 | WiiButtonData data; | ||
| 1355 | SDL_zero(data); | ||
| 1356 | GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); | ||
| 1357 | HandleButtonData(ctx, joystick, &data); | ||
| 1358 | |||
| 1359 | if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_WiiUPro) { | ||
| 1360 | // Wii U has separate battery level tracking | ||
| 1361 | UpdatePowerLevelWii(joystick, ctx->m_rgucReadBuffer[6]); | ||
| 1362 | } | ||
| 1363 | |||
| 1364 | // The report data format has been reset, need to update it | ||
| 1365 | ResetButtonPacketType(ctx); | ||
| 1366 | |||
| 1367 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Status update, extension %s", hasExtension ? "CONNECTED" : "DISCONNECTED"); | ||
| 1368 | |||
| 1369 | /* When Motion Plus is active, we get extension connect/disconnect status | ||
| 1370 | * through the Motion Plus packets. Otherwise we can use the status here. | ||
| 1371 | */ | ||
| 1372 | if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE) { | ||
| 1373 | /* Check to make sure the Motion Plus extension state hasn't changed, | ||
| 1374 | * otherwise we'll get extension connect/disconnect status through | ||
| 1375 | * Motion Plus packets. | ||
| 1376 | */ | ||
| 1377 | if (NeedsPeriodicMotionPlusCheck(ctx, true)) { | ||
| 1378 | ctx->m_ulNextMotionPlusCheck = SDL_GetTicks(); | ||
| 1379 | } | ||
| 1380 | |||
| 1381 | } else if (hadExtension != hasExtension) { | ||
| 1382 | // Reinitialize to get new state | ||
| 1383 | ctx->m_bDisconnected = true; | ||
| 1384 | } | ||
| 1385 | } | ||
| 1386 | |||
| 1387 | static void HandleResponse(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick) | ||
| 1388 | { | ||
| 1389 | EWiiInputReportIDs type = (EWiiInputReportIDs)ctx->m_rgucReadBuffer[0]; | ||
| 1390 | WiiButtonData data; | ||
| 1391 | SDL_assert(type == k_eWiiInputReportIDs_Acknowledge || type == k_eWiiInputReportIDs_ReadMemory); | ||
| 1392 | SDL_zero(data); | ||
| 1393 | GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); | ||
| 1394 | HandleButtonData(ctx, joystick, &data); | ||
| 1395 | |||
| 1396 | switch (ctx->m_eCommState) { | ||
| 1397 | case k_eWiiCommunicationState_None: | ||
| 1398 | break; | ||
| 1399 | |||
| 1400 | case k_eWiiCommunicationState_CheckMotionPlusStage1: | ||
| 1401 | case k_eWiiCommunicationState_CheckMotionPlusStage2: | ||
| 1402 | { | ||
| 1403 | Uint16 extension = 0; | ||
| 1404 | if (ParseExtensionIdentifyResponse(ctx, &extension)) { | ||
| 1405 | if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) { | ||
| 1406 | // Motion Plus is currently active | ||
| 1407 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Motion Plus CONNECTED (stage %d)", ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1 ? 1 : 2); | ||
| 1408 | |||
| 1409 | if (!ctx->m_bMotionPlusPresent) { | ||
| 1410 | // Reinitialize to get new sensor availability | ||
| 1411 | ctx->m_bDisconnected = true; | ||
| 1412 | } | ||
| 1413 | ctx->m_eCommState = k_eWiiCommunicationState_None; | ||
| 1414 | |||
| 1415 | } else if (ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1) { | ||
| 1416 | // Check to see if Motion Plus is present | ||
| 1417 | ReadRegister(ctx, 0xA600FE, 2, false); | ||
| 1418 | |||
| 1419 | ctx->m_eCommState = k_eWiiCommunicationState_CheckMotionPlusStage2; | ||
| 1420 | |||
| 1421 | } else { | ||
| 1422 | // Motion Plus is not present | ||
| 1423 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Motion Plus DISCONNECTED (stage %d)", ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1 ? 1 : 2); | ||
| 1424 | |||
| 1425 | if (ctx->m_bMotionPlusPresent) { | ||
| 1426 | // Reinitialize to get new sensor availability | ||
| 1427 | ctx->m_bDisconnected = true; | ||
| 1428 | } | ||
| 1429 | ctx->m_eCommState = k_eWiiCommunicationState_None; | ||
| 1430 | } | ||
| 1431 | } | ||
| 1432 | } break; | ||
| 1433 | default: | ||
| 1434 | // Should never happen | ||
| 1435 | break; | ||
| 1436 | } | ||
| 1437 | } | ||
| 1438 | |||
| 1439 | static void HandleButtonPacket(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick) | ||
| 1440 | { | ||
| 1441 | EWiiInputReportIDs eExpectedReport = GetButtonPacketType(ctx); | ||
| 1442 | WiiButtonData data; | ||
| 1443 | |||
| 1444 | // FIXME: This should see if the data format is compatible rather than equal | ||
| 1445 | if (eExpectedReport != ctx->m_rgucReadBuffer[0]) { | ||
| 1446 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Resetting report mode to %d", eExpectedReport); | ||
| 1447 | RequestButtonPacketType(ctx, eExpectedReport); | ||
| 1448 | } | ||
| 1449 | |||
| 1450 | // IR camera data is not supported | ||
| 1451 | SDL_zero(data); | ||
| 1452 | switch (ctx->m_rgucReadBuffer[0]) { | ||
| 1453 | case k_eWiiInputReportIDs_ButtonData0: // 30 BB BB | ||
| 1454 | GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); | ||
| 1455 | break; | ||
| 1456 | case k_eWiiInputReportIDs_ButtonData1: // 31 BB BB AA AA AA | ||
| 1457 | case k_eWiiInputReportIDs_ButtonData3: // 33 BB BB AA AA AA II II II II II II II II II II II II | ||
| 1458 | GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); | ||
| 1459 | GetAccelerometer(&data, ctx->m_rgucReadBuffer + 3); | ||
| 1460 | break; | ||
| 1461 | case k_eWiiInputReportIDs_ButtonData2: // 32 BB BB EE EE EE EE EE EE EE EE | ||
| 1462 | GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); | ||
| 1463 | GetExtensionData(&data, ctx->m_rgucReadBuffer + 3, 8); | ||
| 1464 | break; | ||
| 1465 | 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 | ||
| 1466 | GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); | ||
| 1467 | GetExtensionData(&data, ctx->m_rgucReadBuffer + 3, 19); | ||
| 1468 | break; | ||
| 1469 | 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 | ||
| 1470 | GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); | ||
| 1471 | GetAccelerometer(&data, ctx->m_rgucReadBuffer + 3); | ||
| 1472 | GetExtensionData(&data, ctx->m_rgucReadBuffer + 6, 16); | ||
| 1473 | break; | ||
| 1474 | 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 | ||
| 1475 | GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); | ||
| 1476 | GetExtensionData(&data, ctx->m_rgucReadBuffer + 13, 9); | ||
| 1477 | break; | ||
| 1478 | 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 | ||
| 1479 | GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1); | ||
| 1480 | GetExtensionData(&data, ctx->m_rgucReadBuffer + 16, 6); | ||
| 1481 | break; | ||
| 1482 | 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 | ||
| 1483 | GetExtensionData(&data, ctx->m_rgucReadBuffer + 1, 21); | ||
| 1484 | break; | ||
| 1485 | case k_eWiiInputReportIDs_ButtonDataE: | ||
| 1486 | case k_eWiiInputReportIDs_ButtonDataF: | ||
| 1487 | default: | ||
| 1488 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Unsupported button data type %02x", ctx->m_rgucReadBuffer[0]); | ||
| 1489 | return; | ||
| 1490 | } | ||
| 1491 | HandleButtonData(ctx, joystick, &data); | ||
| 1492 | } | ||
| 1493 | |||
| 1494 | static void HandleInput(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick) | ||
| 1495 | { | ||
| 1496 | EWiiInputReportIDs type = (EWiiInputReportIDs)ctx->m_rgucReadBuffer[0]; | ||
| 1497 | |||
| 1498 | // Set up for handling input | ||
| 1499 | ctx->timestamp = SDL_GetTicksNS(); | ||
| 1500 | |||
| 1501 | if (type == k_eWiiInputReportIDs_Status) { | ||
| 1502 | HandleStatus(ctx, joystick); | ||
| 1503 | } else if (type == k_eWiiInputReportIDs_Acknowledge || type == k_eWiiInputReportIDs_ReadMemory) { | ||
| 1504 | HandleResponse(ctx, joystick); | ||
| 1505 | } else if (type >= k_eWiiInputReportIDs_ButtonData0 && type <= k_eWiiInputReportIDs_ButtonDataF) { | ||
| 1506 | HandleButtonPacket(ctx, joystick); | ||
| 1507 | } else { | ||
| 1508 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Unexpected input packet of type %x", type); | ||
| 1509 | } | ||
| 1510 | } | ||
| 1511 | |||
| 1512 | static bool HIDAPI_DriverWii_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 1513 | { | ||
| 1514 | SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; | ||
| 1515 | SDL_Joystick *joystick = NULL; | ||
| 1516 | int size; | ||
| 1517 | Uint64 now; | ||
| 1518 | |||
| 1519 | if (device->num_joysticks > 0) { | ||
| 1520 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 1521 | } else { | ||
| 1522 | return false; | ||
| 1523 | } | ||
| 1524 | |||
| 1525 | now = SDL_GetTicks(); | ||
| 1526 | |||
| 1527 | while ((size = ReadInput(ctx)) > 0) { | ||
| 1528 | if (joystick) { | ||
| 1529 | HandleInput(ctx, joystick); | ||
| 1530 | } | ||
| 1531 | ctx->m_ulLastInput = now; | ||
| 1532 | } | ||
| 1533 | |||
| 1534 | /* Check to see if we've lost connection to the controller. | ||
| 1535 | * We have continuous reporting enabled, so this should be reliable now. | ||
| 1536 | */ | ||
| 1537 | { | ||
| 1538 | SDL_COMPILE_TIME_ASSERT(ENABLE_CONTINUOUS_REPORTING, ENABLE_CONTINUOUS_REPORTING); | ||
| 1539 | } | ||
| 1540 | if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) { | ||
| 1541 | // Bluetooth may have disconnected, try reopening the controller | ||
| 1542 | size = -1; | ||
| 1543 | } | ||
| 1544 | |||
| 1545 | if (joystick) { | ||
| 1546 | // These checks aren't needed on the Wii U Pro Controller | ||
| 1547 | if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_WiiUPro) { | ||
| 1548 | |||
| 1549 | // Check to see if the Motion Plus extension status has changed | ||
| 1550 | if (ctx->m_ulNextMotionPlusCheck && now >= ctx->m_ulNextMotionPlusCheck) { | ||
| 1551 | CheckMotionPlusConnection(ctx); | ||
| 1552 | if (NeedsPeriodicMotionPlusCheck(ctx, false)) { | ||
| 1553 | SchedulePeriodicMotionPlusCheck(ctx); | ||
| 1554 | } else { | ||
| 1555 | ctx->m_ulNextMotionPlusCheck = 0; | ||
| 1556 | } | ||
| 1557 | } | ||
| 1558 | |||
| 1559 | // Request a status update periodically to make sure our battery value is up to date | ||
| 1560 | if (!ctx->m_ulLastStatus || now >= (ctx->m_ulLastStatus + STATUS_UPDATE_TIME_MS)) { | ||
| 1561 | Uint8 data[2]; | ||
| 1562 | |||
| 1563 | data[0] = k_eWiiOutputReportIDs_StatusRequest; | ||
| 1564 | data[1] = (Uint8)ctx->m_bRumbleActive; | ||
| 1565 | WriteOutput(ctx, data, sizeof(data), false); | ||
| 1566 | |||
| 1567 | ctx->m_ulLastStatus = now; | ||
| 1568 | } | ||
| 1569 | } | ||
| 1570 | } | ||
| 1571 | |||
| 1572 | if (size < 0 || ctx->m_bDisconnected) { | ||
| 1573 | // Read error, device is disconnected | ||
| 1574 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 1575 | } | ||
| 1576 | return (size >= 0); | ||
| 1577 | } | ||
| 1578 | |||
| 1579 | static void HIDAPI_DriverWii_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1580 | { | ||
| 1581 | SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context; | ||
| 1582 | |||
| 1583 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED, | ||
| 1584 | SDL_PlayerLEDHintChanged, ctx); | ||
| 1585 | |||
| 1586 | ctx->joystick = NULL; | ||
| 1587 | } | ||
| 1588 | |||
| 1589 | static void HIDAPI_DriverWii_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 1590 | { | ||
| 1591 | } | ||
| 1592 | |||
| 1593 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii = { | ||
| 1594 | SDL_HINT_JOYSTICK_HIDAPI_WII, | ||
| 1595 | true, | ||
| 1596 | HIDAPI_DriverWii_RegisterHints, | ||
| 1597 | HIDAPI_DriverWii_UnregisterHints, | ||
| 1598 | HIDAPI_DriverWii_IsEnabled, | ||
| 1599 | HIDAPI_DriverWii_IsSupportedDevice, | ||
| 1600 | HIDAPI_DriverWii_InitDevice, | ||
| 1601 | HIDAPI_DriverWii_GetDevicePlayerIndex, | ||
| 1602 | HIDAPI_DriverWii_SetDevicePlayerIndex, | ||
| 1603 | HIDAPI_DriverWii_UpdateDevice, | ||
| 1604 | HIDAPI_DriverWii_OpenJoystick, | ||
| 1605 | HIDAPI_DriverWii_RumbleJoystick, | ||
| 1606 | HIDAPI_DriverWii_RumbleJoystickTriggers, | ||
| 1607 | HIDAPI_DriverWii_GetJoystickCapabilities, | ||
| 1608 | HIDAPI_DriverWii_SetJoystickLED, | ||
| 1609 | HIDAPI_DriverWii_SendJoystickEffect, | ||
| 1610 | HIDAPI_DriverWii_SetJoystickSensorsEnabled, | ||
| 1611 | HIDAPI_DriverWii_CloseJoystick, | ||
| 1612 | HIDAPI_DriverWii_FreeDevice, | ||
| 1613 | }; | ||
| 1614 | |||
| 1615 | #endif // SDL_JOYSTICK_HIDAPI_WII | ||
| 1616 | |||
| 1617 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../../SDL_hints_c.h" | ||
| 26 | #include "../SDL_sysjoystick.h" | ||
| 27 | #include "SDL_hidapijoystick_c.h" | ||
| 28 | #include "SDL_hidapi_rumble.h" | ||
| 29 | |||
| 30 | #ifdef SDL_JOYSTICK_HIDAPI_XBOX360 | ||
| 31 | |||
| 32 | // Define this if you want to log all packets from the controller | ||
| 33 | // #define DEBUG_XBOX_PROTOCOL | ||
| 34 | |||
| 35 | typedef struct | ||
| 36 | { | ||
| 37 | SDL_HIDAPI_Device *device; | ||
| 38 | SDL_Joystick *joystick; | ||
| 39 | int player_index; | ||
| 40 | bool player_lights; | ||
| 41 | Uint8 last_state[USB_PACKET_LENGTH]; | ||
| 42 | } SDL_DriverXbox360_Context; | ||
| 43 | |||
| 44 | static void HIDAPI_DriverXbox360_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 45 | { | ||
| 46 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata); | ||
| 47 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata); | ||
| 48 | } | ||
| 49 | |||
| 50 | static void HIDAPI_DriverXbox360_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 51 | { | ||
| 52 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata); | ||
| 53 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata); | ||
| 54 | } | ||
| 55 | |||
| 56 | static bool HIDAPI_DriverXbox360_IsEnabled(void) | ||
| 57 | { | ||
| 58 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, | ||
| 59 | SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT))); | ||
| 60 | } | ||
| 61 | |||
| 62 | 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) | ||
| 63 | { | ||
| 64 | const int XB360W_IFACE_PROTOCOL = 129; // Wireless | ||
| 65 | |||
| 66 | if (vendor_id == USB_VENDOR_ASTRO && product_id == USB_PRODUCT_ASTRO_C40_XBOX360) { | ||
| 67 | // This is the ASTRO C40 in Xbox 360 mode | ||
| 68 | return true; | ||
| 69 | } | ||
| 70 | if (vendor_id == USB_VENDOR_NVIDIA) { | ||
| 71 | // This is the NVIDIA Shield controller which doesn't talk Xbox controller protocol | ||
| 72 | return false; | ||
| 73 | } | ||
| 74 | if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER)) || | ||
| 75 | (type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) { | ||
| 76 | // This is the wireless dongle, which talks a different protocol | ||
| 77 | return false; | ||
| 78 | } | ||
| 79 | if (interface_number > 0) { | ||
| 80 | // This is the chatpad or other input interface, not the Xbox 360 interface | ||
| 81 | return false; | ||
| 82 | } | ||
| 83 | #if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI) | ||
| 84 | if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) { | ||
| 85 | // GCController support doesn't work with the Steam Virtual Gamepad | ||
| 86 | return true; | ||
| 87 | } else { | ||
| 88 | // On macOS you can't write output reports to wired XBox controllers, | ||
| 89 | // so we'll just use the GCController support instead. | ||
| 90 | return false; | ||
| 91 | } | ||
| 92 | #else | ||
| 93 | return (type == SDL_GAMEPAD_TYPE_XBOX360); | ||
| 94 | #endif | ||
| 95 | } | ||
| 96 | |||
| 97 | static bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, bool on) | ||
| 98 | { | ||
| 99 | const bool blink = false; | ||
| 100 | Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0; | ||
| 101 | Uint8 led_packet[] = { 0x01, 0x03, 0x00 }; | ||
| 102 | |||
| 103 | led_packet[2] = mode; | ||
| 104 | if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) { | ||
| 105 | return false; | ||
| 106 | } | ||
| 107 | return true; | ||
| 108 | } | ||
| 109 | |||
| 110 | static void UpdateSlotLED(SDL_DriverXbox360_Context *ctx) | ||
| 111 | { | ||
| 112 | if (ctx->player_lights && ctx->player_index >= 0) { | ||
| 113 | SetSlotLED(ctx->device->dev, (ctx->player_index % 4), true); | ||
| 114 | } else { | ||
| 115 | SetSlotLED(ctx->device->dev, 0, false); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 120 | { | ||
| 121 | SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)userdata; | ||
| 122 | bool player_lights = SDL_GetStringBoolean(hint, true); | ||
| 123 | |||
| 124 | if (player_lights != ctx->player_lights) { | ||
| 125 | ctx->player_lights = player_lights; | ||
| 126 | |||
| 127 | UpdateSlotLED(ctx); | ||
| 128 | HIDAPI_UpdateDeviceProperties(ctx->device); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | static bool HIDAPI_DriverXbox360_InitDevice(SDL_HIDAPI_Device *device) | ||
| 133 | { | ||
| 134 | SDL_DriverXbox360_Context *ctx; | ||
| 135 | |||
| 136 | ctx = (SDL_DriverXbox360_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 137 | if (!ctx) { | ||
| 138 | return false; | ||
| 139 | } | ||
| 140 | ctx->device = device; | ||
| 141 | |||
| 142 | device->context = ctx; | ||
| 143 | |||
| 144 | device->type = SDL_GAMEPAD_TYPE_XBOX360; | ||
| 145 | |||
| 146 | if (SDL_IsJoystickSteamVirtualGamepad(device->vendor_id, device->product_id, device->version) && | ||
| 147 | device->product_string && SDL_strncmp(device->product_string, "GamePad-", 8) == 0) { | ||
| 148 | int slot = 0; | ||
| 149 | SDL_sscanf(device->product_string, "GamePad-%d", &slot); | ||
| 150 | device->steam_virtual_gamepad_slot = (slot - 1); | ||
| 151 | } | ||
| 152 | |||
| 153 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 154 | } | ||
| 155 | |||
| 156 | static int HIDAPI_DriverXbox360_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 157 | { | ||
| 158 | return -1; | ||
| 159 | } | ||
| 160 | |||
| 161 | static void HIDAPI_DriverXbox360_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 162 | { | ||
| 163 | SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; | ||
| 164 | |||
| 165 | if (!ctx->joystick) { | ||
| 166 | return; | ||
| 167 | } | ||
| 168 | |||
| 169 | ctx->player_index = player_index; | ||
| 170 | |||
| 171 | UpdateSlotLED(ctx); | ||
| 172 | } | ||
| 173 | |||
| 174 | static bool HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 175 | { | ||
| 176 | SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; | ||
| 177 | |||
| 178 | SDL_AssertJoysticksLocked(); | ||
| 179 | |||
| 180 | ctx->joystick = joystick; | ||
| 181 | SDL_zeroa(ctx->last_state); | ||
| 182 | |||
| 183 | // Initialize player index (needed for setting LEDs) | ||
| 184 | ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); | ||
| 185 | ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, true); | ||
| 186 | UpdateSlotLED(ctx); | ||
| 187 | |||
| 188 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, | ||
| 189 | SDL_PlayerLEDHintChanged, ctx); | ||
| 190 | |||
| 191 | // Initialize the joystick capabilities | ||
| 192 | joystick->nbuttons = 11; | ||
| 193 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 194 | joystick->nhats = 1; | ||
| 195 | |||
| 196 | return true; | ||
| 197 | } | ||
| 198 | |||
| 199 | static bool HIDAPI_DriverXbox360_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 200 | { | ||
| 201 | Uint8 rumble_packet[] = { 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; | ||
| 202 | |||
| 203 | rumble_packet[3] = (low_frequency_rumble >> 8); | ||
| 204 | rumble_packet[4] = (high_frequency_rumble >> 8); | ||
| 205 | |||
| 206 | if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { | ||
| 207 | return SDL_SetError("Couldn't send rumble packet"); | ||
| 208 | } | ||
| 209 | return true; | ||
| 210 | } | ||
| 211 | |||
| 212 | static bool HIDAPI_DriverXbox360_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 213 | { | ||
| 214 | return SDL_Unsupported(); | ||
| 215 | } | ||
| 216 | |||
| 217 | static Uint32 HIDAPI_DriverXbox360_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 218 | { | ||
| 219 | SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; | ||
| 220 | Uint32 result = SDL_JOYSTICK_CAP_RUMBLE; | ||
| 221 | |||
| 222 | if (ctx->player_lights) { | ||
| 223 | result |= SDL_JOYSTICK_CAP_PLAYER_LED; | ||
| 224 | } | ||
| 225 | return result; | ||
| 226 | } | ||
| 227 | |||
| 228 | static bool HIDAPI_DriverXbox360_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 229 | { | ||
| 230 | return SDL_Unsupported(); | ||
| 231 | } | ||
| 232 | |||
| 233 | static bool HIDAPI_DriverXbox360_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 234 | { | ||
| 235 | return SDL_Unsupported(); | ||
| 236 | } | ||
| 237 | |||
| 238 | static bool HIDAPI_DriverXbox360_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 239 | { | ||
| 240 | return SDL_Unsupported(); | ||
| 241 | } | ||
| 242 | |||
| 243 | static void HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size) | ||
| 244 | { | ||
| 245 | Sint16 axis; | ||
| 246 | #ifdef SDL_PLATFORM_MACOS | ||
| 247 | const bool invert_y_axes = false; | ||
| 248 | #else | ||
| 249 | const bool invert_y_axes = true; | ||
| 250 | #endif | ||
| 251 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 252 | |||
| 253 | if (ctx->last_state[2] != data[2]) { | ||
| 254 | Uint8 hat = 0; | ||
| 255 | |||
| 256 | if (data[2] & 0x01) { | ||
| 257 | hat |= SDL_HAT_UP; | ||
| 258 | } | ||
| 259 | if (data[2] & 0x02) { | ||
| 260 | hat |= SDL_HAT_DOWN; | ||
| 261 | } | ||
| 262 | if (data[2] & 0x04) { | ||
| 263 | hat |= SDL_HAT_LEFT; | ||
| 264 | } | ||
| 265 | if (data[2] & 0x08) { | ||
| 266 | hat |= SDL_HAT_RIGHT; | ||
| 267 | } | ||
| 268 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 269 | |||
| 270 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x10) != 0)); | ||
| 271 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x20) != 0)); | ||
| 272 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x40) != 0)); | ||
| 273 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0)); | ||
| 274 | } | ||
| 275 | |||
| 276 | if (ctx->last_state[3] != data[3]) { | ||
| 277 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x01) != 0)); | ||
| 278 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0)); | ||
| 279 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[3] & 0x04) != 0)); | ||
| 280 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x10) != 0)); | ||
| 281 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0)); | ||
| 282 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x40) != 0)); | ||
| 283 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x80) != 0)); | ||
| 284 | } | ||
| 285 | |||
| 286 | axis = ((int)data[4] * 257) - 32768; | ||
| 287 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 288 | axis = ((int)data[5] * 257) - 32768; | ||
| 289 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 290 | axis = SDL_Swap16LE(*(Sint16 *)(&data[6])); | ||
| 291 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 292 | axis = SDL_Swap16LE(*(Sint16 *)(&data[8])); | ||
| 293 | if (invert_y_axes) { | ||
| 294 | axis = ~axis; | ||
| 295 | } | ||
| 296 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 297 | axis = SDL_Swap16LE(*(Sint16 *)(&data[10])); | ||
| 298 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 299 | axis = SDL_Swap16LE(*(Sint16 *)(&data[12])); | ||
| 300 | if (invert_y_axes) { | ||
| 301 | axis = ~axis; | ||
| 302 | } | ||
| 303 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 304 | |||
| 305 | SDL_memcpy(ctx->last_state, data, SDL_min((size_t)size, sizeof(ctx->last_state))); | ||
| 306 | } | ||
| 307 | |||
| 308 | static bool HIDAPI_DriverXbox360_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 309 | { | ||
| 310 | SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; | ||
| 311 | SDL_Joystick *joystick = NULL; | ||
| 312 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 313 | int size = 0; | ||
| 314 | |||
| 315 | if (device->num_joysticks > 0) { | ||
| 316 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 317 | } else { | ||
| 318 | return false; | ||
| 319 | } | ||
| 320 | |||
| 321 | while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { | ||
| 322 | #ifdef DEBUG_XBOX_PROTOCOL | ||
| 323 | HIDAPI_DumpPacket("Xbox 360 packet: size = %d", data, size); | ||
| 324 | #endif | ||
| 325 | if (!joystick) { | ||
| 326 | continue; | ||
| 327 | } | ||
| 328 | |||
| 329 | if (data[0] == 0x00) { | ||
| 330 | HIDAPI_DriverXbox360_HandleStatePacket(joystick, ctx, data, size); | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | if (size < 0) { | ||
| 335 | // Read error, device is disconnected | ||
| 336 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 337 | } | ||
| 338 | return (size >= 0); | ||
| 339 | } | ||
| 340 | |||
| 341 | static void HIDAPI_DriverXbox360_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 342 | { | ||
| 343 | SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; | ||
| 344 | |||
| 345 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, | ||
| 346 | SDL_PlayerLEDHintChanged, ctx); | ||
| 347 | |||
| 348 | ctx->joystick = NULL; | ||
| 349 | } | ||
| 350 | |||
| 351 | static void HIDAPI_DriverXbox360_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 352 | { | ||
| 353 | } | ||
| 354 | |||
| 355 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 = { | ||
| 356 | SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, | ||
| 357 | true, | ||
| 358 | HIDAPI_DriverXbox360_RegisterHints, | ||
| 359 | HIDAPI_DriverXbox360_UnregisterHints, | ||
| 360 | HIDAPI_DriverXbox360_IsEnabled, | ||
| 361 | HIDAPI_DriverXbox360_IsSupportedDevice, | ||
| 362 | HIDAPI_DriverXbox360_InitDevice, | ||
| 363 | HIDAPI_DriverXbox360_GetDevicePlayerIndex, | ||
| 364 | HIDAPI_DriverXbox360_SetDevicePlayerIndex, | ||
| 365 | HIDAPI_DriverXbox360_UpdateDevice, | ||
| 366 | HIDAPI_DriverXbox360_OpenJoystick, | ||
| 367 | HIDAPI_DriverXbox360_RumbleJoystick, | ||
| 368 | HIDAPI_DriverXbox360_RumbleJoystickTriggers, | ||
| 369 | HIDAPI_DriverXbox360_GetJoystickCapabilities, | ||
| 370 | HIDAPI_DriverXbox360_SetJoystickLED, | ||
| 371 | HIDAPI_DriverXbox360_SendJoystickEffect, | ||
| 372 | HIDAPI_DriverXbox360_SetJoystickSensorsEnabled, | ||
| 373 | HIDAPI_DriverXbox360_CloseJoystick, | ||
| 374 | HIDAPI_DriverXbox360_FreeDevice, | ||
| 375 | }; | ||
| 376 | |||
| 377 | #endif // SDL_JOYSTICK_HIDAPI_XBOX360 | ||
| 378 | |||
| 379 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../../SDL_hints_c.h" | ||
| 26 | #include "../SDL_sysjoystick.h" | ||
| 27 | #include "SDL_hidapijoystick_c.h" | ||
| 28 | #include "SDL_hidapi_rumble.h" | ||
| 29 | |||
| 30 | #ifdef SDL_JOYSTICK_HIDAPI_XBOX360 | ||
| 31 | |||
| 32 | // Define this if you want to log all packets from the controller | ||
| 33 | // #define DEBUG_XBOX_PROTOCOL | ||
| 34 | |||
| 35 | typedef struct | ||
| 36 | { | ||
| 37 | SDL_HIDAPI_Device *device; | ||
| 38 | bool connected; | ||
| 39 | int player_index; | ||
| 40 | bool player_lights; | ||
| 41 | Uint8 last_state[USB_PACKET_LENGTH]; | ||
| 42 | } SDL_DriverXbox360W_Context; | ||
| 43 | |||
| 44 | static void HIDAPI_DriverXbox360W_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 45 | { | ||
| 46 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata); | ||
| 47 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata); | ||
| 48 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, callback, userdata); | ||
| 49 | } | ||
| 50 | |||
| 51 | static void HIDAPI_DriverXbox360W_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 52 | { | ||
| 53 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata); | ||
| 54 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata); | ||
| 55 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, callback, userdata); | ||
| 56 | } | ||
| 57 | |||
| 58 | static bool HIDAPI_DriverXbox360W_IsEnabled(void) | ||
| 59 | { | ||
| 60 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, | ||
| 61 | SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)))); | ||
| 62 | } | ||
| 63 | |||
| 64 | 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) | ||
| 65 | { | ||
| 66 | const int XB360W_IFACE_PROTOCOL = 129; // Wireless | ||
| 67 | |||
| 68 | 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) || | ||
| 69 | (type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) { | ||
| 70 | return true; | ||
| 71 | } | ||
| 72 | return false; | ||
| 73 | } | ||
| 74 | |||
| 75 | static bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, bool on) | ||
| 76 | { | ||
| 77 | const bool blink = false; | ||
| 78 | Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0; | ||
| 79 | Uint8 led_packet[] = { 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; | ||
| 80 | |||
| 81 | led_packet[3] = 0x40 + (mode % 0x0e); | ||
| 82 | if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) { | ||
| 83 | return false; | ||
| 84 | } | ||
| 85 | return true; | ||
| 86 | } | ||
| 87 | |||
| 88 | static void UpdateSlotLED(SDL_DriverXbox360W_Context *ctx) | ||
| 89 | { | ||
| 90 | if (ctx->player_lights && ctx->player_index >= 0) { | ||
| 91 | SetSlotLED(ctx->device->dev, (ctx->player_index % 4), true); | ||
| 92 | } else { | ||
| 93 | SetSlotLED(ctx->device->dev, 0, false); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 98 | { | ||
| 99 | SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)userdata; | ||
| 100 | bool player_lights = SDL_GetStringBoolean(hint, true); | ||
| 101 | |||
| 102 | if (player_lights != ctx->player_lights) { | ||
| 103 | ctx->player_lights = player_lights; | ||
| 104 | |||
| 105 | UpdateSlotLED(ctx); | ||
| 106 | HIDAPI_UpdateDeviceProperties(ctx->device); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | static void UpdatePowerLevel(SDL_Joystick *joystick, Uint8 level) | ||
| 111 | { | ||
| 112 | int percent = (int)SDL_roundf((level / 255.0f) * 100.0f); | ||
| 113 | SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent); | ||
| 114 | } | ||
| 115 | |||
| 116 | static bool HIDAPI_DriverXbox360W_InitDevice(SDL_HIDAPI_Device *device) | ||
| 117 | { | ||
| 118 | SDL_DriverXbox360W_Context *ctx; | ||
| 119 | |||
| 120 | // Requests controller presence information from the wireless dongle | ||
| 121 | const Uint8 init_packet[] = { 0x08, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; | ||
| 122 | |||
| 123 | HIDAPI_SetDeviceName(device, "Xbox 360 Wireless Controller"); | ||
| 124 | |||
| 125 | ctx = (SDL_DriverXbox360W_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 126 | if (!ctx) { | ||
| 127 | return false; | ||
| 128 | } | ||
| 129 | ctx->device = device; | ||
| 130 | |||
| 131 | device->context = ctx; | ||
| 132 | |||
| 133 | if (SDL_hid_write(device->dev, init_packet, sizeof(init_packet)) != sizeof(init_packet)) { | ||
| 134 | SDL_SetError("Couldn't write init packet"); | ||
| 135 | return false; | ||
| 136 | } | ||
| 137 | |||
| 138 | device->type = SDL_GAMEPAD_TYPE_XBOX360; | ||
| 139 | |||
| 140 | return true; | ||
| 141 | } | ||
| 142 | |||
| 143 | static int HIDAPI_DriverXbox360W_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 144 | { | ||
| 145 | return -1; | ||
| 146 | } | ||
| 147 | |||
| 148 | static void HIDAPI_DriverXbox360W_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 149 | { | ||
| 150 | SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context; | ||
| 151 | |||
| 152 | if (!ctx) { | ||
| 153 | return; | ||
| 154 | } | ||
| 155 | |||
| 156 | ctx->player_index = player_index; | ||
| 157 | |||
| 158 | UpdateSlotLED(ctx); | ||
| 159 | } | ||
| 160 | |||
| 161 | static bool HIDAPI_DriverXbox360W_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 162 | { | ||
| 163 | SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context; | ||
| 164 | |||
| 165 | SDL_AssertJoysticksLocked(); | ||
| 166 | |||
| 167 | SDL_zeroa(ctx->last_state); | ||
| 168 | |||
| 169 | // Initialize player index (needed for setting LEDs) | ||
| 170 | ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); | ||
| 171 | ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, true); | ||
| 172 | UpdateSlotLED(ctx); | ||
| 173 | |||
| 174 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, | ||
| 175 | SDL_PlayerLEDHintChanged, ctx); | ||
| 176 | |||
| 177 | // Initialize the joystick capabilities | ||
| 178 | joystick->nbuttons = 11; | ||
| 179 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 180 | joystick->nhats = 1; | ||
| 181 | joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; | ||
| 182 | |||
| 183 | return true; | ||
| 184 | } | ||
| 185 | |||
| 186 | static bool HIDAPI_DriverXbox360W_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 187 | { | ||
| 188 | Uint8 rumble_packet[] = { 0x00, 0x01, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; | ||
| 189 | |||
| 190 | rumble_packet[5] = (low_frequency_rumble >> 8); | ||
| 191 | rumble_packet[6] = (high_frequency_rumble >> 8); | ||
| 192 | |||
| 193 | if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { | ||
| 194 | return SDL_SetError("Couldn't send rumble packet"); | ||
| 195 | } | ||
| 196 | return true; | ||
| 197 | } | ||
| 198 | |||
| 199 | static bool HIDAPI_DriverXbox360W_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 200 | { | ||
| 201 | return SDL_Unsupported(); | ||
| 202 | } | ||
| 203 | |||
| 204 | static Uint32 HIDAPI_DriverXbox360W_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 205 | { | ||
| 206 | SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context; | ||
| 207 | Uint32 result = SDL_JOYSTICK_CAP_RUMBLE; | ||
| 208 | |||
| 209 | if (ctx->player_lights) { | ||
| 210 | result |= SDL_JOYSTICK_CAP_PLAYER_LED; | ||
| 211 | } | ||
| 212 | return result; | ||
| 213 | } | ||
| 214 | |||
| 215 | static bool HIDAPI_DriverXbox360W_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 216 | { | ||
| 217 | return SDL_Unsupported(); | ||
| 218 | } | ||
| 219 | |||
| 220 | static bool HIDAPI_DriverXbox360W_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 221 | { | ||
| 222 | return SDL_Unsupported(); | ||
| 223 | } | ||
| 224 | |||
| 225 | static bool HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 226 | { | ||
| 227 | return SDL_Unsupported(); | ||
| 228 | } | ||
| 229 | |||
| 230 | static void HIDAPI_DriverXbox360W_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverXbox360W_Context *ctx, Uint8 *data, int size) | ||
| 231 | { | ||
| 232 | Sint16 axis; | ||
| 233 | const bool invert_y_axes = true; | ||
| 234 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 235 | |||
| 236 | if (ctx->last_state[2] != data[2]) { | ||
| 237 | Uint8 hat = 0; | ||
| 238 | |||
| 239 | if (data[2] & 0x01) { | ||
| 240 | hat |= SDL_HAT_UP; | ||
| 241 | } | ||
| 242 | if (data[2] & 0x02) { | ||
| 243 | hat |= SDL_HAT_DOWN; | ||
| 244 | } | ||
| 245 | if (data[2] & 0x04) { | ||
| 246 | hat |= SDL_HAT_LEFT; | ||
| 247 | } | ||
| 248 | if (data[2] & 0x08) { | ||
| 249 | hat |= SDL_HAT_RIGHT; | ||
| 250 | } | ||
| 251 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 252 | |||
| 253 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x10) != 0)); | ||
| 254 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x20) != 0)); | ||
| 255 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x40) != 0)); | ||
| 256 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0)); | ||
| 257 | } | ||
| 258 | |||
| 259 | if (ctx->last_state[3] != data[3]) { | ||
| 260 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x01) != 0)); | ||
| 261 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0)); | ||
| 262 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[3] & 0x04) != 0)); | ||
| 263 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x10) != 0)); | ||
| 264 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0)); | ||
| 265 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x40) != 0)); | ||
| 266 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x80) != 0)); | ||
| 267 | } | ||
| 268 | |||
| 269 | axis = ((int)data[4] * 257) - 32768; | ||
| 270 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 271 | axis = ((int)data[5] * 257) - 32768; | ||
| 272 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 273 | axis = SDL_Swap16LE(*(Sint16 *)(&data[6])); | ||
| 274 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 275 | axis = SDL_Swap16LE(*(Sint16 *)(&data[8])); | ||
| 276 | if (invert_y_axes) { | ||
| 277 | axis = ~axis; | ||
| 278 | } | ||
| 279 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 280 | axis = SDL_Swap16LE(*(Sint16 *)(&data[10])); | ||
| 281 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 282 | axis = SDL_Swap16LE(*(Sint16 *)(&data[12])); | ||
| 283 | if (invert_y_axes) { | ||
| 284 | axis = ~axis; | ||
| 285 | } | ||
| 286 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 287 | |||
| 288 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 289 | } | ||
| 290 | |||
| 291 | static bool HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 292 | { | ||
| 293 | SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context; | ||
| 294 | SDL_Joystick *joystick = NULL; | ||
| 295 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 296 | int size; | ||
| 297 | |||
| 298 | if (device->num_joysticks > 0) { | ||
| 299 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 300 | } | ||
| 301 | |||
| 302 | while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { | ||
| 303 | #ifdef DEBUG_XBOX_PROTOCOL | ||
| 304 | HIDAPI_DumpPacket("Xbox 360 wireless packet: size = %d", data, size); | ||
| 305 | #endif | ||
| 306 | if (size == 2 && data[0] == 0x08) { | ||
| 307 | bool connected = (data[1] & 0x80) ? true : false; | ||
| 308 | #ifdef DEBUG_JOYSTICK | ||
| 309 | SDL_Log("Connected = %s", connected ? "TRUE" : "FALSE"); | ||
| 310 | #endif | ||
| 311 | if (connected != ctx->connected) { | ||
| 312 | ctx->connected = connected; | ||
| 313 | |||
| 314 | if (connected) { | ||
| 315 | SDL_JoystickID joystickID; | ||
| 316 | |||
| 317 | HIDAPI_JoystickConnected(device, &joystickID); | ||
| 318 | |||
| 319 | } else if (device->num_joysticks > 0) { | ||
| 320 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 321 | } | ||
| 322 | } | ||
| 323 | } else if (size == 29 && data[0] == 0x00 && data[1] == 0x0f && data[2] == 0x00 && data[3] == 0xf0) { | ||
| 324 | // Serial number is data[7-13] | ||
| 325 | #ifdef DEBUG_JOYSTICK | ||
| 326 | SDL_Log("Battery status (initial): %d", data[17]); | ||
| 327 | #endif | ||
| 328 | if (joystick) { | ||
| 329 | UpdatePowerLevel(joystick, data[17]); | ||
| 330 | } | ||
| 331 | } else if (size == 29 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x13) { | ||
| 332 | #ifdef DEBUG_JOYSTICK | ||
| 333 | SDL_Log("Battery status: %d", data[4]); | ||
| 334 | #endif | ||
| 335 | if (joystick) { | ||
| 336 | UpdatePowerLevel(joystick, data[4]); | ||
| 337 | } | ||
| 338 | } else if (size == 29 && data[0] == 0x00 && (data[1] & 0x01) == 0x01) { | ||
| 339 | if (joystick) { | ||
| 340 | HIDAPI_DriverXbox360W_HandleStatePacket(joystick, device->dev, ctx, data + 4, size - 4); | ||
| 341 | } | ||
| 342 | } | ||
| 343 | } | ||
| 344 | |||
| 345 | if (size < 0 && device->num_joysticks > 0) { | ||
| 346 | // Read error, device is disconnected | ||
| 347 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 348 | } | ||
| 349 | return (size >= 0); | ||
| 350 | } | ||
| 351 | |||
| 352 | static void HIDAPI_DriverXbox360W_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 353 | { | ||
| 354 | SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context; | ||
| 355 | |||
| 356 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, | ||
| 357 | SDL_PlayerLEDHintChanged, ctx); | ||
| 358 | } | ||
| 359 | |||
| 360 | static void HIDAPI_DriverXbox360W_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 361 | { | ||
| 362 | } | ||
| 363 | |||
| 364 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W = { | ||
| 365 | SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, | ||
| 366 | true, | ||
| 367 | HIDAPI_DriverXbox360W_RegisterHints, | ||
| 368 | HIDAPI_DriverXbox360W_UnregisterHints, | ||
| 369 | HIDAPI_DriverXbox360W_IsEnabled, | ||
| 370 | HIDAPI_DriverXbox360W_IsSupportedDevice, | ||
| 371 | HIDAPI_DriverXbox360W_InitDevice, | ||
| 372 | HIDAPI_DriverXbox360W_GetDevicePlayerIndex, | ||
| 373 | HIDAPI_DriverXbox360W_SetDevicePlayerIndex, | ||
| 374 | HIDAPI_DriverXbox360W_UpdateDevice, | ||
| 375 | HIDAPI_DriverXbox360W_OpenJoystick, | ||
| 376 | HIDAPI_DriverXbox360W_RumbleJoystick, | ||
| 377 | HIDAPI_DriverXbox360W_RumbleJoystickTriggers, | ||
| 378 | HIDAPI_DriverXbox360W_GetJoystickCapabilities, | ||
| 379 | HIDAPI_DriverXbox360W_SetJoystickLED, | ||
| 380 | HIDAPI_DriverXbox360W_SendJoystickEffect, | ||
| 381 | HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled, | ||
| 382 | HIDAPI_DriverXbox360W_CloseJoystick, | ||
| 383 | HIDAPI_DriverXbox360W_FreeDevice, | ||
| 384 | }; | ||
| 385 | |||
| 386 | #endif // SDL_JOYSTICK_HIDAPI_XBOX360 | ||
| 387 | |||
| 388 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../../SDL_hints_c.h" | ||
| 26 | #include "../SDL_sysjoystick.h" | ||
| 27 | #include "SDL_hidapijoystick_c.h" | ||
| 28 | #include "SDL_hidapi_rumble.h" | ||
| 29 | |||
| 30 | #ifdef SDL_JOYSTICK_HIDAPI_XBOXONE | ||
| 31 | |||
| 32 | // Define this if you want verbose logging of the init sequence | ||
| 33 | // #define DEBUG_JOYSTICK | ||
| 34 | |||
| 35 | // Define this if you want to log all packets from the controller | ||
| 36 | // #define DEBUG_XBOX_PROTOCOL | ||
| 37 | |||
| 38 | #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) | ||
| 39 | #define XBOX_ONE_DRIVER_ACTIVE 1 | ||
| 40 | #else | ||
| 41 | #define XBOX_ONE_DRIVER_ACTIVE 0 | ||
| 42 | #endif | ||
| 43 | |||
| 44 | #define CONTROLLER_IDENTIFY_TIMEOUT_MS 100 | ||
| 45 | #define CONTROLLER_PREPARE_INPUT_TIMEOUT_MS 50 | ||
| 46 | |||
| 47 | // Deadzone thresholds | ||
| 48 | #define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849 | ||
| 49 | #define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689 | ||
| 50 | #define XINPUT_GAMEPAD_TRIGGER_THRESHOLD -25058 // Uint8 30 scaled to Sint16 full range | ||
| 51 | |||
| 52 | enum | ||
| 53 | { | ||
| 54 | SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON = 11 | ||
| 55 | }; | ||
| 56 | |||
| 57 | // Power on | ||
| 58 | static const Uint8 xbox_init_power_on[] = { | ||
| 59 | 0x05, 0x20, 0x00, 0x01, 0x00 | ||
| 60 | }; | ||
| 61 | // Enable LED | ||
| 62 | static const Uint8 xbox_init_enable_led[] = { | ||
| 63 | 0x0A, 0x20, 0x00, 0x03, 0x00, 0x01, 0x14 | ||
| 64 | }; | ||
| 65 | // This controller passed security check | ||
| 66 | static const Uint8 xbox_init_security_passed[] = { | ||
| 67 | 0x06, 0x20, 0x00, 0x02, 0x01, 0x00 | ||
| 68 | }; | ||
| 69 | // Some PowerA controllers need to actually start the rumble motors | ||
| 70 | static const Uint8 xbox_init_powera_rumble[] = { | ||
| 71 | 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, | ||
| 72 | 0x1D, 0x1D, 0xFF, 0x00, 0x00 | ||
| 73 | }; | ||
| 74 | // Setup rumble (not needed for Microsoft controllers, but it doesn't hurt) | ||
| 75 | static const Uint8 xbox_init_rumble[] = { | ||
| 76 | 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, | ||
| 77 | 0x00, 0x00, 0xFF, 0x00, 0xEB | ||
| 78 | }; | ||
| 79 | |||
| 80 | /* | ||
| 81 | * This specifies the selection of init packets that a gamepad | ||
| 82 | * will be sent on init *and* the order in which they will be | ||
| 83 | * sent. The correct sequence number will be added when the | ||
| 84 | * packet is going to be sent. | ||
| 85 | */ | ||
| 86 | typedef struct | ||
| 87 | { | ||
| 88 | Uint16 vendor_id; | ||
| 89 | Uint16 product_id; | ||
| 90 | const Uint8 *data; | ||
| 91 | int size; | ||
| 92 | } SDL_DriverXboxOne_InitPacket; | ||
| 93 | |||
| 94 | static const SDL_DriverXboxOne_InitPacket xboxone_init_packets[] = { | ||
| 95 | { 0x0000, 0x0000, xbox_init_power_on, sizeof(xbox_init_power_on) }, | ||
| 96 | { 0x0000, 0x0000, xbox_init_enable_led, sizeof(xbox_init_enable_led) }, | ||
| 97 | { 0x0000, 0x0000, xbox_init_security_passed, sizeof(xbox_init_security_passed) }, | ||
| 98 | { 0x24c6, 0x541a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) }, | ||
| 99 | { 0x24c6, 0x542a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) }, | ||
| 100 | { 0x24c6, 0x543a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) }, | ||
| 101 | { 0x0000, 0x0000, xbox_init_rumble, sizeof(xbox_init_rumble) }, | ||
| 102 | }; | ||
| 103 | |||
| 104 | typedef enum | ||
| 105 | { | ||
| 106 | XBOX_ONE_INIT_STATE_ANNOUNCED, | ||
| 107 | XBOX_ONE_INIT_STATE_IDENTIFYING, | ||
| 108 | XBOX_ONE_INIT_STATE_STARTUP, | ||
| 109 | XBOX_ONE_INIT_STATE_PREPARE_INPUT, | ||
| 110 | XBOX_ONE_INIT_STATE_COMPLETE, | ||
| 111 | } SDL_XboxOneInitState; | ||
| 112 | |||
| 113 | typedef enum | ||
| 114 | { | ||
| 115 | XBOX_ONE_RUMBLE_STATE_IDLE, | ||
| 116 | XBOX_ONE_RUMBLE_STATE_QUEUED, | ||
| 117 | XBOX_ONE_RUMBLE_STATE_BUSY | ||
| 118 | } SDL_XboxOneRumbleState; | ||
| 119 | |||
| 120 | typedef struct | ||
| 121 | { | ||
| 122 | SDL_HIDAPI_Device *device; | ||
| 123 | Uint16 vendor_id; | ||
| 124 | Uint16 product_id; | ||
| 125 | SDL_XboxOneInitState init_state; | ||
| 126 | Uint64 start_time; | ||
| 127 | Uint8 sequence; | ||
| 128 | Uint64 send_time; | ||
| 129 | bool has_guide_packet; | ||
| 130 | bool has_color_led; | ||
| 131 | bool has_paddles; | ||
| 132 | bool has_unmapped_state; | ||
| 133 | bool has_trigger_rumble; | ||
| 134 | bool has_share_button; | ||
| 135 | Uint8 last_paddle_state; | ||
| 136 | Uint8 low_frequency_rumble; | ||
| 137 | Uint8 high_frequency_rumble; | ||
| 138 | Uint8 left_trigger_rumble; | ||
| 139 | Uint8 right_trigger_rumble; | ||
| 140 | SDL_XboxOneRumbleState rumble_state; | ||
| 141 | Uint64 rumble_time; | ||
| 142 | bool rumble_pending; | ||
| 143 | Uint8 last_state[USB_PACKET_LENGTH]; | ||
| 144 | Uint8 *chunk_buffer; | ||
| 145 | Uint32 chunk_length; | ||
| 146 | } SDL_DriverXboxOne_Context; | ||
| 147 | |||
| 148 | static bool ControllerHasColorLED(Uint16 vendor_id, Uint16 product_id) | ||
| 149 | { | ||
| 150 | return vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2; | ||
| 151 | } | ||
| 152 | |||
| 153 | static bool ControllerHasPaddles(Uint16 vendor_id, Uint16 product_id) | ||
| 154 | { | ||
| 155 | return SDL_IsJoystickXboxOneElite(vendor_id, product_id); | ||
| 156 | } | ||
| 157 | |||
| 158 | static bool ControllerHasTriggerRumble(Uint16 vendor_id, Uint16 product_id) | ||
| 159 | { | ||
| 160 | // All the Microsoft Xbox One controllers have trigger rumble | ||
| 161 | if (vendor_id == USB_VENDOR_MICROSOFT) { | ||
| 162 | return true; | ||
| 163 | } | ||
| 164 | |||
| 165 | /* It turns out other controllers a mixed bag as to whether they support | ||
| 166 | trigger rumble or not, and when they do it's often a buzz rather than | ||
| 167 | the vibration of the Microsoft trigger rumble, so for now just pretend | ||
| 168 | that it is not available. | ||
| 169 | */ | ||
| 170 | return false; | ||
| 171 | } | ||
| 172 | |||
| 173 | static bool ControllerHasShareButton(Uint16 vendor_id, Uint16 product_id) | ||
| 174 | { | ||
| 175 | return SDL_IsJoystickXboxSeriesX(vendor_id, product_id); | ||
| 176 | } | ||
| 177 | |||
| 178 | static int GetHomeLEDBrightness(const char *hint) | ||
| 179 | { | ||
| 180 | const int MAX_VALUE = 50; | ||
| 181 | int value = 20; | ||
| 182 | |||
| 183 | if (hint && *hint) { | ||
| 184 | if (SDL_strchr(hint, '.') != NULL) { | ||
| 185 | value = (int)(MAX_VALUE * SDL_atof(hint)); | ||
| 186 | } else if (!SDL_GetStringBoolean(hint, true)) { | ||
| 187 | value = 0; | ||
| 188 | } | ||
| 189 | } | ||
| 190 | return value; | ||
| 191 | } | ||
| 192 | |||
| 193 | static void SetHomeLED(SDL_DriverXboxOne_Context *ctx, int value) | ||
| 194 | { | ||
| 195 | Uint8 led_packet[] = { 0x0A, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00 }; | ||
| 196 | |||
| 197 | if (value > 0) { | ||
| 198 | led_packet[5] = 0x01; | ||
| 199 | led_packet[6] = (Uint8)value; | ||
| 200 | } | ||
| 201 | SDL_HIDAPI_SendRumble(ctx->device, led_packet, sizeof(led_packet)); | ||
| 202 | } | ||
| 203 | |||
| 204 | static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 205 | { | ||
| 206 | SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)userdata; | ||
| 207 | |||
| 208 | if (hint && *hint) { | ||
| 209 | SetHomeLED(ctx, GetHomeLEDBrightness(hint)); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | static void SetInitState(SDL_DriverXboxOne_Context *ctx, SDL_XboxOneInitState state) | ||
| 214 | { | ||
| 215 | #ifdef DEBUG_JOYSTICK | ||
| 216 | SDL_Log("Setting init state %d", state); | ||
| 217 | #endif | ||
| 218 | ctx->init_state = state; | ||
| 219 | } | ||
| 220 | |||
| 221 | static Uint8 GetNextPacketSequence(SDL_DriverXboxOne_Context *ctx) | ||
| 222 | { | ||
| 223 | ++ctx->sequence; | ||
| 224 | if (!ctx->sequence) { | ||
| 225 | ctx->sequence = 1; | ||
| 226 | } | ||
| 227 | return ctx->sequence; | ||
| 228 | } | ||
| 229 | |||
| 230 | static bool SendProtocolPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) | ||
| 231 | { | ||
| 232 | #ifdef DEBUG_XBOX_PROTOCOL | ||
| 233 | HIDAPI_DumpPacket("Xbox One sending packet: size = %d", data, size); | ||
| 234 | #endif | ||
| 235 | |||
| 236 | ctx->send_time = SDL_GetTicks(); | ||
| 237 | |||
| 238 | if (!SDL_HIDAPI_LockRumble()) { | ||
| 239 | return false; | ||
| 240 | } | ||
| 241 | if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size) != size) { | ||
| 242 | return false; | ||
| 243 | } | ||
| 244 | return true; | ||
| 245 | } | ||
| 246 | |||
| 247 | #if 0 | ||
| 248 | static bool SendSerialRequest(SDL_DriverXboxOne_Context *ctx) | ||
| 249 | { | ||
| 250 | Uint8 packet[] = { 0x1E, 0x20, 0x00, 0x01, 0x04 }; | ||
| 251 | |||
| 252 | packet[2] = GetNextPacketSequence(ctx); | ||
| 253 | |||
| 254 | /* Request the serial number | ||
| 255 | * Sending this should be done only after startup is complete. | ||
| 256 | * It will cancel the announce packet if sent before that, and will be | ||
| 257 | * ignored if sent during the startup sequence. | ||
| 258 | */ | ||
| 259 | if (!SendProtocolPacket(ctx, packet, sizeof(packet))) { | ||
| 260 | SDL_SetError("Couldn't send serial request packet"); | ||
| 261 | return false; | ||
| 262 | } | ||
| 263 | return true; | ||
| 264 | } | ||
| 265 | #endif | ||
| 266 | |||
| 267 | static bool ControllerSendsAnnouncement(Uint16 vendor_id, Uint16 product_id) | ||
| 268 | { | ||
| 269 | if (vendor_id == USB_VENDOR_PDP && product_id == 0x0246) { | ||
| 270 | // The PDP Rock Candy (PID 0x0246) doesn't send the announce packet on Linux for some reason | ||
| 271 | return false; | ||
| 272 | } | ||
| 273 | return true; | ||
| 274 | } | ||
| 275 | |||
| 276 | static bool SendIdentificationRequest(SDL_DriverXboxOne_Context *ctx) | ||
| 277 | { | ||
| 278 | // Request identification, sent in response to announce packet | ||
| 279 | Uint8 packet[] = { | ||
| 280 | 0x04, 0x20, 0x00, 0x00 | ||
| 281 | }; | ||
| 282 | |||
| 283 | packet[2] = GetNextPacketSequence(ctx); | ||
| 284 | |||
| 285 | if (!SendProtocolPacket(ctx, packet, sizeof(packet))) { | ||
| 286 | SDL_SetError("Couldn't send identification request packet"); | ||
| 287 | return false; | ||
| 288 | } | ||
| 289 | return true; | ||
| 290 | } | ||
| 291 | |||
| 292 | static bool SendControllerStartup(SDL_DriverXboxOne_Context *ctx) | ||
| 293 | { | ||
| 294 | Uint16 vendor_id = ctx->vendor_id; | ||
| 295 | Uint16 product_id = ctx->product_id; | ||
| 296 | Uint8 init_packet[USB_PACKET_LENGTH]; | ||
| 297 | size_t i; | ||
| 298 | |||
| 299 | for (i = 0; i < SDL_arraysize(xboxone_init_packets); ++i) { | ||
| 300 | const SDL_DriverXboxOne_InitPacket *packet = &xboxone_init_packets[i]; | ||
| 301 | |||
| 302 | if (packet->vendor_id && (vendor_id != packet->vendor_id)) { | ||
| 303 | continue; | ||
| 304 | } | ||
| 305 | |||
| 306 | if (packet->product_id && (product_id != packet->product_id)) { | ||
| 307 | continue; | ||
| 308 | } | ||
| 309 | |||
| 310 | SDL_memcpy(init_packet, packet->data, packet->size); | ||
| 311 | init_packet[2] = GetNextPacketSequence(ctx); | ||
| 312 | |||
| 313 | if (init_packet[0] == 0x0A) { | ||
| 314 | // Get the initial brightness value | ||
| 315 | int brightness = GetHomeLEDBrightness(SDL_GetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED)); | ||
| 316 | init_packet[5] = (brightness > 0) ? 0x01 : 0x00; | ||
| 317 | init_packet[6] = (Uint8)brightness; | ||
| 318 | } | ||
| 319 | |||
| 320 | if (!SendProtocolPacket(ctx, init_packet, packet->size)) { | ||
| 321 | SDL_SetError("Couldn't send initialization packet"); | ||
| 322 | return false; | ||
| 323 | } | ||
| 324 | |||
| 325 | // Wait to process the rumble packet | ||
| 326 | if (packet->data == xbox_init_powera_rumble) { | ||
| 327 | SDL_Delay(10); | ||
| 328 | } | ||
| 329 | } | ||
| 330 | return true; | ||
| 331 | } | ||
| 332 | |||
| 333 | static void HIDAPI_DriverXboxOne_RegisterHints(SDL_HintCallback callback, void *userdata) | ||
| 334 | { | ||
| 335 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata); | ||
| 336 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, callback, userdata); | ||
| 337 | } | ||
| 338 | |||
| 339 | static void HIDAPI_DriverXboxOne_UnregisterHints(SDL_HintCallback callback, void *userdata) | ||
| 340 | { | ||
| 341 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata); | ||
| 342 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, callback, userdata); | ||
| 343 | } | ||
| 344 | |||
| 345 | static bool HIDAPI_DriverXboxOne_IsEnabled(void) | ||
| 346 | { | ||
| 347 | return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, | ||
| 348 | SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT))); | ||
| 349 | } | ||
| 350 | |||
| 351 | 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) | ||
| 352 | { | ||
| 353 | #if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI) | ||
| 354 | if (!SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) { | ||
| 355 | // On macOS we get a shortened version of the real report and | ||
| 356 | // you can't write output reports for wired controllers, so | ||
| 357 | // we'll just use the GCController support instead. | ||
| 358 | return false; | ||
| 359 | } | ||
| 360 | #endif | ||
| 361 | return (type == SDL_GAMEPAD_TYPE_XBOXONE); | ||
| 362 | } | ||
| 363 | |||
| 364 | static bool HIDAPI_DriverXboxOne_InitDevice(SDL_HIDAPI_Device *device) | ||
| 365 | { | ||
| 366 | SDL_DriverXboxOne_Context *ctx; | ||
| 367 | |||
| 368 | ctx = (SDL_DriverXboxOne_Context *)SDL_calloc(1, sizeof(*ctx)); | ||
| 369 | if (!ctx) { | ||
| 370 | return false; | ||
| 371 | } | ||
| 372 | ctx->device = device; | ||
| 373 | |||
| 374 | device->context = ctx; | ||
| 375 | |||
| 376 | ctx->vendor_id = device->vendor_id; | ||
| 377 | ctx->product_id = device->product_id; | ||
| 378 | ctx->start_time = SDL_GetTicks(); | ||
| 379 | ctx->sequence = 0; | ||
| 380 | ctx->has_color_led = ControllerHasColorLED(ctx->vendor_id, ctx->product_id); | ||
| 381 | ctx->has_paddles = ControllerHasPaddles(ctx->vendor_id, ctx->product_id); | ||
| 382 | ctx->has_trigger_rumble = ControllerHasTriggerRumble(ctx->vendor_id, ctx->product_id); | ||
| 383 | ctx->has_share_button = ControllerHasShareButton(ctx->vendor_id, ctx->product_id); | ||
| 384 | |||
| 385 | // Assume that the controller is correctly initialized when we start | ||
| 386 | if (!ControllerSendsAnnouncement(device->vendor_id, device->product_id)) { | ||
| 387 | // Jump into the startup sequence for this controller | ||
| 388 | ctx->init_state = XBOX_ONE_INIT_STATE_STARTUP; | ||
| 389 | } else { | ||
| 390 | ctx->init_state = XBOX_ONE_INIT_STATE_COMPLETE; | ||
| 391 | } | ||
| 392 | |||
| 393 | #ifdef DEBUG_JOYSTICK | ||
| 394 | SDL_Log("Controller version: %d (0x%.4x)", device->version, device->version); | ||
| 395 | #endif | ||
| 396 | |||
| 397 | device->type = SDL_GAMEPAD_TYPE_XBOXONE; | ||
| 398 | |||
| 399 | return HIDAPI_JoystickConnected(device, NULL); | ||
| 400 | } | ||
| 401 | |||
| 402 | static int HIDAPI_DriverXboxOne_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) | ||
| 403 | { | ||
| 404 | return -1; | ||
| 405 | } | ||
| 406 | |||
| 407 | static void HIDAPI_DriverXboxOne_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) | ||
| 408 | { | ||
| 409 | } | ||
| 410 | |||
| 411 | static bool HIDAPI_DriverXboxOne_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 412 | { | ||
| 413 | SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; | ||
| 414 | |||
| 415 | SDL_AssertJoysticksLocked(); | ||
| 416 | |||
| 417 | ctx->low_frequency_rumble = 0; | ||
| 418 | ctx->high_frequency_rumble = 0; | ||
| 419 | ctx->left_trigger_rumble = 0; | ||
| 420 | ctx->right_trigger_rumble = 0; | ||
| 421 | ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_IDLE; | ||
| 422 | ctx->rumble_time = 0; | ||
| 423 | ctx->rumble_pending = false; | ||
| 424 | SDL_zeroa(ctx->last_state); | ||
| 425 | |||
| 426 | // Initialize the joystick capabilities | ||
| 427 | joystick->nbuttons = 11; | ||
| 428 | if (ctx->has_share_button) { | ||
| 429 | joystick->nbuttons += 1; | ||
| 430 | } | ||
| 431 | if (ctx->has_paddles) { | ||
| 432 | joystick->nbuttons += 4; | ||
| 433 | } | ||
| 434 | joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 435 | joystick->nhats = 1; | ||
| 436 | |||
| 437 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED, | ||
| 438 | SDL_HomeLEDHintChanged, ctx); | ||
| 439 | return true; | ||
| 440 | } | ||
| 441 | |||
| 442 | static void HIDAPI_DriverXboxOne_RumbleSent(void *userdata) | ||
| 443 | { | ||
| 444 | SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)userdata; | ||
| 445 | ctx->rumble_time = SDL_GetTicks(); | ||
| 446 | } | ||
| 447 | |||
| 448 | static bool HIDAPI_DriverXboxOne_UpdateRumble(SDL_DriverXboxOne_Context *ctx) | ||
| 449 | { | ||
| 450 | if (ctx->rumble_state == XBOX_ONE_RUMBLE_STATE_QUEUED) { | ||
| 451 | if (ctx->rumble_time) { | ||
| 452 | ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_BUSY; | ||
| 453 | } | ||
| 454 | } | ||
| 455 | |||
| 456 | if (ctx->rumble_state == XBOX_ONE_RUMBLE_STATE_BUSY) { | ||
| 457 | const int RUMBLE_BUSY_TIME_MS = ctx->device->is_bluetooth ? 50 : 10; | ||
| 458 | if (SDL_GetTicks() >= (ctx->rumble_time + RUMBLE_BUSY_TIME_MS)) { | ||
| 459 | ctx->rumble_time = 0; | ||
| 460 | ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_IDLE; | ||
| 461 | } | ||
| 462 | } | ||
| 463 | |||
| 464 | if (!ctx->rumble_pending) { | ||
| 465 | return true; | ||
| 466 | } | ||
| 467 | |||
| 468 | if (ctx->rumble_state != XBOX_ONE_RUMBLE_STATE_IDLE) { | ||
| 469 | return true; | ||
| 470 | } | ||
| 471 | |||
| 472 | // We're no longer pending, even if we fail to send the rumble below | ||
| 473 | ctx->rumble_pending = false; | ||
| 474 | |||
| 475 | if (!SDL_HIDAPI_LockRumble()) { | ||
| 476 | return false; | ||
| 477 | } | ||
| 478 | |||
| 479 | if (ctx->device->is_bluetooth) { | ||
| 480 | Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB }; | ||
| 481 | |||
| 482 | rumble_packet[2] = ctx->left_trigger_rumble; | ||
| 483 | rumble_packet[3] = ctx->right_trigger_rumble; | ||
| 484 | rumble_packet[4] = ctx->low_frequency_rumble; | ||
| 485 | rumble_packet[5] = ctx->high_frequency_rumble; | ||
| 486 | |||
| 487 | if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(ctx->device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) { | ||
| 488 | return SDL_SetError("Couldn't send rumble packet"); | ||
| 489 | } | ||
| 490 | } else { | ||
| 491 | Uint8 rumble_packet[] = { 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB }; | ||
| 492 | |||
| 493 | rumble_packet[6] = ctx->left_trigger_rumble; | ||
| 494 | rumble_packet[7] = ctx->right_trigger_rumble; | ||
| 495 | rumble_packet[8] = ctx->low_frequency_rumble; | ||
| 496 | rumble_packet[9] = ctx->high_frequency_rumble; | ||
| 497 | |||
| 498 | if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(ctx->device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) { | ||
| 499 | return SDL_SetError("Couldn't send rumble packet"); | ||
| 500 | } | ||
| 501 | } | ||
| 502 | |||
| 503 | ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_QUEUED; | ||
| 504 | |||
| 505 | return true; | ||
| 506 | } | ||
| 507 | |||
| 508 | static bool HIDAPI_DriverXboxOne_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 509 | { | ||
| 510 | SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; | ||
| 511 | |||
| 512 | // Magnitude is 1..100 so scale the 16-bit input here | ||
| 513 | ctx->low_frequency_rumble = (Uint8)(low_frequency_rumble / 655); | ||
| 514 | ctx->high_frequency_rumble = (Uint8)(high_frequency_rumble / 655); | ||
| 515 | ctx->rumble_pending = true; | ||
| 516 | |||
| 517 | return HIDAPI_DriverXboxOne_UpdateRumble(ctx); | ||
| 518 | } | ||
| 519 | |||
| 520 | static bool HIDAPI_DriverXboxOne_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 521 | { | ||
| 522 | SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; | ||
| 523 | |||
| 524 | if (!ctx->has_trigger_rumble) { | ||
| 525 | return SDL_Unsupported(); | ||
| 526 | } | ||
| 527 | |||
| 528 | // Magnitude is 1..100 so scale the 16-bit input here | ||
| 529 | ctx->left_trigger_rumble = (Uint8)(left_rumble / 655); | ||
| 530 | ctx->right_trigger_rumble = (Uint8)(right_rumble / 655); | ||
| 531 | ctx->rumble_pending = true; | ||
| 532 | |||
| 533 | return HIDAPI_DriverXboxOne_UpdateRumble(ctx); | ||
| 534 | } | ||
| 535 | |||
| 536 | static Uint32 HIDAPI_DriverXboxOne_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 537 | { | ||
| 538 | SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; | ||
| 539 | Uint32 result = 0; | ||
| 540 | |||
| 541 | result |= SDL_JOYSTICK_CAP_RUMBLE; | ||
| 542 | if (ctx->has_trigger_rumble) { | ||
| 543 | result |= SDL_JOYSTICK_CAP_TRIGGER_RUMBLE; | ||
| 544 | } | ||
| 545 | |||
| 546 | if (ctx->has_color_led) { | ||
| 547 | result |= SDL_JOYSTICK_CAP_RGB_LED; | ||
| 548 | } | ||
| 549 | |||
| 550 | return result; | ||
| 551 | } | ||
| 552 | |||
| 553 | static bool HIDAPI_DriverXboxOne_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 554 | { | ||
| 555 | SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; | ||
| 556 | |||
| 557 | if (ctx->has_color_led) { | ||
| 558 | Uint8 led_packet[] = { 0x0E, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 }; | ||
| 559 | |||
| 560 | led_packet[5] = 0x00; // Whiteness? Sets white intensity when RGB is 0, seems additive | ||
| 561 | led_packet[6] = red; | ||
| 562 | led_packet[7] = green; | ||
| 563 | led_packet[8] = blue; | ||
| 564 | |||
| 565 | if (SDL_HIDAPI_SendRumble(device, led_packet, sizeof(led_packet)) != sizeof(led_packet)) { | ||
| 566 | return SDL_SetError("Couldn't send LED packet"); | ||
| 567 | } | ||
| 568 | return true; | ||
| 569 | } else { | ||
| 570 | return SDL_Unsupported(); | ||
| 571 | } | ||
| 572 | } | ||
| 573 | |||
| 574 | static bool HIDAPI_DriverXboxOne_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) | ||
| 575 | { | ||
| 576 | return SDL_Unsupported(); | ||
| 577 | } | ||
| 578 | |||
| 579 | static bool HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) | ||
| 580 | { | ||
| 581 | return SDL_Unsupported(); | ||
| 582 | } | ||
| 583 | |||
| 584 | /* | ||
| 585 | * The Xbox One Elite controller with 5.13+ firmware sends the unmapped state in a separate packet. | ||
| 586 | * We can use this to send the paddle state when they aren't mapped | ||
| 587 | */ | ||
| 588 | static void HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) | ||
| 589 | { | ||
| 590 | Uint8 profile; | ||
| 591 | int paddle_index; | ||
| 592 | int button1_bit; | ||
| 593 | int button2_bit; | ||
| 594 | int button3_bit; | ||
| 595 | int button4_bit; | ||
| 596 | bool paddles_mapped; | ||
| 597 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 598 | |||
| 599 | if (size == 17) { | ||
| 600 | // XBox One Elite Series 2 | ||
| 601 | paddle_index = 14; | ||
| 602 | button1_bit = 0x01; | ||
| 603 | button2_bit = 0x02; | ||
| 604 | button3_bit = 0x04; | ||
| 605 | button4_bit = 0x08; | ||
| 606 | profile = data[15]; | ||
| 607 | |||
| 608 | if (profile == 0) { | ||
| 609 | paddles_mapped = false; | ||
| 610 | } else if (SDL_memcmp(&data[0], &ctx->last_state[0], 14) == 0) { | ||
| 611 | // We're using a profile, but paddles aren't mapped | ||
| 612 | paddles_mapped = false; | ||
| 613 | } else { | ||
| 614 | // Something is mapped, we can't use the paddles | ||
| 615 | paddles_mapped = true; | ||
| 616 | } | ||
| 617 | |||
| 618 | } else { | ||
| 619 | // Unknown format | ||
| 620 | return; | ||
| 621 | } | ||
| 622 | #ifdef DEBUG_XBOX_PROTOCOL | ||
| 623 | SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s", | ||
| 624 | (data[paddle_index] & button1_bit) ? 1 : 0, | ||
| 625 | (data[paddle_index] & button2_bit) ? 1 : 0, | ||
| 626 | (data[paddle_index] & button3_bit) ? 1 : 0, | ||
| 627 | (data[paddle_index] & button4_bit) ? 1 : 0, | ||
| 628 | paddles_mapped ? "TRUE" : "FALSE"); | ||
| 629 | #endif | ||
| 630 | |||
| 631 | if (paddles_mapped) { | ||
| 632 | // Respect that the paddles are being used for other controls and don't pass them on to the app | ||
| 633 | data[paddle_index] = 0; | ||
| 634 | } | ||
| 635 | |||
| 636 | if (ctx->last_paddle_state != data[paddle_index]) { | ||
| 637 | Uint8 nButton = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); // Next available button | ||
| 638 | SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button1_bit) != 0)); | ||
| 639 | SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button2_bit) != 0)); | ||
| 640 | SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button3_bit) != 0)); | ||
| 641 | SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button4_bit) != 0)); | ||
| 642 | ctx->last_paddle_state = data[paddle_index]; | ||
| 643 | } | ||
| 644 | ctx->has_unmapped_state = true; | ||
| 645 | } | ||
| 646 | |||
| 647 | static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) | ||
| 648 | { | ||
| 649 | Sint16 axis; | ||
| 650 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 651 | |||
| 652 | // Enable paddles on the Xbox Elite controller when connected over USB | ||
| 653 | if (ctx->has_paddles && !ctx->has_unmapped_state && size == 46) { | ||
| 654 | Uint8 packet[] = { 0x4d, 0x00, 0x00, 0x02, 0x07, 0x00 }; | ||
| 655 | |||
| 656 | #ifdef DEBUG_JOYSTICK | ||
| 657 | SDL_Log("Enabling paddles on XBox Elite 2"); | ||
| 658 | #endif | ||
| 659 | SDL_HIDAPI_SendRumble(ctx->device, packet, sizeof(packet)); | ||
| 660 | } | ||
| 661 | |||
| 662 | if (ctx->last_state[0] != data[0]) { | ||
| 663 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[0] & 0x04) != 0)); | ||
| 664 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[0] & 0x08) != 0)); | ||
| 665 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x10) != 0)); | ||
| 666 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x20) != 0)); | ||
| 667 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x40) != 0)); | ||
| 668 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x80) != 0)); | ||
| 669 | } | ||
| 670 | |||
| 671 | if (ctx->last_state[1] != data[1]) { | ||
| 672 | Uint8 hat = 0; | ||
| 673 | |||
| 674 | if (data[1] & 0x01) { | ||
| 675 | hat |= SDL_HAT_UP; | ||
| 676 | } | ||
| 677 | if (data[1] & 0x02) { | ||
| 678 | hat |= SDL_HAT_DOWN; | ||
| 679 | } | ||
| 680 | if (data[1] & 0x04) { | ||
| 681 | hat |= SDL_HAT_LEFT; | ||
| 682 | } | ||
| 683 | if (data[1] & 0x08) { | ||
| 684 | hat |= SDL_HAT_RIGHT; | ||
| 685 | } | ||
| 686 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 687 | |||
| 688 | if (ctx->vendor_id == USB_VENDOR_RAZER && ctx->product_id == USB_PRODUCT_RAZER_ATROX) { | ||
| 689 | // The Razer Atrox has the right and left shoulder bits reversed | ||
| 690 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x20) != 0)); | ||
| 691 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x10) != 0)); | ||
| 692 | } else { | ||
| 693 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0)); | ||
| 694 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0)); | ||
| 695 | } | ||
| 696 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x40) != 0)); | ||
| 697 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x80) != 0)); | ||
| 698 | } | ||
| 699 | |||
| 700 | if (ctx->has_share_button) { | ||
| 701 | /* Xbox Series X firmware version 5.0, report is 32 bytes, share button is in byte 14 | ||
| 702 | * Xbox Series X firmware version 5.1, report is 40 bytes, share button is in byte 14 | ||
| 703 | * Xbox Series X firmware version 5.5, report is 44 bytes, share button is in byte 18 | ||
| 704 | * Victrix Gambit Tournament Controller, report is 46 bytes, share button is in byte 28 | ||
| 705 | * ThrustMaster eSwap PRO Controller Xbox, report is 60 bytes, share button is in byte 42 | ||
| 706 | */ | ||
| 707 | if (size < 44) { | ||
| 708 | if (ctx->last_state[14] != data[14]) { | ||
| 709 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[14] & 0x01) != 0)); | ||
| 710 | } | ||
| 711 | } else if (size == 44) { | ||
| 712 | if (ctx->last_state[18] != data[18]) { | ||
| 713 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[18] & 0x01) != 0)); | ||
| 714 | } | ||
| 715 | } else if (size == 46) { | ||
| 716 | if (ctx->last_state[28] != data[28]) { | ||
| 717 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[28] & 0x01) != 0)); | ||
| 718 | } | ||
| 719 | } else if (size == 60) { | ||
| 720 | if (ctx->last_state[42] != data[42]) { | ||
| 721 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[42] & 0x01) != 0)); | ||
| 722 | } | ||
| 723 | } | ||
| 724 | } | ||
| 725 | |||
| 726 | /* Xbox One S report is 14 bytes | ||
| 727 | 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 | ||
| 728 | Paddle bits: | ||
| 729 | P3: 0x01 (A) P1: 0x02 (B) | ||
| 730 | P4: 0x04 (X) P2: 0x08 (Y) | ||
| 731 | 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 | ||
| 732 | Paddle bits: | ||
| 733 | P3: 0x04 (A) P1: 0x01 (B) | ||
| 734 | P4: 0x08 (X) P2: 0x02 (Y) | ||
| 735 | 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 | ||
| 736 | Paddle bits: | ||
| 737 | P3: 0x04 (A) P1: 0x01 (B) | ||
| 738 | P4: 0x08 (X) P2: 0x02 (Y) | ||
| 739 | 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 | ||
| 740 | Paddle bits: | ||
| 741 | P3: 0x04 (A) P1: 0x01 (B) | ||
| 742 | P4: 0x08 (X) P2: 0x02 (Y) | ||
| 743 | */ | ||
| 744 | if (ctx->has_paddles && !ctx->has_unmapped_state && (size == 29 || size == 34 || size == 46 || size == 47)) { | ||
| 745 | int paddle_index; | ||
| 746 | int button1_bit; | ||
| 747 | int button2_bit; | ||
| 748 | int button3_bit; | ||
| 749 | int button4_bit; | ||
| 750 | bool paddles_mapped; | ||
| 751 | |||
| 752 | if (size == 29) { | ||
| 753 | // XBox One Elite Series 1 | ||
| 754 | paddle_index = 28; | ||
| 755 | button1_bit = 0x02; | ||
| 756 | button2_bit = 0x08; | ||
| 757 | button3_bit = 0x01; | ||
| 758 | button4_bit = 0x04; | ||
| 759 | |||
| 760 | // The mapped controller state is at offset 0, the raw state is at offset 14, compare them to see if the paddles are mapped | ||
| 761 | paddles_mapped = (SDL_memcmp(&data[0], &data[14], 2) != 0); | ||
| 762 | |||
| 763 | } else if (size == 34) { | ||
| 764 | // XBox One Elite Series 2 | ||
| 765 | paddle_index = 14; | ||
| 766 | button1_bit = 0x01; | ||
| 767 | button2_bit = 0x02; | ||
| 768 | button3_bit = 0x04; | ||
| 769 | button4_bit = 0x08; | ||
| 770 | paddles_mapped = (data[15] != 0); | ||
| 771 | |||
| 772 | } else if (size == 46) { | ||
| 773 | // XBox One Elite Series 2 | ||
| 774 | paddle_index = 18; | ||
| 775 | button1_bit = 0x01; | ||
| 776 | button2_bit = 0x02; | ||
| 777 | button3_bit = 0x04; | ||
| 778 | button4_bit = 0x08; | ||
| 779 | paddles_mapped = (data[19] != 0); | ||
| 780 | } else /* if (size == 47) */ { | ||
| 781 | // XBox One Elite Series 2 | ||
| 782 | paddle_index = 14; | ||
| 783 | button1_bit = 0x01; | ||
| 784 | button2_bit = 0x02; | ||
| 785 | button3_bit = 0x04; | ||
| 786 | button4_bit = 0x08; | ||
| 787 | paddles_mapped = (data[20] != 0); | ||
| 788 | } | ||
| 789 | #ifdef DEBUG_XBOX_PROTOCOL | ||
| 790 | SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s", | ||
| 791 | (data[paddle_index] & button1_bit) ? 1 : 0, | ||
| 792 | (data[paddle_index] & button2_bit) ? 1 : 0, | ||
| 793 | (data[paddle_index] & button3_bit) ? 1 : 0, | ||
| 794 | (data[paddle_index] & button4_bit) ? 1 : 0, | ||
| 795 | paddles_mapped ? "TRUE" : "FALSE"); | ||
| 796 | #endif | ||
| 797 | |||
| 798 | if (paddles_mapped) { | ||
| 799 | // Respect that the paddles are being used for other controls and don't pass them on to the app | ||
| 800 | data[paddle_index] = 0; | ||
| 801 | } | ||
| 802 | |||
| 803 | if (ctx->last_paddle_state != data[paddle_index]) { | ||
| 804 | Uint8 nButton = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); // Next available button | ||
| 805 | SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button1_bit) != 0)); | ||
| 806 | SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button2_bit) != 0)); | ||
| 807 | SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button3_bit) != 0)); | ||
| 808 | SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button4_bit) != 0)); | ||
| 809 | ctx->last_paddle_state = data[paddle_index]; | ||
| 810 | } | ||
| 811 | } | ||
| 812 | |||
| 813 | axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[2])) * 64) - 32768; | ||
| 814 | if (axis == 32704) { | ||
| 815 | axis = 32767; | ||
| 816 | } | ||
| 817 | if (axis == -32768 && size == 26 && (data[18] & 0x80)) { | ||
| 818 | axis = 32767; | ||
| 819 | } | ||
| 820 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 821 | |||
| 822 | axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[4])) * 64) - 32768; | ||
| 823 | if (axis == -32768 && size == 26 && (data[18] & 0x40)) { | ||
| 824 | axis = 32767; | ||
| 825 | } | ||
| 826 | if (axis == 32704) { | ||
| 827 | axis = 32767; | ||
| 828 | } | ||
| 829 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 830 | |||
| 831 | axis = SDL_Swap16LE(*(Sint16 *)(&data[6])); | ||
| 832 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 833 | axis = SDL_Swap16LE(*(Sint16 *)(&data[8])); | ||
| 834 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); | ||
| 835 | axis = SDL_Swap16LE(*(Sint16 *)(&data[10])); | ||
| 836 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 837 | axis = SDL_Swap16LE(*(Sint16 *)(&data[12])); | ||
| 838 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); | ||
| 839 | |||
| 840 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 841 | |||
| 842 | // We don't have the unmapped state for this packet | ||
| 843 | ctx->has_unmapped_state = false; | ||
| 844 | } | ||
| 845 | |||
| 846 | static void HIDAPI_DriverXboxOne_HandleStatusPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) | ||
| 847 | { | ||
| 848 | if (ctx->init_state < XBOX_ONE_INIT_STATE_COMPLETE) { | ||
| 849 | SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); | ||
| 850 | } | ||
| 851 | } | ||
| 852 | |||
| 853 | static void HIDAPI_DriverXboxOne_HandleModePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) | ||
| 854 | { | ||
| 855 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 856 | |||
| 857 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[0] & 0x01) != 0)); | ||
| 858 | } | ||
| 859 | |||
| 860 | /* | ||
| 861 | * Xbox One S with firmware 3.1.1221 uses a 16 byte packet and the GUIDE button in a separate packet | ||
| 862 | */ | ||
| 863 | static void HIDAPI_DriverXboxOneBluetooth_HandleButtons16(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) | ||
| 864 | { | ||
| 865 | if (ctx->last_state[14] != data[14]) { | ||
| 866 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0)); | ||
| 867 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0)); | ||
| 868 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x04) != 0)); | ||
| 869 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x08) != 0)); | ||
| 870 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x10) != 0)); | ||
| 871 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x20) != 0)); | ||
| 872 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[14] & 0x40) != 0)); | ||
| 873 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[14] & 0x80) != 0)); | ||
| 874 | } | ||
| 875 | |||
| 876 | if (ctx->last_state[15] != data[15]) { | ||
| 877 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x01) != 0)); | ||
| 878 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x02) != 0)); | ||
| 879 | } | ||
| 880 | } | ||
| 881 | |||
| 882 | /* | ||
| 883 | * 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) | ||
| 884 | * Xbox One S with firmware 5.x uses a 17 byte packet with BACK and GUIDE buttons in byte 15 | ||
| 885 | * 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 | ||
| 886 | * 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 | ||
| 887 | * Xbox One Elite Series 2 with firmware 5.11.3112 uses a 19 byte packet with BACK and GUIDE buttons in byte 15 | ||
| 888 | * 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 | ||
| 889 | */ | ||
| 890 | static void HIDAPI_DriverXboxOneBluetooth_HandleButtons(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) | ||
| 891 | { | ||
| 892 | if (ctx->last_state[14] != data[14]) { | ||
| 893 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0)); | ||
| 894 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0)); | ||
| 895 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x08) != 0)); | ||
| 896 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x10) != 0)); | ||
| 897 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x40) != 0)); | ||
| 898 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x80) != 0)); | ||
| 899 | } | ||
| 900 | |||
| 901 | if (ctx->last_state[15] != data[15]) { | ||
| 902 | if (!ctx->has_guide_packet) { | ||
| 903 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[15] & 0x10) != 0)); | ||
| 904 | } | ||
| 905 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[15] & 0x08) != 0)); | ||
| 906 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x20) != 0)); | ||
| 907 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x40) != 0)); | ||
| 908 | } | ||
| 909 | |||
| 910 | if (ctx->has_share_button) { | ||
| 911 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[15] & 0x04) != 0)); | ||
| 912 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[16] & 0x01) != 0)); | ||
| 913 | } else { | ||
| 914 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[15] & 0x04) || ((data[16] & 0x01)) != 0)); | ||
| 915 | } | ||
| 916 | |||
| 917 | /* | ||
| 918 | Paddle bits: | ||
| 919 | P3: 0x04 (A) P1: 0x01 (B) | ||
| 920 | P4: 0x08 (X) P2: 0x02 (Y) | ||
| 921 | */ | ||
| 922 | if (ctx->has_paddles && (size == 20 || size == 39 || size == 55)) { | ||
| 923 | int paddle_index; | ||
| 924 | int button1_bit; | ||
| 925 | int button2_bit; | ||
| 926 | int button3_bit; | ||
| 927 | int button4_bit; | ||
| 928 | bool paddles_mapped; | ||
| 929 | |||
| 930 | if (size == 55) { | ||
| 931 | // Initial firmware for the Xbox Elite Series 2 controller | ||
| 932 | paddle_index = 33; | ||
| 933 | button1_bit = 0x01; | ||
| 934 | button2_bit = 0x02; | ||
| 935 | button3_bit = 0x04; | ||
| 936 | button4_bit = 0x08; | ||
| 937 | paddles_mapped = (data[35] != 0); | ||
| 938 | } else if (size == 39) { | ||
| 939 | // Updated firmware for the Xbox Elite Series 2 controller | ||
| 940 | paddle_index = 17; | ||
| 941 | button1_bit = 0x01; | ||
| 942 | button2_bit = 0x02; | ||
| 943 | button3_bit = 0x04; | ||
| 944 | button4_bit = 0x08; | ||
| 945 | paddles_mapped = (data[19] != 0); | ||
| 946 | } else /* if (size == 20) */ { | ||
| 947 | // Updated firmware for the Xbox Elite Series 2 controller (5.13+) | ||
| 948 | paddle_index = 19; | ||
| 949 | button1_bit = 0x01; | ||
| 950 | button2_bit = 0x02; | ||
| 951 | button3_bit = 0x04; | ||
| 952 | button4_bit = 0x08; | ||
| 953 | paddles_mapped = (data[17] != 0); | ||
| 954 | } | ||
| 955 | |||
| 956 | #ifdef DEBUG_XBOX_PROTOCOL | ||
| 957 | SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s", | ||
| 958 | (data[paddle_index] & button1_bit) ? 1 : 0, | ||
| 959 | (data[paddle_index] & button2_bit) ? 1 : 0, | ||
| 960 | (data[paddle_index] & button3_bit) ? 1 : 0, | ||
| 961 | (data[paddle_index] & button4_bit) ? 1 : 0, | ||
| 962 | paddles_mapped ? "TRUE" : "FALSE"); | ||
| 963 | #endif | ||
| 964 | |||
| 965 | if (paddles_mapped) { | ||
| 966 | // Respect that the paddles are being used for other controls and don't pass them on to the app | ||
| 967 | data[paddle_index] = 0; | ||
| 968 | } | ||
| 969 | |||
| 970 | if (ctx->last_paddle_state != data[paddle_index]) { | ||
| 971 | Uint8 nButton = SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON; // Next available button | ||
| 972 | SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button1_bit) != 0)); | ||
| 973 | SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button2_bit) != 0)); | ||
| 974 | SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button3_bit) != 0)); | ||
| 975 | SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button4_bit) != 0)); | ||
| 976 | ctx->last_paddle_state = data[paddle_index]; | ||
| 977 | } | ||
| 978 | } | ||
| 979 | } | ||
| 980 | |||
| 981 | static void HIDAPI_DriverXboxOneBluetooth_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) | ||
| 982 | { | ||
| 983 | Sint16 axis; | ||
| 984 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 985 | |||
| 986 | if (size == 16) { | ||
| 987 | // Original Xbox One S, with separate report for guide button | ||
| 988 | HIDAPI_DriverXboxOneBluetooth_HandleButtons16(timestamp, joystick, ctx, data, size); | ||
| 989 | } else if (size > 16) { | ||
| 990 | HIDAPI_DriverXboxOneBluetooth_HandleButtons(timestamp, joystick, ctx, data, size); | ||
| 991 | } else { | ||
| 992 | #ifdef DEBUG_XBOX_PROTOCOL | ||
| 993 | SDL_Log("Unknown Bluetooth state packet format"); | ||
| 994 | #endif | ||
| 995 | return; | ||
| 996 | } | ||
| 997 | |||
| 998 | if (ctx->last_state[13] != data[13]) { | ||
| 999 | Uint8 hat; | ||
| 1000 | |||
| 1001 | switch (data[13]) { | ||
| 1002 | case 1: | ||
| 1003 | hat = SDL_HAT_UP; | ||
| 1004 | break; | ||
| 1005 | case 2: | ||
| 1006 | hat = SDL_HAT_RIGHTUP; | ||
| 1007 | break; | ||
| 1008 | case 3: | ||
| 1009 | hat = SDL_HAT_RIGHT; | ||
| 1010 | break; | ||
| 1011 | case 4: | ||
| 1012 | hat = SDL_HAT_RIGHTDOWN; | ||
| 1013 | break; | ||
| 1014 | case 5: | ||
| 1015 | hat = SDL_HAT_DOWN; | ||
| 1016 | break; | ||
| 1017 | case 6: | ||
| 1018 | hat = SDL_HAT_LEFTDOWN; | ||
| 1019 | break; | ||
| 1020 | case 7: | ||
| 1021 | hat = SDL_HAT_LEFT; | ||
| 1022 | break; | ||
| 1023 | case 8: | ||
| 1024 | hat = SDL_HAT_LEFTUP; | ||
| 1025 | break; | ||
| 1026 | default: | ||
| 1027 | hat = SDL_HAT_CENTERED; | ||
| 1028 | break; | ||
| 1029 | } | ||
| 1030 | SDL_SendJoystickHat(timestamp, joystick, 0, hat); | ||
| 1031 | } | ||
| 1032 | |||
| 1033 | axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[9])) * 64) - 32768; | ||
| 1034 | if (axis == 32704) { | ||
| 1035 | axis = 32767; | ||
| 1036 | } | ||
| 1037 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); | ||
| 1038 | |||
| 1039 | axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[11])) * 64) - 32768; | ||
| 1040 | if (axis == 32704) { | ||
| 1041 | axis = 32767; | ||
| 1042 | } | ||
| 1043 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); | ||
| 1044 | |||
| 1045 | axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[1])) - 0x8000; | ||
| 1046 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); | ||
| 1047 | axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[3])) - 0x8000; | ||
| 1048 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); | ||
| 1049 | axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[5])) - 0x8000; | ||
| 1050 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); | ||
| 1051 | axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[7])) - 0x8000; | ||
| 1052 | SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); | ||
| 1053 | |||
| 1054 | SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); | ||
| 1055 | } | ||
| 1056 | |||
| 1057 | static void HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) | ||
| 1058 | { | ||
| 1059 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 1060 | |||
| 1061 | ctx->has_guide_packet = true; | ||
| 1062 | SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x01) != 0)); | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | static void HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) | ||
| 1066 | { | ||
| 1067 | Uint8 flags = data[1]; | ||
| 1068 | bool on_usb = (((flags & 0x0C) >> 2) == 0); | ||
| 1069 | SDL_PowerState state; | ||
| 1070 | int percent = 0; | ||
| 1071 | |||
| 1072 | // Mapped percentage value from: | ||
| 1073 | // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getbatterystate | ||
| 1074 | switch (flags & 0x03) { | ||
| 1075 | case 0: | ||
| 1076 | percent = 10; | ||
| 1077 | break; | ||
| 1078 | case 1: | ||
| 1079 | percent = 40; | ||
| 1080 | break; | ||
| 1081 | case 2: | ||
| 1082 | percent = 70; | ||
| 1083 | break; | ||
| 1084 | case 3: | ||
| 1085 | percent = 100; | ||
| 1086 | break; | ||
| 1087 | } | ||
| 1088 | if (on_usb) { | ||
| 1089 | state = SDL_POWERSTATE_CHARGING; | ||
| 1090 | } else { | ||
| 1091 | state = SDL_POWERSTATE_ON_BATTERY; | ||
| 1092 | } | ||
| 1093 | SDL_SendJoystickPowerInfo(joystick, state, percent); | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | static void HIDAPI_DriverXboxOne_HandleSerialIDPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size) | ||
| 1097 | { | ||
| 1098 | char serial[29]; | ||
| 1099 | int i; | ||
| 1100 | |||
| 1101 | for (i = 0; i < 14; ++i) { | ||
| 1102 | SDL_uitoa(data[2 + i], &serial[i * 2], 16); | ||
| 1103 | } | ||
| 1104 | serial[i * 2] = '\0'; | ||
| 1105 | |||
| 1106 | #ifdef DEBUG_JOYSTICK | ||
| 1107 | SDL_Log("Setting serial number to %s", serial); | ||
| 1108 | #endif | ||
| 1109 | HIDAPI_SetDeviceSerial(ctx->device, serial); | ||
| 1110 | } | ||
| 1111 | |||
| 1112 | static bool HIDAPI_DriverXboxOne_UpdateInitState(SDL_DriverXboxOne_Context *ctx) | ||
| 1113 | { | ||
| 1114 | SDL_XboxOneInitState prev_state; | ||
| 1115 | do { | ||
| 1116 | prev_state = ctx->init_state; | ||
| 1117 | |||
| 1118 | switch (ctx->init_state) { | ||
| 1119 | case XBOX_ONE_INIT_STATE_ANNOUNCED: | ||
| 1120 | if (XBOX_ONE_DRIVER_ACTIVE) { | ||
| 1121 | // The driver is taking care of identification | ||
| 1122 | SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); | ||
| 1123 | } else { | ||
| 1124 | SendIdentificationRequest(ctx); | ||
| 1125 | SetInitState(ctx, XBOX_ONE_INIT_STATE_IDENTIFYING); | ||
| 1126 | } | ||
| 1127 | break; | ||
| 1128 | case XBOX_ONE_INIT_STATE_IDENTIFYING: | ||
| 1129 | if (SDL_GetTicks() >= (ctx->send_time + CONTROLLER_IDENTIFY_TIMEOUT_MS)) { | ||
| 1130 | // We haven't heard anything, let's move on | ||
| 1131 | #ifdef DEBUG_JOYSTICK | ||
| 1132 | SDL_Log("Identification request timed out after %llu ms", (SDL_GetTicks() - ctx->send_time)); | ||
| 1133 | #endif | ||
| 1134 | SetInitState(ctx, XBOX_ONE_INIT_STATE_STARTUP); | ||
| 1135 | } | ||
| 1136 | break; | ||
| 1137 | case XBOX_ONE_INIT_STATE_STARTUP: | ||
| 1138 | if (XBOX_ONE_DRIVER_ACTIVE) { | ||
| 1139 | // The driver is taking care of startup | ||
| 1140 | SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); | ||
| 1141 | } else { | ||
| 1142 | SendControllerStartup(ctx); | ||
| 1143 | SetInitState(ctx, XBOX_ONE_INIT_STATE_PREPARE_INPUT); | ||
| 1144 | } | ||
| 1145 | break; | ||
| 1146 | case XBOX_ONE_INIT_STATE_PREPARE_INPUT: | ||
| 1147 | if (SDL_GetTicks() >= (ctx->send_time + CONTROLLER_PREPARE_INPUT_TIMEOUT_MS)) { | ||
| 1148 | #ifdef DEBUG_JOYSTICK | ||
| 1149 | SDL_Log("Prepare input complete after %llu ms", (SDL_GetTicks() - ctx->send_time)); | ||
| 1150 | #endif | ||
| 1151 | SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); | ||
| 1152 | } | ||
| 1153 | break; | ||
| 1154 | case XBOX_ONE_INIT_STATE_COMPLETE: | ||
| 1155 | break; | ||
| 1156 | } | ||
| 1157 | |||
| 1158 | } while (ctx->init_state != prev_state); | ||
| 1159 | |||
| 1160 | return true; | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | /* GIP protocol handling adapted under the Zlib license with permission from @medusalix: | ||
| 1164 | * https://github.com/medusalix/xone/blob/master/bus/protocol.h | ||
| 1165 | * https://github.com/medusalix/xone/blob/master/bus/protocol.c | ||
| 1166 | */ | ||
| 1167 | #define GIP_HEADER_MIN_LENGTH 3 | ||
| 1168 | |||
| 1169 | // Internal commands | ||
| 1170 | #define GIP_CMD_ACKNOWLEDGE 0x01 | ||
| 1171 | #define GIP_CMD_ANNOUNCE 0x02 | ||
| 1172 | #define GIP_CMD_STATUS 0x03 | ||
| 1173 | #define GIP_CMD_IDENTIFY 0x04 | ||
| 1174 | #define GIP_CMD_POWER 0x05 | ||
| 1175 | #define GIP_CMD_AUTHENTICATE 0x06 | ||
| 1176 | #define GIP_CMD_VIRTUAL_KEY 0x07 | ||
| 1177 | #define GIP_CMD_AUDIO_CONTROL 0x08 | ||
| 1178 | #define GIP_CMD_LED 0x0A | ||
| 1179 | #define GIP_CMD_HID_REPORT 0x0B | ||
| 1180 | #define GIP_CMD_FIRMWARE 0x0C | ||
| 1181 | #define GIP_CMD_SERIAL_NUMBER 0x1E | ||
| 1182 | #define GIP_CMD_AUDIO_SAMPLES 0x60 | ||
| 1183 | |||
| 1184 | // External commands | ||
| 1185 | #define GIP_CMD_RUMBLE 0x09 | ||
| 1186 | #define GIP_CMD_UNMAPPED_STATE 0x0C | ||
| 1187 | #define GIP_CMD_INPUT 0x20 | ||
| 1188 | |||
| 1189 | // Header option flags | ||
| 1190 | #define GIP_OPT_ACKNOWLEDGE 0x10 | ||
| 1191 | #define GIP_OPT_INTERNAL 0x20 | ||
| 1192 | #define GIP_OPT_CHUNK_START 0x40 | ||
| 1193 | #define GIP_OPT_CHUNK 0x80 | ||
| 1194 | |||
| 1195 | #pragma pack(push, 1) | ||
| 1196 | |||
| 1197 | struct gip_header { | ||
| 1198 | Uint8 command; | ||
| 1199 | Uint8 options; | ||
| 1200 | Uint8 sequence; | ||
| 1201 | Uint32 packet_length; | ||
| 1202 | Uint32 chunk_offset; | ||
| 1203 | }; | ||
| 1204 | |||
| 1205 | struct gip_pkt_acknowledge { | ||
| 1206 | Uint8 unknown; | ||
| 1207 | Uint8 command; | ||
| 1208 | Uint8 options; | ||
| 1209 | Uint16 length; | ||
| 1210 | Uint8 padding[2]; | ||
| 1211 | Uint16 remaining; | ||
| 1212 | }; | ||
| 1213 | |||
| 1214 | #pragma pack(pop) | ||
| 1215 | |||
| 1216 | static int EncodeVariableInt(Uint8 *buf, Uint32 val) | ||
| 1217 | { | ||
| 1218 | int i; | ||
| 1219 | |||
| 1220 | for (i = 0; i < sizeof(val); i++) { | ||
| 1221 | buf[i] = (Uint8)val; | ||
| 1222 | if (val > 0x7F) { | ||
| 1223 | buf[i] |= 0x80; | ||
| 1224 | } | ||
| 1225 | |||
| 1226 | val >>= 7; | ||
| 1227 | if (!val) { | ||
| 1228 | break; | ||
| 1229 | } | ||
| 1230 | } | ||
| 1231 | return i + 1; | ||
| 1232 | } | ||
| 1233 | |||
| 1234 | static int DecodeVariableInt(const Uint8 *data, int len, void *out) | ||
| 1235 | { | ||
| 1236 | int i; | ||
| 1237 | Uint32 val = 0; | ||
| 1238 | |||
| 1239 | for (i = 0; i < sizeof(val) && i < len; i++) { | ||
| 1240 | val |= (data[i] & 0x7F) << (i * 7); | ||
| 1241 | |||
| 1242 | if (!(data[i] & 0x80)) { | ||
| 1243 | break; | ||
| 1244 | } | ||
| 1245 | } | ||
| 1246 | SDL_memcpy(out, &val, sizeof(val)); | ||
| 1247 | return i + 1; | ||
| 1248 | } | ||
| 1249 | |||
| 1250 | static int HIDAPI_GIP_GetActualHeaderLength(struct gip_header *hdr) | ||
| 1251 | { | ||
| 1252 | Uint32 pkt_len = hdr->packet_length; | ||
| 1253 | Uint32 chunk_offset = hdr->chunk_offset; | ||
| 1254 | int len = GIP_HEADER_MIN_LENGTH; | ||
| 1255 | |||
| 1256 | do { | ||
| 1257 | len++; | ||
| 1258 | pkt_len >>= 7; | ||
| 1259 | } while (pkt_len); | ||
| 1260 | |||
| 1261 | if (hdr->options & GIP_OPT_CHUNK) { | ||
| 1262 | while (chunk_offset) { | ||
| 1263 | len++; | ||
| 1264 | chunk_offset >>= 7; | ||
| 1265 | } | ||
| 1266 | } | ||
| 1267 | |||
| 1268 | return len; | ||
| 1269 | } | ||
| 1270 | |||
| 1271 | static int HIDAPI_GIP_GetHeaderLength(struct gip_header *hdr) | ||
| 1272 | { | ||
| 1273 | int len = HIDAPI_GIP_GetActualHeaderLength(hdr); | ||
| 1274 | |||
| 1275 | // Header length must be even | ||
| 1276 | return len + (len % 2); | ||
| 1277 | } | ||
| 1278 | |||
| 1279 | static void HIDAPI_GIP_EncodeHeader(struct gip_header *hdr, Uint8 *buf) | ||
| 1280 | { | ||
| 1281 | int hdr_len = 0; | ||
| 1282 | |||
| 1283 | buf[hdr_len++] = hdr->command; | ||
| 1284 | buf[hdr_len++] = hdr->options; | ||
| 1285 | buf[hdr_len++] = hdr->sequence; | ||
| 1286 | |||
| 1287 | hdr_len += EncodeVariableInt(buf + hdr_len, hdr->packet_length); | ||
| 1288 | |||
| 1289 | // Header length must be even | ||
| 1290 | if (HIDAPI_GIP_GetActualHeaderLength(hdr) % 2) { | ||
| 1291 | buf[hdr_len - 1] |= 0x80; | ||
| 1292 | buf[hdr_len++] = 0; | ||
| 1293 | } | ||
| 1294 | |||
| 1295 | if (hdr->options & GIP_OPT_CHUNK) { | ||
| 1296 | EncodeVariableInt(buf + hdr_len, hdr->chunk_offset); | ||
| 1297 | } | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | static int HIDAPI_GIP_DecodeHeader(struct gip_header *hdr, const Uint8 *data, int len) | ||
| 1301 | { | ||
| 1302 | int hdr_len = 0; | ||
| 1303 | |||
| 1304 | hdr->command = data[hdr_len++]; | ||
| 1305 | hdr->options = data[hdr_len++]; | ||
| 1306 | hdr->sequence = data[hdr_len++]; | ||
| 1307 | hdr->packet_length = 0; | ||
| 1308 | hdr->chunk_offset = 0; | ||
| 1309 | |||
| 1310 | hdr_len += DecodeVariableInt(data + hdr_len, len - hdr_len, &hdr->packet_length); | ||
| 1311 | |||
| 1312 | if (hdr->options & GIP_OPT_CHUNK) { | ||
| 1313 | hdr_len += DecodeVariableInt(data + hdr_len, len - hdr_len, &hdr->chunk_offset); | ||
| 1314 | } | ||
| 1315 | return hdr_len; | ||
| 1316 | } | ||
| 1317 | |||
| 1318 | static bool HIDAPI_GIP_SendPacket(SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, const void *data) | ||
| 1319 | { | ||
| 1320 | Uint8 packet[USB_PACKET_LENGTH]; | ||
| 1321 | int hdr_len, size; | ||
| 1322 | |||
| 1323 | hdr_len = HIDAPI_GIP_GetHeaderLength(hdr); | ||
| 1324 | size = (hdr_len + hdr->packet_length); | ||
| 1325 | if (size > sizeof(packet)) { | ||
| 1326 | SDL_SetError("Couldn't send GIP packet, size (%d) too large", size); | ||
| 1327 | return false; | ||
| 1328 | } | ||
| 1329 | |||
| 1330 | if (!hdr->sequence) { | ||
| 1331 | hdr->sequence = GetNextPacketSequence(ctx); | ||
| 1332 | } | ||
| 1333 | |||
| 1334 | HIDAPI_GIP_EncodeHeader(hdr, packet); | ||
| 1335 | if (data) { | ||
| 1336 | SDL_memcpy(&packet[hdr_len], data, hdr->packet_length); | ||
| 1337 | } | ||
| 1338 | |||
| 1339 | if (!SendProtocolPacket(ctx, packet, size)) { | ||
| 1340 | SDL_SetError("Couldn't send protocol packet"); | ||
| 1341 | return false; | ||
| 1342 | } | ||
| 1343 | return true; | ||
| 1344 | } | ||
| 1345 | |||
| 1346 | static bool HIDAPI_GIP_AcknowledgePacket(SDL_DriverXboxOne_Context *ctx, struct gip_header *ack) | ||
| 1347 | { | ||
| 1348 | if (XBOX_ONE_DRIVER_ACTIVE) { | ||
| 1349 | // The driver is taking care of acks | ||
| 1350 | return true; | ||
| 1351 | } else { | ||
| 1352 | struct gip_header hdr; | ||
| 1353 | struct gip_pkt_acknowledge pkt; | ||
| 1354 | |||
| 1355 | SDL_zero(hdr); | ||
| 1356 | hdr.command = GIP_CMD_ACKNOWLEDGE; | ||
| 1357 | hdr.options = GIP_OPT_INTERNAL; | ||
| 1358 | hdr.sequence = ack->sequence; | ||
| 1359 | hdr.packet_length = sizeof(pkt); | ||
| 1360 | |||
| 1361 | SDL_zero(pkt); | ||
| 1362 | pkt.command = ack->command; | ||
| 1363 | pkt.options = GIP_OPT_INTERNAL; | ||
| 1364 | pkt.length = SDL_Swap16LE((Uint16)(ack->chunk_offset + ack->packet_length)); | ||
| 1365 | |||
| 1366 | if ((ack->options & GIP_OPT_CHUNK) && ctx->chunk_buffer) { | ||
| 1367 | pkt.remaining = SDL_Swap16LE((Uint16)(ctx->chunk_length - pkt.length)); | ||
| 1368 | } | ||
| 1369 | |||
| 1370 | return HIDAPI_GIP_SendPacket(ctx, &hdr, &pkt); | ||
| 1371 | } | ||
| 1372 | } | ||
| 1373 | |||
| 1374 | static bool HIDAPI_GIP_DispatchPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data, Uint32 size) | ||
| 1375 | { | ||
| 1376 | if ((hdr->options & 0x0F) != 0) { | ||
| 1377 | // This is a packet for a device plugged into the controller, skip it | ||
| 1378 | return true; | ||
| 1379 | } | ||
| 1380 | |||
| 1381 | if (hdr->options & GIP_OPT_INTERNAL) { | ||
| 1382 | switch (hdr->command) { | ||
| 1383 | case GIP_CMD_ACKNOWLEDGE: | ||
| 1384 | // Ignore this packet | ||
| 1385 | break; | ||
| 1386 | case GIP_CMD_ANNOUNCE: | ||
| 1387 | // Controller is connected and waiting for initialization | ||
| 1388 | /* The data bytes are: | ||
| 1389 | 0x02 0x20 NN 0x1c, where NN is the packet sequence | ||
| 1390 | then 6 bytes of wireless MAC address | ||
| 1391 | then 2 bytes padding | ||
| 1392 | then 16-bit VID | ||
| 1393 | then 16-bit PID | ||
| 1394 | then 16-bit firmware version quartet AA.BB.CC.DD | ||
| 1395 | e.g. 0x05 0x00 0x05 0x00 0x51 0x0a 0x00 0x00 | ||
| 1396 | is firmware version 5.5.2641.0, and product version 0x0505 = 1285 | ||
| 1397 | then 8 bytes of unknown data | ||
| 1398 | */ | ||
| 1399 | #ifdef DEBUG_JOYSTICK | ||
| 1400 | SDL_Log("Controller announce after %llu ms", (SDL_GetTicks() - ctx->start_time)); | ||
| 1401 | #endif | ||
| 1402 | SetInitState(ctx, XBOX_ONE_INIT_STATE_ANNOUNCED); | ||
| 1403 | break; | ||
| 1404 | case GIP_CMD_STATUS: | ||
| 1405 | // Controller status update | ||
| 1406 | HIDAPI_DriverXboxOne_HandleStatusPacket(ctx, data, size); | ||
| 1407 | break; | ||
| 1408 | case GIP_CMD_IDENTIFY: | ||
| 1409 | #ifdef DEBUG_JOYSTICK | ||
| 1410 | SDL_Log("Identification request completed after %llu ms", (SDL_GetTicks() - ctx->send_time)); | ||
| 1411 | #endif | ||
| 1412 | #ifdef DEBUG_XBOX_PROTOCOL | ||
| 1413 | HIDAPI_DumpPacket("Xbox One identification data: size = %d", data, size); | ||
| 1414 | #endif | ||
| 1415 | SetInitState(ctx, XBOX_ONE_INIT_STATE_STARTUP); | ||
| 1416 | break; | ||
| 1417 | case GIP_CMD_POWER: | ||
| 1418 | // Ignore this packet | ||
| 1419 | break; | ||
| 1420 | case GIP_CMD_AUTHENTICATE: | ||
| 1421 | // Ignore this packet | ||
| 1422 | break; | ||
| 1423 | case GIP_CMD_VIRTUAL_KEY: | ||
| 1424 | if (!joystick) { | ||
| 1425 | break; | ||
| 1426 | } | ||
| 1427 | HIDAPI_DriverXboxOne_HandleModePacket(joystick, ctx, data, size); | ||
| 1428 | break; | ||
| 1429 | case GIP_CMD_SERIAL_NUMBER: | ||
| 1430 | /* If the packet starts with this: | ||
| 1431 | 0x1E 0x30 0x00 0x10 0x04 0x00 | ||
| 1432 | then the next 14 bytes are the controller serial number | ||
| 1433 | e.g. 0x30 0x39 0x37 0x31 0x32 0x33 0x33 0x32 0x33 0x35 0x34 0x30 0x33 0x36 | ||
| 1434 | is serial number "3039373132333332333534303336" | ||
| 1435 | |||
| 1436 | The controller sends that in response to this request: | ||
| 1437 | 0x1E 0x20 0x00 0x01 0x04 | ||
| 1438 | */ | ||
| 1439 | HIDAPI_DriverXboxOne_HandleSerialIDPacket(ctx, data, size); | ||
| 1440 | break; | ||
| 1441 | default: | ||
| 1442 | #ifdef DEBUG_JOYSTICK | ||
| 1443 | SDL_Log("Unknown Xbox One packet: 0x%.2x", hdr->command); | ||
| 1444 | #endif | ||
| 1445 | break; | ||
| 1446 | } | ||
| 1447 | } else { | ||
| 1448 | switch (hdr->command) { | ||
| 1449 | case GIP_CMD_INPUT: | ||
| 1450 | if (ctx->init_state < XBOX_ONE_INIT_STATE_COMPLETE) { | ||
| 1451 | SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE); | ||
| 1452 | |||
| 1453 | // Ignore the first input, it may be spurious | ||
| 1454 | #ifdef DEBUG_JOYSTICK | ||
| 1455 | SDL_Log("Controller ignoring spurious input"); | ||
| 1456 | #endif | ||
| 1457 | break; | ||
| 1458 | } | ||
| 1459 | if (!joystick) { | ||
| 1460 | break; | ||
| 1461 | } | ||
| 1462 | HIDAPI_DriverXboxOne_HandleStatePacket(joystick, ctx, data, size); | ||
| 1463 | break; | ||
| 1464 | case GIP_CMD_UNMAPPED_STATE: | ||
| 1465 | if (!joystick) { | ||
| 1466 | break; | ||
| 1467 | } | ||
| 1468 | HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(joystick, ctx, data, size); | ||
| 1469 | break; | ||
| 1470 | default: | ||
| 1471 | #ifdef DEBUG_JOYSTICK | ||
| 1472 | SDL_Log("Unknown Xbox One packet: 0x%.2x", hdr->command); | ||
| 1473 | #endif | ||
| 1474 | break; | ||
| 1475 | } | ||
| 1476 | } | ||
| 1477 | return true; | ||
| 1478 | } | ||
| 1479 | |||
| 1480 | static void HIDAPI_GIP_DestroyChunkBuffer(SDL_DriverXboxOne_Context *ctx) | ||
| 1481 | { | ||
| 1482 | if (ctx->chunk_buffer) { | ||
| 1483 | SDL_free(ctx->chunk_buffer); | ||
| 1484 | ctx->chunk_buffer = NULL; | ||
| 1485 | ctx->chunk_length = 0; | ||
| 1486 | } | ||
| 1487 | } | ||
| 1488 | |||
| 1489 | static bool HIDAPI_GIP_CreateChunkBuffer(SDL_DriverXboxOne_Context *ctx, Uint32 size) | ||
| 1490 | { | ||
| 1491 | HIDAPI_GIP_DestroyChunkBuffer(ctx); | ||
| 1492 | |||
| 1493 | ctx->chunk_buffer = (Uint8 *)SDL_malloc(size); | ||
| 1494 | if (ctx->chunk_buffer) { | ||
| 1495 | ctx->chunk_length = size; | ||
| 1496 | return true; | ||
| 1497 | } else { | ||
| 1498 | return false; | ||
| 1499 | } | ||
| 1500 | } | ||
| 1501 | |||
| 1502 | static bool HIDAPI_GIP_ProcessPacketChunked(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data) | ||
| 1503 | { | ||
| 1504 | bool result; | ||
| 1505 | |||
| 1506 | if (!ctx->chunk_buffer) { | ||
| 1507 | return false; | ||
| 1508 | } | ||
| 1509 | |||
| 1510 | if ((hdr->chunk_offset + hdr->packet_length) > ctx->chunk_length) { | ||
| 1511 | return false; | ||
| 1512 | } | ||
| 1513 | |||
| 1514 | if (hdr->packet_length) { | ||
| 1515 | SDL_memcpy(ctx->chunk_buffer + hdr->chunk_offset, data, hdr->packet_length); | ||
| 1516 | return true; | ||
| 1517 | } | ||
| 1518 | |||
| 1519 | result = HIDAPI_GIP_DispatchPacket(joystick, ctx, hdr, ctx->chunk_buffer, ctx->chunk_length); | ||
| 1520 | |||
| 1521 | HIDAPI_GIP_DestroyChunkBuffer(ctx); | ||
| 1522 | |||
| 1523 | return result; | ||
| 1524 | } | ||
| 1525 | |||
| 1526 | static bool HIDAPI_GIP_ProcessPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data) | ||
| 1527 | { | ||
| 1528 | if (hdr->options & GIP_OPT_CHUNK_START) { | ||
| 1529 | if (!HIDAPI_GIP_CreateChunkBuffer(ctx, hdr->chunk_offset)) { | ||
| 1530 | return false; | ||
| 1531 | } | ||
| 1532 | ctx->chunk_length = hdr->chunk_offset; | ||
| 1533 | |||
| 1534 | hdr->chunk_offset = 0; | ||
| 1535 | } | ||
| 1536 | |||
| 1537 | if (hdr->options & GIP_OPT_ACKNOWLEDGE) { | ||
| 1538 | if (!HIDAPI_GIP_AcknowledgePacket(ctx, hdr)) { | ||
| 1539 | return false; | ||
| 1540 | } | ||
| 1541 | } | ||
| 1542 | |||
| 1543 | if (hdr->options & GIP_OPT_CHUNK) { | ||
| 1544 | return HIDAPI_GIP_ProcessPacketChunked(joystick, ctx, hdr, data); | ||
| 1545 | } else { | ||
| 1546 | return HIDAPI_GIP_DispatchPacket(joystick, ctx, hdr, data, hdr->packet_length); | ||
| 1547 | } | ||
| 1548 | } | ||
| 1549 | |||
| 1550 | static bool HIDAPI_GIP_ProcessData(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) | ||
| 1551 | { | ||
| 1552 | struct gip_header hdr; | ||
| 1553 | int hdr_len; | ||
| 1554 | |||
| 1555 | while (size > GIP_HEADER_MIN_LENGTH) { | ||
| 1556 | hdr_len = HIDAPI_GIP_DecodeHeader(&hdr, data, size); | ||
| 1557 | if ((hdr_len + hdr.packet_length) > (Uint32)size) { | ||
| 1558 | // On macOS we get a shortened version of the real report | ||
| 1559 | hdr.packet_length = (Uint32)(size - hdr_len); | ||
| 1560 | } | ||
| 1561 | |||
| 1562 | if (!HIDAPI_GIP_ProcessPacket(joystick, ctx, &hdr, data + hdr_len)) { | ||
| 1563 | return false; | ||
| 1564 | } | ||
| 1565 | |||
| 1566 | data += hdr_len + hdr.packet_length; | ||
| 1567 | size -= hdr_len + hdr.packet_length; | ||
| 1568 | } | ||
| 1569 | return true; | ||
| 1570 | } | ||
| 1571 | |||
| 1572 | static bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device) | ||
| 1573 | { | ||
| 1574 | SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; | ||
| 1575 | SDL_Joystick *joystick = NULL; | ||
| 1576 | Uint8 data[USB_PACKET_LENGTH]; | ||
| 1577 | int size; | ||
| 1578 | |||
| 1579 | if (device->num_joysticks > 0) { | ||
| 1580 | joystick = SDL_GetJoystickFromID(device->joysticks[0]); | ||
| 1581 | } else { | ||
| 1582 | return false; | ||
| 1583 | } | ||
| 1584 | |||
| 1585 | while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { | ||
| 1586 | #ifdef DEBUG_XBOX_PROTOCOL | ||
| 1587 | HIDAPI_DumpPacket("Xbox One packet: size = %d", data, size); | ||
| 1588 | #endif | ||
| 1589 | if (device->is_bluetooth) { | ||
| 1590 | switch (data[0]) { | ||
| 1591 | case 0x01: | ||
| 1592 | if (!joystick) { | ||
| 1593 | break; | ||
| 1594 | } | ||
| 1595 | if (size >= 16) { | ||
| 1596 | HIDAPI_DriverXboxOneBluetooth_HandleStatePacket(joystick, ctx, data, size); | ||
| 1597 | } else { | ||
| 1598 | #ifdef DEBUG_JOYSTICK | ||
| 1599 | SDL_Log("Unknown Xbox One Bluetooth packet size: %d", size); | ||
| 1600 | #endif | ||
| 1601 | } | ||
| 1602 | break; | ||
| 1603 | case 0x02: | ||
| 1604 | if (!joystick) { | ||
| 1605 | break; | ||
| 1606 | } | ||
| 1607 | HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(joystick, ctx, data, size); | ||
| 1608 | break; | ||
| 1609 | case 0x04: | ||
| 1610 | if (!joystick) { | ||
| 1611 | break; | ||
| 1612 | } | ||
| 1613 | HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(joystick, ctx, data, size); | ||
| 1614 | break; | ||
| 1615 | default: | ||
| 1616 | #ifdef DEBUG_JOYSTICK | ||
| 1617 | SDL_Log("Unknown Xbox One packet: 0x%.2x", data[0]); | ||
| 1618 | #endif | ||
| 1619 | break; | ||
| 1620 | } | ||
| 1621 | } else { | ||
| 1622 | HIDAPI_GIP_ProcessData(joystick, ctx, data, size); | ||
| 1623 | } | ||
| 1624 | } | ||
| 1625 | |||
| 1626 | HIDAPI_DriverXboxOne_UpdateInitState(ctx); | ||
| 1627 | HIDAPI_DriverXboxOne_UpdateRumble(ctx); | ||
| 1628 | |||
| 1629 | if (size < 0) { | ||
| 1630 | // Read error, device is disconnected | ||
| 1631 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 1632 | } | ||
| 1633 | return (size >= 0); | ||
| 1634 | } | ||
| 1635 | |||
| 1636 | static void HIDAPI_DriverXboxOne_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 1637 | { | ||
| 1638 | SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; | ||
| 1639 | |||
| 1640 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED, | ||
| 1641 | SDL_HomeLEDHintChanged, ctx); | ||
| 1642 | } | ||
| 1643 | |||
| 1644 | static void HIDAPI_DriverXboxOne_FreeDevice(SDL_HIDAPI_Device *device) | ||
| 1645 | { | ||
| 1646 | SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; | ||
| 1647 | |||
| 1648 | HIDAPI_GIP_DestroyChunkBuffer(ctx); | ||
| 1649 | } | ||
| 1650 | |||
| 1651 | SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne = { | ||
| 1652 | SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, | ||
| 1653 | true, | ||
| 1654 | HIDAPI_DriverXboxOne_RegisterHints, | ||
| 1655 | HIDAPI_DriverXboxOne_UnregisterHints, | ||
| 1656 | HIDAPI_DriverXboxOne_IsEnabled, | ||
| 1657 | HIDAPI_DriverXboxOne_IsSupportedDevice, | ||
| 1658 | HIDAPI_DriverXboxOne_InitDevice, | ||
| 1659 | HIDAPI_DriverXboxOne_GetDevicePlayerIndex, | ||
| 1660 | HIDAPI_DriverXboxOne_SetDevicePlayerIndex, | ||
| 1661 | HIDAPI_DriverXboxOne_UpdateDevice, | ||
| 1662 | HIDAPI_DriverXboxOne_OpenJoystick, | ||
| 1663 | HIDAPI_DriverXboxOne_RumbleJoystick, | ||
| 1664 | HIDAPI_DriverXboxOne_RumbleJoystickTriggers, | ||
| 1665 | HIDAPI_DriverXboxOne_GetJoystickCapabilities, | ||
| 1666 | HIDAPI_DriverXboxOne_SetJoystickLED, | ||
| 1667 | HIDAPI_DriverXboxOne_SendJoystickEffect, | ||
| 1668 | HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled, | ||
| 1669 | HIDAPI_DriverXboxOne_CloseJoystick, | ||
| 1670 | HIDAPI_DriverXboxOne_FreeDevice, | ||
| 1671 | }; | ||
| 1672 | |||
| 1673 | #endif // SDL_JOYSTICK_HIDAPI_XBOXONE | ||
| 1674 | |||
| 1675 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_JOYSTICK_HIDAPI | ||
| 24 | |||
| 25 | #include "../SDL_sysjoystick.h" | ||
| 26 | #include "SDL_hidapijoystick_c.h" | ||
| 27 | #include "SDL_hidapi_rumble.h" | ||
| 28 | #include "../../SDL_hints_c.h" | ||
| 29 | |||
| 30 | #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) | ||
| 31 | #include "../windows/SDL_rawinputjoystick_c.h" | ||
| 32 | #endif | ||
| 33 | |||
| 34 | |||
| 35 | struct joystick_hwdata | ||
| 36 | { | ||
| 37 | SDL_HIDAPI_Device *device; | ||
| 38 | }; | ||
| 39 | |||
| 40 | static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { | ||
| 41 | #ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE | ||
| 42 | &SDL_HIDAPI_DriverGameCube, | ||
| 43 | #endif | ||
| 44 | #ifdef SDL_JOYSTICK_HIDAPI_LUNA | ||
| 45 | &SDL_HIDAPI_DriverLuna, | ||
| 46 | #endif | ||
| 47 | #ifdef SDL_JOYSTICK_HIDAPI_SHIELD | ||
| 48 | &SDL_HIDAPI_DriverShield, | ||
| 49 | #endif | ||
| 50 | #ifdef SDL_JOYSTICK_HIDAPI_PS3 | ||
| 51 | &SDL_HIDAPI_DriverPS3, | ||
| 52 | &SDL_HIDAPI_DriverPS3ThirdParty, | ||
| 53 | &SDL_HIDAPI_DriverPS3SonySixaxis, | ||
| 54 | #endif | ||
| 55 | #ifdef SDL_JOYSTICK_HIDAPI_PS4 | ||
| 56 | &SDL_HIDAPI_DriverPS4, | ||
| 57 | #endif | ||
| 58 | #ifdef SDL_JOYSTICK_HIDAPI_PS5 | ||
| 59 | &SDL_HIDAPI_DriverPS5, | ||
| 60 | #endif | ||
| 61 | #ifdef SDL_JOYSTICK_HIDAPI_STADIA | ||
| 62 | &SDL_HIDAPI_DriverStadia, | ||
| 63 | #endif | ||
| 64 | #ifdef SDL_JOYSTICK_HIDAPI_STEAM | ||
| 65 | &SDL_HIDAPI_DriverSteam, | ||
| 66 | #endif | ||
| 67 | #ifdef SDL_JOYSTICK_HIDAPI_STEAM_HORI | ||
| 68 | &SDL_HIDAPI_DriverSteamHori, | ||
| 69 | #endif | ||
| 70 | #ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK | ||
| 71 | &SDL_HIDAPI_DriverSteamDeck, | ||
| 72 | #endif | ||
| 73 | #ifdef SDL_JOYSTICK_HIDAPI_SWITCH | ||
| 74 | &SDL_HIDAPI_DriverNintendoClassic, | ||
| 75 | &SDL_HIDAPI_DriverJoyCons, | ||
| 76 | &SDL_HIDAPI_DriverSwitch, | ||
| 77 | #endif | ||
| 78 | #ifdef SDL_JOYSTICK_HIDAPI_WII | ||
| 79 | &SDL_HIDAPI_DriverWii, | ||
| 80 | #endif | ||
| 81 | #ifdef SDL_JOYSTICK_HIDAPI_XBOX360 | ||
| 82 | &SDL_HIDAPI_DriverXbox360, | ||
| 83 | &SDL_HIDAPI_DriverXbox360W, | ||
| 84 | #endif | ||
| 85 | #ifdef SDL_JOYSTICK_HIDAPI_XBOXONE | ||
| 86 | &SDL_HIDAPI_DriverXboxOne, | ||
| 87 | #endif | ||
| 88 | }; | ||
| 89 | static int SDL_HIDAPI_numdrivers = 0; | ||
| 90 | static SDL_AtomicInt SDL_HIDAPI_updating_devices; | ||
| 91 | static bool SDL_HIDAPI_hints_changed = false; | ||
| 92 | static Uint32 SDL_HIDAPI_change_count = 0; | ||
| 93 | static SDL_HIDAPI_Device *SDL_HIDAPI_devices SDL_GUARDED_BY(SDL_joystick_lock); | ||
| 94 | static int SDL_HIDAPI_numjoysticks = 0; | ||
| 95 | static bool SDL_HIDAPI_combine_joycons = true; | ||
| 96 | static bool initialized = false; | ||
| 97 | static bool shutting_down = false; | ||
| 98 | |||
| 99 | static char *HIDAPI_ConvertString(const wchar_t *wide_string) | ||
| 100 | { | ||
| 101 | char *string = NULL; | ||
| 102 | |||
| 103 | if (wide_string) { | ||
| 104 | string = SDL_iconv_string("UTF-8", "WCHAR_T", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t)); | ||
| 105 | if (!string) { | ||
| 106 | switch (sizeof(wchar_t)) { | ||
| 107 | case 2: | ||
| 108 | string = SDL_iconv_string("UTF-8", "UCS-2-INTERNAL", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t)); | ||
| 109 | break; | ||
| 110 | case 4: | ||
| 111 | string = SDL_iconv_string("UTF-8", "UCS-4-INTERNAL", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t)); | ||
| 112 | break; | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | return string; | ||
| 117 | } | ||
| 118 | |||
| 119 | void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size) | ||
| 120 | { | ||
| 121 | int i; | ||
| 122 | char *buffer; | ||
| 123 | size_t length = SDL_strlen(prefix) + 11 * (size / 8) + (5 * size * 2) + 1 + 1; | ||
| 124 | int start = 0, amount = size; | ||
| 125 | size_t current_len; | ||
| 126 | |||
| 127 | buffer = (char *)SDL_malloc(length); | ||
| 128 | current_len = SDL_snprintf(buffer, length, prefix, size); | ||
| 129 | for (i = start; i < start + amount; ++i) { | ||
| 130 | if ((i % 8) == 0) { | ||
| 131 | current_len += SDL_snprintf(&buffer[current_len], length - current_len, "\n%.2d: ", i); | ||
| 132 | } | ||
| 133 | current_len += SDL_snprintf(&buffer[current_len], length - current_len, " 0x%.2x", data[i]); | ||
| 134 | } | ||
| 135 | SDL_strlcat(buffer, "\n", length); | ||
| 136 | SDL_Log("%s", buffer); | ||
| 137 | SDL_free(buffer); | ||
| 138 | } | ||
| 139 | |||
| 140 | bool HIDAPI_SupportsPlaystationDetection(Uint16 vendor, Uint16 product) | ||
| 141 | { | ||
| 142 | /* If we already know the controller is a different type, don't try to detect it. | ||
| 143 | * This fixes a hang with the HORIPAD for Nintendo Switch (0x0f0d/0x00c1) | ||
| 144 | */ | ||
| 145 | if (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, false) != SDL_GAMEPAD_TYPE_STANDARD) { | ||
| 146 | return false; | ||
| 147 | } | ||
| 148 | |||
| 149 | switch (vendor) { | ||
| 150 | case USB_VENDOR_DRAGONRISE: | ||
| 151 | return true; | ||
| 152 | case USB_VENDOR_HORI: | ||
| 153 | return true; | ||
| 154 | case USB_VENDOR_LOGITECH: | ||
| 155 | /* Most Logitech devices are not PlayStation controllers, and some of them | ||
| 156 | * lock up or reset when we send them the Sony third-party query feature | ||
| 157 | * report, so don't include that vendor here. Instead add devices as | ||
| 158 | * appropriate to controller_list.h | ||
| 159 | */ | ||
| 160 | return false; | ||
| 161 | case USB_VENDOR_MADCATZ: | ||
| 162 | if (product == USB_PRODUCT_MADCATZ_SAITEK_SIDE_PANEL_CONTROL_DECK) { | ||
| 163 | // This is not a Playstation compatible device | ||
| 164 | return false; | ||
| 165 | } | ||
| 166 | return true; | ||
| 167 | case USB_VENDOR_MAYFLASH: | ||
| 168 | return true; | ||
| 169 | case USB_VENDOR_NACON: | ||
| 170 | case USB_VENDOR_NACON_ALT: | ||
| 171 | return true; | ||
| 172 | case USB_VENDOR_PDP: | ||
| 173 | return true; | ||
| 174 | case USB_VENDOR_POWERA: | ||
| 175 | return true; | ||
| 176 | case USB_VENDOR_POWERA_ALT: | ||
| 177 | return true; | ||
| 178 | case USB_VENDOR_QANBA: | ||
| 179 | return true; | ||
| 180 | case USB_VENDOR_RAZER: | ||
| 181 | /* Most Razer devices are not PlayStation controllers, and some of them | ||
| 182 | * lock up or reset when we send them the Sony third-party query feature | ||
| 183 | * report, so don't include that vendor here. Instead add devices as | ||
| 184 | * appropriate to controller_list.h | ||
| 185 | * | ||
| 186 | * Reference: https://github.com/libsdl-org/SDL/issues/6733 | ||
| 187 | * https://github.com/libsdl-org/SDL/issues/6799 | ||
| 188 | */ | ||
| 189 | return false; | ||
| 190 | case USB_VENDOR_SHANWAN: | ||
| 191 | return true; | ||
| 192 | case USB_VENDOR_SHANWAN_ALT: | ||
| 193 | return true; | ||
| 194 | case USB_VENDOR_THRUSTMASTER: | ||
| 195 | /* Most of these are wheels, don't have the full set of effects, and | ||
| 196 | * at least in the case of the T248 and T300 RS, the hid-tmff2 driver | ||
| 197 | * puts them in a non-standard report mode and they can't be read. | ||
| 198 | * | ||
| 199 | * If these should use the HIDAPI driver, add them to controller_list.h | ||
| 200 | */ | ||
| 201 | return false; | ||
| 202 | case USB_VENDOR_ZEROPLUS: | ||
| 203 | return true; | ||
| 204 | case 0x7545 /* SZ-MYPOWER */: | ||
| 205 | return true; | ||
| 206 | default: | ||
| 207 | return false; | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | float HIDAPI_RemapVal(float val, float val_min, float val_max, float output_min, float output_max) | ||
| 212 | { | ||
| 213 | return output_min + (output_max - output_min) * (val - val_min) / (val_max - val_min); | ||
| 214 | } | ||
| 215 | |||
| 216 | static void HIDAPI_UpdateDeviceList(void); | ||
| 217 | static void HIDAPI_JoystickClose(SDL_Joystick *joystick); | ||
| 218 | |||
| 219 | static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, Uint16 vendor, Uint16 product, int interface_number, int interface_class, int interface_subclass, int interface_protocol) | ||
| 220 | { | ||
| 221 | static const int LIBUSB_CLASS_VENDOR_SPEC = 0xFF; | ||
| 222 | static const int XB360_IFACE_SUBCLASS = 93; | ||
| 223 | static const int XB360_IFACE_PROTOCOL = 1; // Wired | ||
| 224 | static const int XB360W_IFACE_PROTOCOL = 129; // Wireless | ||
| 225 | static const int XBONE_IFACE_SUBCLASS = 71; | ||
| 226 | static const int XBONE_IFACE_PROTOCOL = 208; | ||
| 227 | |||
| 228 | SDL_GamepadType type = SDL_GAMEPAD_TYPE_STANDARD; | ||
| 229 | |||
| 230 | // This code should match the checks in libusb/hid.c and HIDDeviceManager.java | ||
| 231 | if (interface_class == LIBUSB_CLASS_VENDOR_SPEC && | ||
| 232 | interface_subclass == XB360_IFACE_SUBCLASS && | ||
| 233 | (interface_protocol == XB360_IFACE_PROTOCOL || | ||
| 234 | interface_protocol == XB360W_IFACE_PROTOCOL)) { | ||
| 235 | |||
| 236 | static const int SUPPORTED_VENDORS[] = { | ||
| 237 | 0x0079, // GPD Win 2 | ||
| 238 | 0x044f, // Thrustmaster | ||
| 239 | 0x045e, // Microsoft | ||
| 240 | 0x046d, // Logitech | ||
| 241 | 0x056e, // Elecom | ||
| 242 | 0x06a3, // Saitek | ||
| 243 | 0x0738, // Mad Catz | ||
| 244 | 0x07ff, // Mad Catz | ||
| 245 | 0x0e6f, // PDP | ||
| 246 | 0x0f0d, // Hori | ||
| 247 | 0x1038, // SteelSeries | ||
| 248 | 0x11c9, // Nacon | ||
| 249 | 0x12ab, // Unknown | ||
| 250 | 0x1430, // RedOctane | ||
| 251 | 0x146b, // BigBen | ||
| 252 | 0x1532, // Razer | ||
| 253 | 0x15e4, // Numark | ||
| 254 | 0x162e, // Joytech | ||
| 255 | 0x1689, // Razer Onza | ||
| 256 | 0x1949, // Lab126, Inc. | ||
| 257 | 0x1bad, // Harmonix | ||
| 258 | 0x20d6, // PowerA | ||
| 259 | 0x24c6, // PowerA | ||
| 260 | 0x2c22, // Qanba | ||
| 261 | 0x2dc8, // 8BitDo | ||
| 262 | 0x9886, // ASTRO Gaming | ||
| 263 | }; | ||
| 264 | |||
| 265 | int i; | ||
| 266 | for (i = 0; i < SDL_arraysize(SUPPORTED_VENDORS); ++i) { | ||
| 267 | if (vendor == SUPPORTED_VENDORS[i]) { | ||
| 268 | type = SDL_GAMEPAD_TYPE_XBOX360; | ||
| 269 | break; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | } | ||
| 273 | |||
| 274 | if (interface_number == 0 && | ||
| 275 | interface_class == LIBUSB_CLASS_VENDOR_SPEC && | ||
| 276 | interface_subclass == XBONE_IFACE_SUBCLASS && | ||
| 277 | interface_protocol == XBONE_IFACE_PROTOCOL) { | ||
| 278 | |||
| 279 | static const int SUPPORTED_VENDORS[] = { | ||
| 280 | 0x03f0, // HP | ||
| 281 | 0x044f, // Thrustmaster | ||
| 282 | 0x045e, // Microsoft | ||
| 283 | 0x0738, // Mad Catz | ||
| 284 | 0x0b05, // ASUS | ||
| 285 | 0x0e6f, // PDP | ||
| 286 | 0x0f0d, // Hori | ||
| 287 | 0x10f5, // Turtle Beach | ||
| 288 | 0x1532, // Razer | ||
| 289 | 0x20d6, // PowerA | ||
| 290 | 0x24c6, // PowerA | ||
| 291 | 0x2dc8, // 8BitDo | ||
| 292 | 0x2e24, // Hyperkin | ||
| 293 | 0x3537, // GameSir | ||
| 294 | }; | ||
| 295 | |||
| 296 | int i; | ||
| 297 | for (i = 0; i < SDL_arraysize(SUPPORTED_VENDORS); ++i) { | ||
| 298 | if (vendor == SUPPORTED_VENDORS[i]) { | ||
| 299 | type = SDL_GAMEPAD_TYPE_XBOXONE; | ||
| 300 | break; | ||
| 301 | } | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | if (type == SDL_GAMEPAD_TYPE_STANDARD) { | ||
| 306 | type = SDL_GetGamepadTypeFromVIDPID(vendor, product, name, false); | ||
| 307 | } | ||
| 308 | return type; | ||
| 309 | } | ||
| 310 | |||
| 311 | static bool HIDAPI_IsDeviceSupported(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) | ||
| 312 | { | ||
| 313 | int i; | ||
| 314 | SDL_GamepadType type = SDL_GetJoystickGameControllerProtocol(name, vendor_id, product_id, -1, 0, 0, 0); | ||
| 315 | |||
| 316 | for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { | ||
| 317 | SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; | ||
| 318 | if (driver->enabled && driver->IsSupportedDevice(NULL, name, type, vendor_id, product_id, version, -1, 0, 0, 0)) { | ||
| 319 | return true; | ||
| 320 | } | ||
| 321 | } | ||
| 322 | return false; | ||
| 323 | } | ||
| 324 | |||
| 325 | static SDL_HIDAPI_DeviceDriver *HIDAPI_GetDeviceDriver(SDL_HIDAPI_Device *device) | ||
| 326 | { | ||
| 327 | const Uint16 USAGE_PAGE_GENERIC_DESKTOP = 0x0001; | ||
| 328 | const Uint16 USAGE_JOYSTICK = 0x0004; | ||
| 329 | const Uint16 USAGE_GAMEPAD = 0x0005; | ||
| 330 | const Uint16 USAGE_MULTIAXISCONTROLLER = 0x0008; | ||
| 331 | int i; | ||
| 332 | |||
| 333 | if (device->num_children > 0) { | ||
| 334 | return &SDL_HIDAPI_DriverCombined; | ||
| 335 | } | ||
| 336 | |||
| 337 | if (SDL_ShouldIgnoreJoystick(device->vendor_id, device->product_id, device->version, device->name)) { | ||
| 338 | return NULL; | ||
| 339 | } | ||
| 340 | |||
| 341 | if (device->vendor_id != USB_VENDOR_VALVE) { | ||
| 342 | if (device->usage_page && device->usage_page != USAGE_PAGE_GENERIC_DESKTOP) { | ||
| 343 | return NULL; | ||
| 344 | } | ||
| 345 | if (device->usage && device->usage != USAGE_JOYSTICK && device->usage != USAGE_GAMEPAD && device->usage != USAGE_MULTIAXISCONTROLLER) { | ||
| 346 | return NULL; | ||
| 347 | } | ||
| 348 | } | ||
| 349 | |||
| 350 | for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { | ||
| 351 | SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; | ||
| 352 | 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)) { | ||
| 353 | return driver; | ||
| 354 | } | ||
| 355 | } | ||
| 356 | return NULL; | ||
| 357 | } | ||
| 358 | |||
| 359 | static SDL_HIDAPI_Device *HIDAPI_GetDeviceByIndex(int device_index, SDL_JoystickID *pJoystickID) | ||
| 360 | { | ||
| 361 | SDL_HIDAPI_Device *device; | ||
| 362 | |||
| 363 | SDL_AssertJoysticksLocked(); | ||
| 364 | |||
| 365 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 366 | if (device->parent || device->broken) { | ||
| 367 | continue; | ||
| 368 | } | ||
| 369 | if (device->driver) { | ||
| 370 | if (device_index < device->num_joysticks) { | ||
| 371 | if (pJoystickID) { | ||
| 372 | *pJoystickID = device->joysticks[device_index]; | ||
| 373 | } | ||
| 374 | return device; | ||
| 375 | } | ||
| 376 | device_index -= device->num_joysticks; | ||
| 377 | } | ||
| 378 | } | ||
| 379 | return NULL; | ||
| 380 | } | ||
| 381 | |||
| 382 | static SDL_HIDAPI_Device *HIDAPI_GetJoystickByInfo(const char *path, Uint16 vendor_id, Uint16 product_id) | ||
| 383 | { | ||
| 384 | SDL_HIDAPI_Device *device; | ||
| 385 | |||
| 386 | SDL_AssertJoysticksLocked(); | ||
| 387 | |||
| 388 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 389 | if (device->vendor_id == vendor_id && device->product_id == product_id && | ||
| 390 | SDL_strcmp(device->path, path) == 0) { | ||
| 391 | break; | ||
| 392 | } | ||
| 393 | } | ||
| 394 | return device; | ||
| 395 | } | ||
| 396 | |||
| 397 | static void HIDAPI_CleanupDeviceDriver(SDL_HIDAPI_Device *device) | ||
| 398 | { | ||
| 399 | if (!device->driver) { | ||
| 400 | return; // Already cleaned up | ||
| 401 | } | ||
| 402 | |||
| 403 | // Disconnect any joysticks | ||
| 404 | while (device->num_joysticks && device->joysticks) { | ||
| 405 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 406 | } | ||
| 407 | |||
| 408 | device->driver->FreeDevice(device); | ||
| 409 | device->driver = NULL; | ||
| 410 | |||
| 411 | SDL_LockMutex(device->dev_lock); | ||
| 412 | { | ||
| 413 | if (device->dev) { | ||
| 414 | SDL_hid_close(device->dev); | ||
| 415 | device->dev = NULL; | ||
| 416 | } | ||
| 417 | |||
| 418 | if (device->context) { | ||
| 419 | SDL_free(device->context); | ||
| 420 | device->context = NULL; | ||
| 421 | } | ||
| 422 | } | ||
| 423 | SDL_UnlockMutex(device->dev_lock); | ||
| 424 | } | ||
| 425 | |||
| 426 | 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 | ||
| 427 | { | ||
| 428 | *removed = false; | ||
| 429 | |||
| 430 | if (device->driver) { | ||
| 431 | bool enabled; | ||
| 432 | |||
| 433 | if (device->vendor_id == USB_VENDOR_NINTENDO && device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) { | ||
| 434 | enabled = SDL_HIDAPI_combine_joycons; | ||
| 435 | } else { | ||
| 436 | enabled = device->driver->enabled; | ||
| 437 | } | ||
| 438 | if (device->children) { | ||
| 439 | int i; | ||
| 440 | |||
| 441 | for (i = 0; i < device->num_children; ++i) { | ||
| 442 | SDL_HIDAPI_Device *child = device->children[i]; | ||
| 443 | if (!child->driver || !child->driver->enabled) { | ||
| 444 | enabled = false; | ||
| 445 | break; | ||
| 446 | } | ||
| 447 | } | ||
| 448 | } | ||
| 449 | if (!enabled) { | ||
| 450 | HIDAPI_CleanupDeviceDriver(device); | ||
| 451 | } | ||
| 452 | return; // Already setup | ||
| 453 | } | ||
| 454 | |||
| 455 | if (HIDAPI_GetDeviceDriver(device)) { | ||
| 456 | // We might have a device driver for this device, try opening it and see | ||
| 457 | if (device->num_children == 0) { | ||
| 458 | SDL_hid_device *dev; | ||
| 459 | |||
| 460 | // Wait a little bit for the device to initialize | ||
| 461 | SDL_Delay(10); | ||
| 462 | |||
| 463 | dev = SDL_hid_open_path(device->path); | ||
| 464 | |||
| 465 | if (dev == NULL) { | ||
| 466 | SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, | ||
| 467 | "HIDAPI_SetupDeviceDriver() couldn't open %s: %s", | ||
| 468 | device->path, SDL_GetError()); | ||
| 469 | return; | ||
| 470 | } | ||
| 471 | SDL_hid_set_nonblocking(dev, 1); | ||
| 472 | |||
| 473 | device->dev = dev; | ||
| 474 | } | ||
| 475 | |||
| 476 | device->driver = HIDAPI_GetDeviceDriver(device); | ||
| 477 | |||
| 478 | // Initialize the device, which may cause a connected event | ||
| 479 | if (device->driver && !device->driver->InitDevice(device)) { | ||
| 480 | HIDAPI_CleanupDeviceDriver(device); | ||
| 481 | } | ||
| 482 | |||
| 483 | if (!device->driver && device->dev) { | ||
| 484 | // No driver claimed this device, go ahead and close it | ||
| 485 | SDL_hid_close(device->dev); | ||
| 486 | device->dev = NULL; | ||
| 487 | } | ||
| 488 | } | ||
| 489 | } | ||
| 490 | |||
| 491 | static void SDL_HIDAPI_UpdateDrivers(void) | ||
| 492 | { | ||
| 493 | int i; | ||
| 494 | SDL_HIDAPI_Device *device; | ||
| 495 | bool removed; | ||
| 496 | |||
| 497 | SDL_AssertJoysticksLocked(); | ||
| 498 | |||
| 499 | SDL_HIDAPI_numdrivers = 0; | ||
| 500 | for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { | ||
| 501 | SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; | ||
| 502 | driver->enabled = driver->IsEnabled(); | ||
| 503 | if (driver->enabled && driver != &SDL_HIDAPI_DriverCombined) { | ||
| 504 | ++SDL_HIDAPI_numdrivers; | ||
| 505 | } | ||
| 506 | } | ||
| 507 | |||
| 508 | removed = false; | ||
| 509 | do { | ||
| 510 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 511 | HIDAPI_SetupDeviceDriver(device, &removed); | ||
| 512 | if (removed) { | ||
| 513 | break; | ||
| 514 | } | ||
| 515 | } | ||
| 516 | } while (removed); | ||
| 517 | } | ||
| 518 | |||
| 519 | static void SDLCALL SDL_HIDAPIDriverHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 520 | { | ||
| 521 | if (SDL_strcmp(name, SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS) == 0) { | ||
| 522 | SDL_HIDAPI_combine_joycons = SDL_GetStringBoolean(hint, true); | ||
| 523 | } | ||
| 524 | SDL_HIDAPI_hints_changed = true; | ||
| 525 | SDL_HIDAPI_change_count = 0; | ||
| 526 | } | ||
| 527 | |||
| 528 | static bool HIDAPI_JoystickInit(void) | ||
| 529 | { | ||
| 530 | int i; | ||
| 531 | |||
| 532 | if (initialized) { | ||
| 533 | return true; | ||
| 534 | } | ||
| 535 | |||
| 536 | if (SDL_hid_init() < 0) { | ||
| 537 | return SDL_SetError("Couldn't initialize hidapi"); | ||
| 538 | } | ||
| 539 | |||
| 540 | for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { | ||
| 541 | SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; | ||
| 542 | driver->RegisterHints(SDL_HIDAPIDriverHintChanged, driver); | ||
| 543 | } | ||
| 544 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS, | ||
| 545 | SDL_HIDAPIDriverHintChanged, NULL); | ||
| 546 | SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI, | ||
| 547 | SDL_HIDAPIDriverHintChanged, NULL); | ||
| 548 | |||
| 549 | SDL_HIDAPI_change_count = SDL_hid_device_change_count(); | ||
| 550 | HIDAPI_UpdateDeviceList(); | ||
| 551 | HIDAPI_UpdateDevices(); | ||
| 552 | |||
| 553 | initialized = true; | ||
| 554 | |||
| 555 | return true; | ||
| 556 | } | ||
| 557 | |||
| 558 | static bool HIDAPI_AddJoystickInstanceToDevice(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) | ||
| 559 | { | ||
| 560 | SDL_JoystickID *joysticks = (SDL_JoystickID *)SDL_realloc(device->joysticks, (device->num_joysticks + 1) * sizeof(*device->joysticks)); | ||
| 561 | if (!joysticks) { | ||
| 562 | return false; | ||
| 563 | } | ||
| 564 | |||
| 565 | device->joysticks = joysticks; | ||
| 566 | device->joysticks[device->num_joysticks++] = joystickID; | ||
| 567 | return true; | ||
| 568 | } | ||
| 569 | |||
| 570 | static bool HIDAPI_DelJoystickInstanceFromDevice(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) | ||
| 571 | { | ||
| 572 | int i, size; | ||
| 573 | |||
| 574 | for (i = 0; i < device->num_joysticks; ++i) { | ||
| 575 | if (device->joysticks[i] == joystickID) { | ||
| 576 | size = (device->num_joysticks - i - 1) * sizeof(SDL_JoystickID); | ||
| 577 | SDL_memmove(&device->joysticks[i], &device->joysticks[i + 1], size); | ||
| 578 | --device->num_joysticks; | ||
| 579 | if (device->num_joysticks == 0) { | ||
| 580 | SDL_free(device->joysticks); | ||
| 581 | device->joysticks = NULL; | ||
| 582 | } | ||
| 583 | return true; | ||
| 584 | } | ||
| 585 | } | ||
| 586 | return false; | ||
| 587 | } | ||
| 588 | |||
| 589 | static bool HIDAPI_JoystickInstanceIsUnique(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) | ||
| 590 | { | ||
| 591 | if (device->parent && device->num_joysticks == 1 && device->parent->num_joysticks == 1 && | ||
| 592 | device->joysticks[0] == device->parent->joysticks[0]) { | ||
| 593 | return false; | ||
| 594 | } | ||
| 595 | return true; | ||
| 596 | } | ||
| 597 | |||
| 598 | void HIDAPI_SetDeviceName(SDL_HIDAPI_Device *device, const char *name) | ||
| 599 | { | ||
| 600 | if (name && *name && SDL_strcmp(name, device->name) != 0) { | ||
| 601 | SDL_free(device->name); | ||
| 602 | device->name = SDL_strdup(name); | ||
| 603 | SDL_SetJoystickGUIDCRC(&device->guid, SDL_crc16(0, name, SDL_strlen(name))); | ||
| 604 | } | ||
| 605 | } | ||
| 606 | |||
| 607 | void HIDAPI_SetDeviceProduct(SDL_HIDAPI_Device *device, Uint16 vendor_id, Uint16 product_id) | ||
| 608 | { | ||
| 609 | // Don't set the device product ID directly, or we'll constantly re-enumerate this device | ||
| 610 | device->guid = SDL_CreateJoystickGUID(device->guid.data[0], vendor_id, product_id, device->version, device->manufacturer_string, device->product_string, 'h', 0); | ||
| 611 | } | ||
| 612 | |||
| 613 | static void HIDAPI_UpdateJoystickSerial(SDL_HIDAPI_Device *device) | ||
| 614 | { | ||
| 615 | int i; | ||
| 616 | |||
| 617 | SDL_AssertJoysticksLocked(); | ||
| 618 | |||
| 619 | for (i = 0; i < device->num_joysticks; ++i) { | ||
| 620 | SDL_Joystick *joystick = SDL_GetJoystickFromID(device->joysticks[i]); | ||
| 621 | if (joystick && device->serial) { | ||
| 622 | SDL_free(joystick->serial); | ||
| 623 | joystick->serial = SDL_strdup(device->serial); | ||
| 624 | } | ||
| 625 | } | ||
| 626 | } | ||
| 627 | |||
| 628 | static bool HIDAPI_SerialIsEmpty(SDL_HIDAPI_Device *device) | ||
| 629 | { | ||
| 630 | bool all_zeroes = true; | ||
| 631 | |||
| 632 | if (device->serial) { | ||
| 633 | const char *serial = device->serial; | ||
| 634 | for (serial = device->serial; *serial; ++serial) { | ||
| 635 | if (*serial != '0') { | ||
| 636 | all_zeroes = false; | ||
| 637 | break; | ||
| 638 | } | ||
| 639 | } | ||
| 640 | } | ||
| 641 | return all_zeroes; | ||
| 642 | } | ||
| 643 | |||
| 644 | void HIDAPI_SetDeviceSerial(SDL_HIDAPI_Device *device, const char *serial) | ||
| 645 | { | ||
| 646 | if (serial && *serial && (!device->serial || SDL_strcmp(serial, device->serial) != 0)) { | ||
| 647 | SDL_free(device->serial); | ||
| 648 | device->serial = SDL_strdup(serial); | ||
| 649 | HIDAPI_UpdateJoystickSerial(device); | ||
| 650 | } | ||
| 651 | } | ||
| 652 | |||
| 653 | static int wcstrcmp(const wchar_t *str1, const char *str2) | ||
| 654 | { | ||
| 655 | int result; | ||
| 656 | |||
| 657 | while (1) { | ||
| 658 | result = (*str1 - *str2); | ||
| 659 | if (result != 0 || *str1 == 0) { | ||
| 660 | break; | ||
| 661 | } | ||
| 662 | ++str1; | ||
| 663 | ++str2; | ||
| 664 | } | ||
| 665 | return result; | ||
| 666 | } | ||
| 667 | |||
| 668 | static void HIDAPI_SetDeviceSerialW(SDL_HIDAPI_Device *device, const wchar_t *serial) | ||
| 669 | { | ||
| 670 | if (serial && *serial && (!device->serial || wcstrcmp(serial, device->serial) != 0)) { | ||
| 671 | SDL_free(device->serial); | ||
| 672 | device->serial = HIDAPI_ConvertString(serial); | ||
| 673 | HIDAPI_UpdateJoystickSerial(device); | ||
| 674 | } | ||
| 675 | } | ||
| 676 | |||
| 677 | bool HIDAPI_HasConnectedUSBDevice(const char *serial) | ||
| 678 | { | ||
| 679 | SDL_HIDAPI_Device *device; | ||
| 680 | |||
| 681 | SDL_AssertJoysticksLocked(); | ||
| 682 | |||
| 683 | if (!serial) { | ||
| 684 | return false; | ||
| 685 | } | ||
| 686 | |||
| 687 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 688 | if (!device->driver || device->broken) { | ||
| 689 | continue; | ||
| 690 | } | ||
| 691 | |||
| 692 | if (device->is_bluetooth) { | ||
| 693 | continue; | ||
| 694 | } | ||
| 695 | |||
| 696 | if (device->serial && SDL_strcmp(serial, device->serial) == 0) { | ||
| 697 | return true; | ||
| 698 | } | ||
| 699 | } | ||
| 700 | return false; | ||
| 701 | } | ||
| 702 | |||
| 703 | void HIDAPI_DisconnectBluetoothDevice(const char *serial) | ||
| 704 | { | ||
| 705 | SDL_HIDAPI_Device *device; | ||
| 706 | |||
| 707 | SDL_AssertJoysticksLocked(); | ||
| 708 | |||
| 709 | if (!serial) { | ||
| 710 | return; | ||
| 711 | } | ||
| 712 | |||
| 713 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 714 | if (!device->driver || device->broken) { | ||
| 715 | continue; | ||
| 716 | } | ||
| 717 | |||
| 718 | if (!device->is_bluetooth) { | ||
| 719 | continue; | ||
| 720 | } | ||
| 721 | |||
| 722 | if (device->serial && SDL_strcmp(serial, device->serial) == 0) { | ||
| 723 | while (device->num_joysticks && device->joysticks) { | ||
| 724 | HIDAPI_JoystickDisconnected(device, device->joysticks[0]); | ||
| 725 | } | ||
| 726 | } | ||
| 727 | } | ||
| 728 | } | ||
| 729 | |||
| 730 | bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID) | ||
| 731 | { | ||
| 732 | int i, j; | ||
| 733 | SDL_JoystickID joystickID; | ||
| 734 | |||
| 735 | SDL_AssertJoysticksLocked(); | ||
| 736 | |||
| 737 | for (i = 0; i < device->num_children; ++i) { | ||
| 738 | SDL_HIDAPI_Device *child = device->children[i]; | ||
| 739 | for (j = child->num_joysticks; j--;) { | ||
| 740 | HIDAPI_JoystickDisconnected(child, child->joysticks[j]); | ||
| 741 | } | ||
| 742 | } | ||
| 743 | |||
| 744 | joystickID = SDL_GetNextObjectID(); | ||
| 745 | HIDAPI_AddJoystickInstanceToDevice(device, joystickID); | ||
| 746 | |||
| 747 | for (i = 0; i < device->num_children; ++i) { | ||
| 748 | SDL_HIDAPI_Device *child = device->children[i]; | ||
| 749 | HIDAPI_AddJoystickInstanceToDevice(child, joystickID); | ||
| 750 | } | ||
| 751 | |||
| 752 | ++SDL_HIDAPI_numjoysticks; | ||
| 753 | |||
| 754 | SDL_PrivateJoystickAdded(joystickID); | ||
| 755 | |||
| 756 | if (pJoystickID) { | ||
| 757 | *pJoystickID = joystickID; | ||
| 758 | } | ||
| 759 | return true; | ||
| 760 | } | ||
| 761 | |||
| 762 | void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) | ||
| 763 | { | ||
| 764 | int i, j; | ||
| 765 | |||
| 766 | SDL_LockJoysticks(); | ||
| 767 | |||
| 768 | if (!HIDAPI_JoystickInstanceIsUnique(device, joystickID)) { | ||
| 769 | // Disconnecting a child always disconnects the parent | ||
| 770 | device = device->parent; | ||
| 771 | } | ||
| 772 | |||
| 773 | for (i = 0; i < device->num_joysticks; ++i) { | ||
| 774 | if (device->joysticks[i] == joystickID) { | ||
| 775 | SDL_Joystick *joystick = SDL_GetJoystickFromID(joystickID); | ||
| 776 | if (joystick) { | ||
| 777 | HIDAPI_JoystickClose(joystick); | ||
| 778 | } | ||
| 779 | |||
| 780 | HIDAPI_DelJoystickInstanceFromDevice(device, joystickID); | ||
| 781 | |||
| 782 | for (j = 0; j < device->num_children; ++j) { | ||
| 783 | SDL_HIDAPI_Device *child = device->children[j]; | ||
| 784 | HIDAPI_DelJoystickInstanceFromDevice(child, joystickID); | ||
| 785 | } | ||
| 786 | |||
| 787 | --SDL_HIDAPI_numjoysticks; | ||
| 788 | |||
| 789 | if (!shutting_down) { | ||
| 790 | SDL_PrivateJoystickRemoved(joystickID); | ||
| 791 | } | ||
| 792 | } | ||
| 793 | } | ||
| 794 | |||
| 795 | // Rescan the device list in case device state has changed | ||
| 796 | SDL_HIDAPI_change_count = 0; | ||
| 797 | |||
| 798 | SDL_UnlockJoysticks(); | ||
| 799 | } | ||
| 800 | |||
| 801 | static void HIDAPI_UpdateJoystickProperties(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) | ||
| 802 | { | ||
| 803 | SDL_PropertiesID props = SDL_GetJoystickProperties(joystick); | ||
| 804 | Uint32 caps = device->driver->GetJoystickCapabilities(device, joystick); | ||
| 805 | |||
| 806 | if (caps & SDL_JOYSTICK_CAP_MONO_LED) { | ||
| 807 | SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_MONO_LED_BOOLEAN, true); | ||
| 808 | } else { | ||
| 809 | SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_MONO_LED_BOOLEAN, false); | ||
| 810 | } | ||
| 811 | if (caps & SDL_JOYSTICK_CAP_RGB_LED) { | ||
| 812 | SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true); | ||
| 813 | } else { | ||
| 814 | SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, false); | ||
| 815 | } | ||
| 816 | if (caps & SDL_JOYSTICK_CAP_PLAYER_LED) { | ||
| 817 | SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_PLAYER_LED_BOOLEAN, true); | ||
| 818 | } else { | ||
| 819 | SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_PLAYER_LED_BOOLEAN, false); | ||
| 820 | } | ||
| 821 | if (caps & SDL_JOYSTICK_CAP_RUMBLE) { | ||
| 822 | SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); | ||
| 823 | } else { | ||
| 824 | SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, false); | ||
| 825 | } | ||
| 826 | if (caps & SDL_JOYSTICK_CAP_TRIGGER_RUMBLE) { | ||
| 827 | SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); | ||
| 828 | } else { | ||
| 829 | SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, false); | ||
| 830 | } | ||
| 831 | } | ||
| 832 | |||
| 833 | void HIDAPI_UpdateDeviceProperties(SDL_HIDAPI_Device *device) | ||
| 834 | { | ||
| 835 | int i; | ||
| 836 | |||
| 837 | SDL_LockJoysticks(); | ||
| 838 | |||
| 839 | for (i = 0; i < device->num_joysticks; ++i) { | ||
| 840 | SDL_Joystick *joystick = SDL_GetJoystickFromID(device->joysticks[i]); | ||
| 841 | if (joystick) { | ||
| 842 | HIDAPI_UpdateJoystickProperties(device, joystick); | ||
| 843 | } | ||
| 844 | } | ||
| 845 | |||
| 846 | SDL_UnlockJoysticks(); | ||
| 847 | } | ||
| 848 | |||
| 849 | static int HIDAPI_JoystickGetCount(void) | ||
| 850 | { | ||
| 851 | return SDL_HIDAPI_numjoysticks; | ||
| 852 | } | ||
| 853 | |||
| 854 | static SDL_HIDAPI_Device *HIDAPI_AddDevice(const struct SDL_hid_device_info *info, int num_children, SDL_HIDAPI_Device **children) | ||
| 855 | { | ||
| 856 | SDL_HIDAPI_Device *device; | ||
| 857 | SDL_HIDAPI_Device *curr, *last = NULL; | ||
| 858 | bool removed; | ||
| 859 | Uint16 bus; | ||
| 860 | |||
| 861 | SDL_AssertJoysticksLocked(); | ||
| 862 | |||
| 863 | for (curr = SDL_HIDAPI_devices, last = NULL; curr; last = curr, curr = curr->next) { | ||
| 864 | } | ||
| 865 | |||
| 866 | device = (SDL_HIDAPI_Device *)SDL_calloc(1, sizeof(*device)); | ||
| 867 | if (!device) { | ||
| 868 | return NULL; | ||
| 869 | } | ||
| 870 | SDL_SetObjectValid(device, SDL_OBJECT_TYPE_HIDAPI_JOYSTICK, true); | ||
| 871 | device->path = SDL_strdup(info->path); | ||
| 872 | if (!device->path) { | ||
| 873 | SDL_free(device); | ||
| 874 | return NULL; | ||
| 875 | } | ||
| 876 | device->seen = true; | ||
| 877 | device->vendor_id = info->vendor_id; | ||
| 878 | device->product_id = info->product_id; | ||
| 879 | device->version = info->release_number; | ||
| 880 | device->interface_number = info->interface_number; | ||
| 881 | device->interface_class = info->interface_class; | ||
| 882 | device->interface_subclass = info->interface_subclass; | ||
| 883 | device->interface_protocol = info->interface_protocol; | ||
| 884 | device->usage_page = info->usage_page; | ||
| 885 | device->usage = info->usage; | ||
| 886 | device->is_bluetooth = (info->bus_type == SDL_HID_API_BUS_BLUETOOTH); | ||
| 887 | device->dev_lock = SDL_CreateMutex(); | ||
| 888 | |||
| 889 | // Need the device name before getting the driver to know whether to ignore this device | ||
| 890 | { | ||
| 891 | char *serial_number = HIDAPI_ConvertString(info->serial_number); | ||
| 892 | |||
| 893 | device->manufacturer_string = HIDAPI_ConvertString(info->manufacturer_string); | ||
| 894 | device->product_string = HIDAPI_ConvertString(info->product_string); | ||
| 895 | device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, device->manufacturer_string, device->product_string); | ||
| 896 | |||
| 897 | if (serial_number && *serial_number) { | ||
| 898 | device->serial = serial_number; | ||
| 899 | } else { | ||
| 900 | SDL_free(serial_number); | ||
| 901 | } | ||
| 902 | |||
| 903 | if (!device->name) { | ||
| 904 | SDL_free(device->manufacturer_string); | ||
| 905 | SDL_free(device->product_string); | ||
| 906 | SDL_free(device->serial); | ||
| 907 | SDL_free(device->path); | ||
| 908 | SDL_free(device); | ||
| 909 | return NULL; | ||
| 910 | } | ||
| 911 | } | ||
| 912 | |||
| 913 | if (info->bus_type == SDL_HID_API_BUS_BLUETOOTH) { | ||
| 914 | bus = SDL_HARDWARE_BUS_BLUETOOTH; | ||
| 915 | } else { | ||
| 916 | bus = SDL_HARDWARE_BUS_USB; | ||
| 917 | } | ||
| 918 | device->guid = SDL_CreateJoystickGUID(bus, device->vendor_id, device->product_id, device->version, device->manufacturer_string, device->product_string, 'h', 0); | ||
| 919 | device->joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD; | ||
| 920 | device->type = SDL_GetJoystickGameControllerProtocol(device->name, device->vendor_id, device->product_id, device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol); | ||
| 921 | device->steam_virtual_gamepad_slot = -1; | ||
| 922 | |||
| 923 | if (num_children > 0) { | ||
| 924 | int i; | ||
| 925 | |||
| 926 | device->num_children = num_children; | ||
| 927 | device->children = children; | ||
| 928 | for (i = 0; i < num_children; ++i) { | ||
| 929 | children[i]->parent = device; | ||
| 930 | } | ||
| 931 | } | ||
| 932 | |||
| 933 | // Add it to the list | ||
| 934 | if (last) { | ||
| 935 | last->next = device; | ||
| 936 | } else { | ||
| 937 | SDL_HIDAPI_devices = device; | ||
| 938 | } | ||
| 939 | |||
| 940 | removed = false; | ||
| 941 | HIDAPI_SetupDeviceDriver(device, &removed); | ||
| 942 | if (removed) { | ||
| 943 | return NULL; | ||
| 944 | } | ||
| 945 | |||
| 946 | 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, | ||
| 947 | device->serial ? device->serial : "NONE", device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol, device->usage_page, device->usage, | ||
| 948 | device->path, device->driver ? device->driver->name : "NONE", device->driver && device->driver->enabled ? "ENABLED" : "DISABLED"); | ||
| 949 | |||
| 950 | return device; | ||
| 951 | } | ||
| 952 | |||
| 953 | static void HIDAPI_DelDevice(SDL_HIDAPI_Device *device) | ||
| 954 | { | ||
| 955 | SDL_HIDAPI_Device *curr, *last; | ||
| 956 | int i; | ||
| 957 | |||
| 958 | SDL_AssertJoysticksLocked(); | ||
| 959 | |||
| 960 | 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, | ||
| 961 | device->serial ? device->serial : "NONE", device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol, device->usage_page, device->usage, | ||
| 962 | device->path, device->driver ? device->driver->name : "NONE", device->driver && device->driver->enabled ? "ENABLED" : "DISABLED"); | ||
| 963 | |||
| 964 | for (curr = SDL_HIDAPI_devices, last = NULL; curr; last = curr, curr = curr->next) { | ||
| 965 | if (curr == device) { | ||
| 966 | if (last) { | ||
| 967 | last->next = curr->next; | ||
| 968 | } else { | ||
| 969 | SDL_HIDAPI_devices = curr->next; | ||
| 970 | } | ||
| 971 | |||
| 972 | HIDAPI_CleanupDeviceDriver(device); | ||
| 973 | |||
| 974 | // Make sure the rumble thread is done with this device | ||
| 975 | while (SDL_GetAtomicInt(&device->rumble_pending) > 0) { | ||
| 976 | SDL_Delay(10); | ||
| 977 | } | ||
| 978 | |||
| 979 | for (i = 0; i < device->num_children; ++i) { | ||
| 980 | device->children[i]->parent = NULL; | ||
| 981 | } | ||
| 982 | |||
| 983 | SDL_SetObjectValid(device, SDL_OBJECT_TYPE_HIDAPI_JOYSTICK, false); | ||
| 984 | SDL_DestroyMutex(device->dev_lock); | ||
| 985 | SDL_free(device->manufacturer_string); | ||
| 986 | SDL_free(device->product_string); | ||
| 987 | SDL_free(device->serial); | ||
| 988 | SDL_free(device->name); | ||
| 989 | SDL_free(device->path); | ||
| 990 | SDL_free(device->children); | ||
| 991 | SDL_free(device); | ||
| 992 | return; | ||
| 993 | } | ||
| 994 | } | ||
| 995 | } | ||
| 996 | |||
| 997 | static bool HIDAPI_CreateCombinedJoyCons(void) | ||
| 998 | { | ||
| 999 | SDL_HIDAPI_Device *device, *combined; | ||
| 1000 | SDL_HIDAPI_Device *joycons[2] = { NULL, NULL }; | ||
| 1001 | |||
| 1002 | SDL_AssertJoysticksLocked(); | ||
| 1003 | |||
| 1004 | if (!SDL_HIDAPI_combine_joycons) { | ||
| 1005 | return false; | ||
| 1006 | } | ||
| 1007 | |||
| 1008 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 1009 | Uint16 vendor, product; | ||
| 1010 | |||
| 1011 | if (!device->driver) { | ||
| 1012 | // Unsupported device | ||
| 1013 | continue; | ||
| 1014 | } | ||
| 1015 | if (device->parent) { | ||
| 1016 | // This device is already part of a combined device | ||
| 1017 | continue; | ||
| 1018 | } | ||
| 1019 | if (device->broken) { | ||
| 1020 | // This device can't be used | ||
| 1021 | continue; | ||
| 1022 | } | ||
| 1023 | |||
| 1024 | SDL_GetJoystickGUIDInfo(device->guid, &vendor, &product, NULL, NULL); | ||
| 1025 | |||
| 1026 | if (!joycons[0] && | ||
| 1027 | (SDL_IsJoystickNintendoSwitchJoyConLeft(vendor, product) || | ||
| 1028 | (SDL_IsJoystickNintendoSwitchJoyConGrip(vendor, product) && | ||
| 1029 | SDL_strstr(device->name, "(L)") != NULL))) { | ||
| 1030 | joycons[0] = device; | ||
| 1031 | } | ||
| 1032 | if (!joycons[1] && | ||
| 1033 | (SDL_IsJoystickNintendoSwitchJoyConRight(vendor, product) || | ||
| 1034 | (SDL_IsJoystickNintendoSwitchJoyConGrip(vendor, product) && | ||
| 1035 | SDL_strstr(device->name, "(R)") != NULL))) { | ||
| 1036 | joycons[1] = device; | ||
| 1037 | } | ||
| 1038 | if (joycons[0] && joycons[1]) { | ||
| 1039 | SDL_hid_device_info info; | ||
| 1040 | SDL_HIDAPI_Device **children = (SDL_HIDAPI_Device **)SDL_malloc(2 * sizeof(SDL_HIDAPI_Device *)); | ||
| 1041 | if (!children) { | ||
| 1042 | return false; | ||
| 1043 | } | ||
| 1044 | children[0] = joycons[0]; | ||
| 1045 | children[1] = joycons[1]; | ||
| 1046 | |||
| 1047 | SDL_zero(info); | ||
| 1048 | info.path = "nintendo_joycons_combined"; | ||
| 1049 | info.vendor_id = USB_VENDOR_NINTENDO; | ||
| 1050 | info.product_id = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; | ||
| 1051 | info.interface_number = -1; | ||
| 1052 | info.usage_page = USB_USAGEPAGE_GENERIC_DESKTOP; | ||
| 1053 | info.usage = USB_USAGE_GENERIC_GAMEPAD; | ||
| 1054 | info.manufacturer_string = L"Nintendo"; | ||
| 1055 | info.product_string = L"Switch Joy-Con (L/R)"; | ||
| 1056 | |||
| 1057 | combined = HIDAPI_AddDevice(&info, 2, children); | ||
| 1058 | if (combined && combined->driver) { | ||
| 1059 | return true; | ||
| 1060 | } else { | ||
| 1061 | if (combined) { | ||
| 1062 | HIDAPI_DelDevice(combined); | ||
| 1063 | } else { | ||
| 1064 | SDL_free(children); | ||
| 1065 | } | ||
| 1066 | return false; | ||
| 1067 | } | ||
| 1068 | } | ||
| 1069 | } | ||
| 1070 | return false; | ||
| 1071 | } | ||
| 1072 | |||
| 1073 | static void HIDAPI_UpdateDeviceList(void) | ||
| 1074 | { | ||
| 1075 | SDL_HIDAPI_Device *device; | ||
| 1076 | struct SDL_hid_device_info *devs, *info; | ||
| 1077 | |||
| 1078 | SDL_LockJoysticks(); | ||
| 1079 | |||
| 1080 | if (SDL_HIDAPI_hints_changed) { | ||
| 1081 | SDL_HIDAPI_UpdateDrivers(); | ||
| 1082 | SDL_HIDAPI_hints_changed = false; | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | // Prepare the existing device list | ||
| 1086 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 1087 | if (device->children) { | ||
| 1088 | continue; | ||
| 1089 | } | ||
| 1090 | device->seen = false; | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | // Enumerate the devices | ||
| 1094 | if (SDL_HIDAPI_numdrivers > 0) { | ||
| 1095 | devs = SDL_hid_enumerate(0, 0); | ||
| 1096 | if (devs) { | ||
| 1097 | for (info = devs; info; info = info->next) { | ||
| 1098 | device = HIDAPI_GetJoystickByInfo(info->path, info->vendor_id, info->product_id); | ||
| 1099 | if (device) { | ||
| 1100 | device->seen = true; | ||
| 1101 | |||
| 1102 | // Check to see if the serial number is available now | ||
| 1103 | if(HIDAPI_SerialIsEmpty(device)) { | ||
| 1104 | HIDAPI_SetDeviceSerialW(device, info->serial_number); | ||
| 1105 | } | ||
| 1106 | } else { | ||
| 1107 | HIDAPI_AddDevice(info, 0, NULL); | ||
| 1108 | } | ||
| 1109 | } | ||
| 1110 | SDL_hid_free_enumeration(devs); | ||
| 1111 | } | ||
| 1112 | } | ||
| 1113 | |||
| 1114 | // Remove any devices that weren't seen or have been disconnected due to read errors | ||
| 1115 | check_removed: | ||
| 1116 | device = SDL_HIDAPI_devices; | ||
| 1117 | while (device) { | ||
| 1118 | SDL_HIDAPI_Device *next = device->next; | ||
| 1119 | |||
| 1120 | if (!device->seen || | ||
| 1121 | ((device->driver || device->children) && device->num_joysticks == 0 && !device->dev)) { | ||
| 1122 | if (device->parent) { | ||
| 1123 | // When a child device goes away, so does the parent | ||
| 1124 | int i; | ||
| 1125 | device = device->parent; | ||
| 1126 | for (i = 0; i < device->num_children; ++i) { | ||
| 1127 | HIDAPI_DelDevice(device->children[i]); | ||
| 1128 | } | ||
| 1129 | HIDAPI_DelDevice(device); | ||
| 1130 | |||
| 1131 | // Update the device list again to pick up any children left | ||
| 1132 | SDL_HIDAPI_change_count = 0; | ||
| 1133 | |||
| 1134 | // We deleted more than one device here, restart the loop | ||
| 1135 | goto check_removed; | ||
| 1136 | } else { | ||
| 1137 | HIDAPI_DelDevice(device); | ||
| 1138 | device = NULL; | ||
| 1139 | |||
| 1140 | // Update the device list again in case this device comes back | ||
| 1141 | SDL_HIDAPI_change_count = 0; | ||
| 1142 | } | ||
| 1143 | } | ||
| 1144 | if (device && device->broken && device->parent) { | ||
| 1145 | HIDAPI_DelDevice(device->parent); | ||
| 1146 | |||
| 1147 | // We deleted a different device here, restart the loop | ||
| 1148 | goto check_removed; | ||
| 1149 | } | ||
| 1150 | device = next; | ||
| 1151 | } | ||
| 1152 | |||
| 1153 | // See if we can create any combined Joy-Con controllers | ||
| 1154 | while (HIDAPI_CreateCombinedJoyCons()) { | ||
| 1155 | } | ||
| 1156 | |||
| 1157 | SDL_UnlockJoysticks(); | ||
| 1158 | } | ||
| 1159 | |||
| 1160 | static bool HIDAPI_IsEquivalentToDevice(Uint16 vendor_id, Uint16 product_id, SDL_HIDAPI_Device *device) | ||
| 1161 | { | ||
| 1162 | if (vendor_id == device->vendor_id && product_id == device->product_id) { | ||
| 1163 | return true; | ||
| 1164 | } | ||
| 1165 | |||
| 1166 | if (vendor_id == USB_VENDOR_MICROSOFT) { | ||
| 1167 | // If we're looking for the wireless XBox 360 controller, also look for the dongle | ||
| 1168 | if (product_id == USB_PRODUCT_XBOX360_XUSB_CONTROLLER && device->product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER) { | ||
| 1169 | return true; | ||
| 1170 | } | ||
| 1171 | |||
| 1172 | // If we're looking for the raw input Xbox One controller, match it against any other Xbox One controller | ||
| 1173 | if (product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER && | ||
| 1174 | device->type == SDL_GAMEPAD_TYPE_XBOXONE) { | ||
| 1175 | return true; | ||
| 1176 | } | ||
| 1177 | |||
| 1178 | // If we're looking for an XInput controller, match it against any other Xbox controller | ||
| 1179 | if (product_id == USB_PRODUCT_XBOX360_XUSB_CONTROLLER) { | ||
| 1180 | if (device->type == SDL_GAMEPAD_TYPE_XBOX360 || device->type == SDL_GAMEPAD_TYPE_XBOXONE) { | ||
| 1181 | return true; | ||
| 1182 | } | ||
| 1183 | } | ||
| 1184 | } | ||
| 1185 | |||
| 1186 | if (vendor_id == USB_VENDOR_NVIDIA) { | ||
| 1187 | // If we're looking for the NVIDIA SHIELD controller Xbox interface, match it against any NVIDIA SHIELD controller | ||
| 1188 | if (product_id == 0xb400 && | ||
| 1189 | SDL_IsJoystickNVIDIASHIELDController(vendor_id, product_id)) { | ||
| 1190 | return true; | ||
| 1191 | } | ||
| 1192 | } | ||
| 1193 | return false; | ||
| 1194 | } | ||
| 1195 | |||
| 1196 | static bool HIDAPI_StartUpdatingDevices(void) | ||
| 1197 | { | ||
| 1198 | return SDL_CompareAndSwapAtomicInt(&SDL_HIDAPI_updating_devices, false, true); | ||
| 1199 | } | ||
| 1200 | |||
| 1201 | static void HIDAPI_FinishUpdatingDevices(void) | ||
| 1202 | { | ||
| 1203 | SDL_SetAtomicInt(&SDL_HIDAPI_updating_devices, false); | ||
| 1204 | } | ||
| 1205 | |||
| 1206 | bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type) | ||
| 1207 | { | ||
| 1208 | SDL_HIDAPI_Device *device; | ||
| 1209 | bool result = false; | ||
| 1210 | |||
| 1211 | // Make sure we're initialized, as this could be called from other drivers during startup | ||
| 1212 | if (!HIDAPI_JoystickInit()) { | ||
| 1213 | return false; | ||
| 1214 | } | ||
| 1215 | |||
| 1216 | if (HIDAPI_StartUpdatingDevices()) { | ||
| 1217 | HIDAPI_UpdateDeviceList(); | ||
| 1218 | HIDAPI_FinishUpdatingDevices(); | ||
| 1219 | } | ||
| 1220 | |||
| 1221 | SDL_LockJoysticks(); | ||
| 1222 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 1223 | if (device->driver && device->type == type) { | ||
| 1224 | result = true; | ||
| 1225 | break; | ||
| 1226 | } | ||
| 1227 | } | ||
| 1228 | SDL_UnlockJoysticks(); | ||
| 1229 | |||
| 1230 | #ifdef DEBUG_HIDAPI | ||
| 1231 | SDL_Log("HIDAPI_IsDeviceTypePresent() returning %s for %d", result ? "true" : "false", type); | ||
| 1232 | #endif | ||
| 1233 | return result; | ||
| 1234 | } | ||
| 1235 | |||
| 1236 | bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) | ||
| 1237 | { | ||
| 1238 | SDL_HIDAPI_Device *device; | ||
| 1239 | bool supported = false; | ||
| 1240 | bool result = false; | ||
| 1241 | |||
| 1242 | // Make sure we're initialized, as this could be called from other drivers during startup | ||
| 1243 | if (!HIDAPI_JoystickInit()) { | ||
| 1244 | return false; | ||
| 1245 | } | ||
| 1246 | |||
| 1247 | /* Only update the device list for devices we know might be supported. | ||
| 1248 | If we did this for every device, it would hit the USB driver too hard and potentially | ||
| 1249 | lock up the system. This won't catch devices that we support but can only detect using | ||
| 1250 | USB interface details, like Xbox controllers, but hopefully the device list update is | ||
| 1251 | responsive enough to catch those. | ||
| 1252 | */ | ||
| 1253 | supported = HIDAPI_IsDeviceSupported(vendor_id, product_id, version, name); | ||
| 1254 | #if defined(SDL_JOYSTICK_HIDAPI_XBOX360) || defined(SDL_JOYSTICK_HIDAPI_XBOXONE) | ||
| 1255 | if (!supported && | ||
| 1256 | (SDL_strstr(name, "Xbox") || SDL_strstr(name, "X-Box") || SDL_strstr(name, "XBOX"))) { | ||
| 1257 | supported = true; | ||
| 1258 | } | ||
| 1259 | #endif // SDL_JOYSTICK_HIDAPI_XBOX360 || SDL_JOYSTICK_HIDAPI_XBOXONE | ||
| 1260 | if (supported) { | ||
| 1261 | if (HIDAPI_StartUpdatingDevices()) { | ||
| 1262 | HIDAPI_UpdateDeviceList(); | ||
| 1263 | HIDAPI_FinishUpdatingDevices(); | ||
| 1264 | } | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | /* Note that this isn't a perfect check - there may be multiple devices with 0 VID/PID, | ||
| 1268 | or a different name than we have it listed here, etc, but if we support the device | ||
| 1269 | and we have something similar in our device list, mark it as present. | ||
| 1270 | */ | ||
| 1271 | SDL_LockJoysticks(); | ||
| 1272 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 1273 | if (device->driver && | ||
| 1274 | HIDAPI_IsEquivalentToDevice(vendor_id, product_id, device)) { | ||
| 1275 | result = true; | ||
| 1276 | break; | ||
| 1277 | } | ||
| 1278 | } | ||
| 1279 | SDL_UnlockJoysticks(); | ||
| 1280 | |||
| 1281 | #ifdef DEBUG_HIDAPI | ||
| 1282 | SDL_Log("HIDAPI_IsDevicePresent() returning %s for 0x%.4x / 0x%.4x", result ? "true" : "false", vendor_id, product_id); | ||
| 1283 | #endif | ||
| 1284 | return result; | ||
| 1285 | } | ||
| 1286 | |||
| 1287 | char *HIDAPI_GetDeviceProductName(Uint16 vendor_id, Uint16 product_id) | ||
| 1288 | { | ||
| 1289 | SDL_HIDAPI_Device *device; | ||
| 1290 | char *name = NULL; | ||
| 1291 | |||
| 1292 | SDL_LockJoysticks(); | ||
| 1293 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 1294 | if (vendor_id == device->vendor_id && product_id == device->product_id) { | ||
| 1295 | if (device->product_string) { | ||
| 1296 | name = SDL_strdup(device->product_string); | ||
| 1297 | } | ||
| 1298 | break; | ||
| 1299 | } | ||
| 1300 | } | ||
| 1301 | SDL_UnlockJoysticks(); | ||
| 1302 | |||
| 1303 | return name; | ||
| 1304 | } | ||
| 1305 | |||
| 1306 | char *HIDAPI_GetDeviceManufacturerName(Uint16 vendor_id, Uint16 product_id) | ||
| 1307 | { | ||
| 1308 | SDL_HIDAPI_Device *device; | ||
| 1309 | char *name = NULL; | ||
| 1310 | |||
| 1311 | SDL_LockJoysticks(); | ||
| 1312 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 1313 | if (vendor_id == device->vendor_id && product_id == device->product_id) { | ||
| 1314 | if (device->manufacturer_string) { | ||
| 1315 | name = SDL_strdup(device->manufacturer_string); | ||
| 1316 | } | ||
| 1317 | break; | ||
| 1318 | } | ||
| 1319 | } | ||
| 1320 | SDL_UnlockJoysticks(); | ||
| 1321 | |||
| 1322 | return name; | ||
| 1323 | } | ||
| 1324 | |||
| 1325 | SDL_JoystickType HIDAPI_GetJoystickTypeFromGUID(SDL_GUID guid) | ||
| 1326 | { | ||
| 1327 | SDL_HIDAPI_Device *device; | ||
| 1328 | SDL_JoystickType type = SDL_JOYSTICK_TYPE_UNKNOWN; | ||
| 1329 | |||
| 1330 | SDL_LockJoysticks(); | ||
| 1331 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 1332 | if (SDL_memcmp(&guid, &device->guid, sizeof(guid)) == 0) { | ||
| 1333 | type = device->joystick_type; | ||
| 1334 | break; | ||
| 1335 | } | ||
| 1336 | } | ||
| 1337 | SDL_UnlockJoysticks(); | ||
| 1338 | |||
| 1339 | return type; | ||
| 1340 | } | ||
| 1341 | |||
| 1342 | SDL_GamepadType HIDAPI_GetGamepadTypeFromGUID(SDL_GUID guid) | ||
| 1343 | { | ||
| 1344 | SDL_HIDAPI_Device *device; | ||
| 1345 | SDL_GamepadType type = SDL_GAMEPAD_TYPE_STANDARD; | ||
| 1346 | |||
| 1347 | SDL_LockJoysticks(); | ||
| 1348 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 1349 | if (SDL_memcmp(&guid, &device->guid, sizeof(guid)) == 0) { | ||
| 1350 | type = device->type; | ||
| 1351 | break; | ||
| 1352 | } | ||
| 1353 | } | ||
| 1354 | SDL_UnlockJoysticks(); | ||
| 1355 | |||
| 1356 | return type; | ||
| 1357 | } | ||
| 1358 | |||
| 1359 | static void HIDAPI_JoystickDetect(void) | ||
| 1360 | { | ||
| 1361 | if (HIDAPI_StartUpdatingDevices()) { | ||
| 1362 | Uint32 count = SDL_hid_device_change_count(); | ||
| 1363 | if (SDL_HIDAPI_change_count != count) { | ||
| 1364 | SDL_HIDAPI_change_count = count; | ||
| 1365 | HIDAPI_UpdateDeviceList(); | ||
| 1366 | } | ||
| 1367 | HIDAPI_FinishUpdatingDevices(); | ||
| 1368 | } | ||
| 1369 | } | ||
| 1370 | |||
| 1371 | void HIDAPI_UpdateDevices(void) | ||
| 1372 | { | ||
| 1373 | SDL_HIDAPI_Device *device; | ||
| 1374 | |||
| 1375 | SDL_AssertJoysticksLocked(); | ||
| 1376 | |||
| 1377 | // Update the devices, which may change connected joysticks and send events | ||
| 1378 | |||
| 1379 | // Prepare the existing device list | ||
| 1380 | if (HIDAPI_StartUpdatingDevices()) { | ||
| 1381 | for (device = SDL_HIDAPI_devices; device; device = device->next) { | ||
| 1382 | if (device->parent) { | ||
| 1383 | continue; | ||
| 1384 | } | ||
| 1385 | if (device->driver) { | ||
| 1386 | if (SDL_TryLockMutex(device->dev_lock)) { | ||
| 1387 | device->updating = true; | ||
| 1388 | device->driver->UpdateDevice(device); | ||
| 1389 | device->updating = false; | ||
| 1390 | SDL_UnlockMutex(device->dev_lock); | ||
| 1391 | } | ||
| 1392 | } | ||
| 1393 | } | ||
| 1394 | HIDAPI_FinishUpdatingDevices(); | ||
| 1395 | } | ||
| 1396 | } | ||
| 1397 | |||
| 1398 | static const char *HIDAPI_JoystickGetDeviceName(int device_index) | ||
| 1399 | { | ||
| 1400 | SDL_HIDAPI_Device *device; | ||
| 1401 | const char *name = NULL; | ||
| 1402 | |||
| 1403 | device = HIDAPI_GetDeviceByIndex(device_index, NULL); | ||
| 1404 | if (device) { | ||
| 1405 | // FIXME: The device could be freed after this name is returned... | ||
| 1406 | name = device->name; | ||
| 1407 | } | ||
| 1408 | |||
| 1409 | return name; | ||
| 1410 | } | ||
| 1411 | |||
| 1412 | static const char *HIDAPI_JoystickGetDevicePath(int device_index) | ||
| 1413 | { | ||
| 1414 | SDL_HIDAPI_Device *device; | ||
| 1415 | const char *path = NULL; | ||
| 1416 | |||
| 1417 | device = HIDAPI_GetDeviceByIndex(device_index, NULL); | ||
| 1418 | if (device) { | ||
| 1419 | // FIXME: The device could be freed after this path is returned... | ||
| 1420 | path = device->path; | ||
| 1421 | } | ||
| 1422 | |||
| 1423 | return path; | ||
| 1424 | } | ||
| 1425 | |||
| 1426 | static int HIDAPI_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) | ||
| 1427 | { | ||
| 1428 | SDL_HIDAPI_Device *device; | ||
| 1429 | |||
| 1430 | device = HIDAPI_GetDeviceByIndex(device_index, NULL); | ||
| 1431 | if (device) { | ||
| 1432 | return device->steam_virtual_gamepad_slot; | ||
| 1433 | } | ||
| 1434 | return -1; | ||
| 1435 | } | ||
| 1436 | |||
| 1437 | static int HIDAPI_JoystickGetDevicePlayerIndex(int device_index) | ||
| 1438 | { | ||
| 1439 | SDL_HIDAPI_Device *device; | ||
| 1440 | SDL_JoystickID instance_id; | ||
| 1441 | int player_index = -1; | ||
| 1442 | |||
| 1443 | device = HIDAPI_GetDeviceByIndex(device_index, &instance_id); | ||
| 1444 | if (device) { | ||
| 1445 | player_index = device->driver->GetDevicePlayerIndex(device, instance_id); | ||
| 1446 | } | ||
| 1447 | |||
| 1448 | return player_index; | ||
| 1449 | } | ||
| 1450 | |||
| 1451 | static void HIDAPI_JoystickSetDevicePlayerIndex(int device_index, int player_index) | ||
| 1452 | { | ||
| 1453 | SDL_HIDAPI_Device *device; | ||
| 1454 | SDL_JoystickID instance_id; | ||
| 1455 | |||
| 1456 | device = HIDAPI_GetDeviceByIndex(device_index, &instance_id); | ||
| 1457 | if (device) { | ||
| 1458 | device->driver->SetDevicePlayerIndex(device, instance_id, player_index); | ||
| 1459 | } | ||
| 1460 | } | ||
| 1461 | |||
| 1462 | static SDL_GUID HIDAPI_JoystickGetDeviceGUID(int device_index) | ||
| 1463 | { | ||
| 1464 | SDL_HIDAPI_Device *device; | ||
| 1465 | SDL_GUID guid; | ||
| 1466 | |||
| 1467 | device = HIDAPI_GetDeviceByIndex(device_index, NULL); | ||
| 1468 | if (device) { | ||
| 1469 | SDL_memcpy(&guid, &device->guid, sizeof(guid)); | ||
| 1470 | } else { | ||
| 1471 | SDL_zero(guid); | ||
| 1472 | } | ||
| 1473 | |||
| 1474 | return guid; | ||
| 1475 | } | ||
| 1476 | |||
| 1477 | static SDL_JoystickID HIDAPI_JoystickGetDeviceInstanceID(int device_index) | ||
| 1478 | { | ||
| 1479 | SDL_JoystickID joystickID = 0; | ||
| 1480 | HIDAPI_GetDeviceByIndex(device_index, &joystickID); | ||
| 1481 | return joystickID; | ||
| 1482 | } | ||
| 1483 | |||
| 1484 | static bool HIDAPI_JoystickOpen(SDL_Joystick *joystick, int device_index) | ||
| 1485 | { | ||
| 1486 | SDL_JoystickID joystickID = 0; | ||
| 1487 | SDL_HIDAPI_Device *device = HIDAPI_GetDeviceByIndex(device_index, &joystickID); | ||
| 1488 | struct joystick_hwdata *hwdata; | ||
| 1489 | |||
| 1490 | SDL_AssertJoysticksLocked(); | ||
| 1491 | |||
| 1492 | if (!device || !device->driver || device->broken) { | ||
| 1493 | // This should never happen - validated before being called | ||
| 1494 | return SDL_SetError("Couldn't find HIDAPI device at index %d", device_index); | ||
| 1495 | } | ||
| 1496 | |||
| 1497 | hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata)); | ||
| 1498 | if (!hwdata) { | ||
| 1499 | return false; | ||
| 1500 | } | ||
| 1501 | hwdata->device = device; | ||
| 1502 | |||
| 1503 | // Process any pending reports before opening the device | ||
| 1504 | SDL_LockMutex(device->dev_lock); | ||
| 1505 | device->updating = true; | ||
| 1506 | device->driver->UpdateDevice(device); | ||
| 1507 | device->updating = false; | ||
| 1508 | SDL_UnlockMutex(device->dev_lock); | ||
| 1509 | |||
| 1510 | // UpdateDevice() may have called HIDAPI_JoystickDisconnected() if the device went away | ||
| 1511 | if (device->num_joysticks == 0) { | ||
| 1512 | SDL_free(hwdata); | ||
| 1513 | return SDL_SetError("HIDAPI device disconnected while opening"); | ||
| 1514 | } | ||
| 1515 | |||
| 1516 | // Set the default connection state, can be overridden below | ||
| 1517 | if (device->is_bluetooth) { | ||
| 1518 | joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; | ||
| 1519 | } else { | ||
| 1520 | joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; | ||
| 1521 | } | ||
| 1522 | |||
| 1523 | if (!device->driver->OpenJoystick(device, joystick)) { | ||
| 1524 | // The open failed, mark this device as disconnected and update devices | ||
| 1525 | HIDAPI_JoystickDisconnected(device, joystickID); | ||
| 1526 | SDL_free(hwdata); | ||
| 1527 | return false; | ||
| 1528 | } | ||
| 1529 | |||
| 1530 | HIDAPI_UpdateJoystickProperties(device, joystick); | ||
| 1531 | |||
| 1532 | if (device->serial) { | ||
| 1533 | joystick->serial = SDL_strdup(device->serial); | ||
| 1534 | } | ||
| 1535 | |||
| 1536 | joystick->hwdata = hwdata; | ||
| 1537 | return true; | ||
| 1538 | } | ||
| 1539 | |||
| 1540 | static bool HIDAPI_GetJoystickDevice(SDL_Joystick *joystick, SDL_HIDAPI_Device **device) | ||
| 1541 | { | ||
| 1542 | SDL_AssertJoysticksLocked(); | ||
| 1543 | |||
| 1544 | if (joystick && joystick->hwdata) { | ||
| 1545 | *device = joystick->hwdata->device; | ||
| 1546 | if (SDL_ObjectValid(*device, SDL_OBJECT_TYPE_HIDAPI_JOYSTICK) && (*device)->driver != NULL) { | ||
| 1547 | return true; | ||
| 1548 | } | ||
| 1549 | } | ||
| 1550 | return false; | ||
| 1551 | } | ||
| 1552 | |||
| 1553 | static bool HIDAPI_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 1554 | { | ||
| 1555 | bool result; | ||
| 1556 | SDL_HIDAPI_Device *device = NULL; | ||
| 1557 | |||
| 1558 | if (HIDAPI_GetJoystickDevice(joystick, &device)) { | ||
| 1559 | result = device->driver->RumbleJoystick(device, joystick, low_frequency_rumble, high_frequency_rumble); | ||
| 1560 | } else { | ||
| 1561 | result = SDL_SetError("Rumble failed, device disconnected"); | ||
| 1562 | } | ||
| 1563 | |||
| 1564 | return result; | ||
| 1565 | } | ||
| 1566 | |||
| 1567 | static bool HIDAPI_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) | ||
| 1568 | { | ||
| 1569 | bool result; | ||
| 1570 | SDL_HIDAPI_Device *device = NULL; | ||
| 1571 | |||
| 1572 | if (HIDAPI_GetJoystickDevice(joystick, &device)) { | ||
| 1573 | result = device->driver->RumbleJoystickTriggers(device, joystick, left_rumble, right_rumble); | ||
| 1574 | } else { | ||
| 1575 | result = SDL_SetError("Rumble failed, device disconnected"); | ||
| 1576 | } | ||
| 1577 | |||
| 1578 | return result; | ||
| 1579 | } | ||
| 1580 | |||
| 1581 | static bool HIDAPI_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) | ||
| 1582 | { | ||
| 1583 | bool result; | ||
| 1584 | SDL_HIDAPI_Device *device = NULL; | ||
| 1585 | |||
| 1586 | if (HIDAPI_GetJoystickDevice(joystick, &device)) { | ||
| 1587 | result = device->driver->SetJoystickLED(device, joystick, red, green, blue); | ||
| 1588 | } else { | ||
| 1589 | result = SDL_SetError("SetLED failed, device disconnected"); | ||
| 1590 | } | ||
| 1591 | |||
| 1592 | return result; | ||
| 1593 | } | ||
| 1594 | |||
| 1595 | static bool HIDAPI_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) | ||
| 1596 | { | ||
| 1597 | bool result; | ||
| 1598 | SDL_HIDAPI_Device *device = NULL; | ||
| 1599 | |||
| 1600 | if (HIDAPI_GetJoystickDevice(joystick, &device)) { | ||
| 1601 | result = device->driver->SendJoystickEffect(device, joystick, data, size); | ||
| 1602 | } else { | ||
| 1603 | result = SDL_SetError("SendEffect failed, device disconnected"); | ||
| 1604 | } | ||
| 1605 | |||
| 1606 | return result; | ||
| 1607 | } | ||
| 1608 | |||
| 1609 | static bool HIDAPI_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) | ||
| 1610 | { | ||
| 1611 | bool result; | ||
| 1612 | SDL_HIDAPI_Device *device = NULL; | ||
| 1613 | |||
| 1614 | if (HIDAPI_GetJoystickDevice(joystick, &device)) { | ||
| 1615 | result = device->driver->SetJoystickSensorsEnabled(device, joystick, enabled); | ||
| 1616 | } else { | ||
| 1617 | result = SDL_SetError("SetSensorsEnabled failed, device disconnected"); | ||
| 1618 | } | ||
| 1619 | |||
| 1620 | return result; | ||
| 1621 | } | ||
| 1622 | |||
| 1623 | static void HIDAPI_JoystickUpdate(SDL_Joystick *joystick) | ||
| 1624 | { | ||
| 1625 | // This is handled in SDL_HIDAPI_UpdateDevices() | ||
| 1626 | } | ||
| 1627 | |||
| 1628 | static void HIDAPI_JoystickClose(SDL_Joystick *joystick) SDL_NO_THREAD_SAFETY_ANALYSIS // We unlock the device lock so rumble can complete | ||
| 1629 | { | ||
| 1630 | SDL_AssertJoysticksLocked(); | ||
| 1631 | |||
| 1632 | if (joystick->hwdata) { | ||
| 1633 | SDL_HIDAPI_Device *device = joystick->hwdata->device; | ||
| 1634 | int i; | ||
| 1635 | |||
| 1636 | // Wait up to 30 ms for pending rumble to complete | ||
| 1637 | if (device->updating) { | ||
| 1638 | // Unlock the device so rumble can complete | ||
| 1639 | SDL_UnlockMutex(device->dev_lock); | ||
| 1640 | } | ||
| 1641 | for (i = 0; i < 3; ++i) { | ||
| 1642 | if (SDL_GetAtomicInt(&device->rumble_pending) > 0) { | ||
| 1643 | SDL_Delay(10); | ||
| 1644 | } | ||
| 1645 | } | ||
| 1646 | if (device->updating) { | ||
| 1647 | // Relock the device | ||
| 1648 | SDL_LockMutex(device->dev_lock); | ||
| 1649 | } | ||
| 1650 | |||
| 1651 | device->driver->CloseJoystick(device, joystick); | ||
| 1652 | |||
| 1653 | SDL_free(joystick->hwdata); | ||
| 1654 | joystick->hwdata = NULL; | ||
| 1655 | } | ||
| 1656 | } | ||
| 1657 | |||
| 1658 | static void HIDAPI_JoystickQuit(void) | ||
| 1659 | { | ||
| 1660 | int i; | ||
| 1661 | |||
| 1662 | SDL_AssertJoysticksLocked(); | ||
| 1663 | |||
| 1664 | shutting_down = true; | ||
| 1665 | |||
| 1666 | SDL_HIDAPI_QuitRumble(); | ||
| 1667 | |||
| 1668 | while (SDL_HIDAPI_devices) { | ||
| 1669 | SDL_HIDAPI_Device *device = SDL_HIDAPI_devices; | ||
| 1670 | if (device->parent) { | ||
| 1671 | // When a child device goes away, so does the parent | ||
| 1672 | device = device->parent; | ||
| 1673 | for (i = 0; i < device->num_children; ++i) { | ||
| 1674 | HIDAPI_DelDevice(device->children[i]); | ||
| 1675 | } | ||
| 1676 | HIDAPI_DelDevice(device); | ||
| 1677 | } else { | ||
| 1678 | HIDAPI_DelDevice(device); | ||
| 1679 | } | ||
| 1680 | } | ||
| 1681 | |||
| 1682 | // Make sure the drivers cleaned up properly | ||
| 1683 | SDL_assert(SDL_HIDAPI_numjoysticks == 0); | ||
| 1684 | |||
| 1685 | for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { | ||
| 1686 | SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; | ||
| 1687 | driver->UnregisterHints(SDL_HIDAPIDriverHintChanged, driver); | ||
| 1688 | } | ||
| 1689 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS, | ||
| 1690 | SDL_HIDAPIDriverHintChanged, NULL); | ||
| 1691 | SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI, | ||
| 1692 | SDL_HIDAPIDriverHintChanged, NULL); | ||
| 1693 | |||
| 1694 | SDL_hid_exit(); | ||
| 1695 | |||
| 1696 | SDL_HIDAPI_change_count = 0; | ||
| 1697 | shutting_down = false; | ||
| 1698 | initialized = false; | ||
| 1699 | } | ||
| 1700 | |||
| 1701 | static bool HIDAPI_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) | ||
| 1702 | { | ||
| 1703 | return false; | ||
| 1704 | } | ||
| 1705 | |||
| 1706 | SDL_JoystickDriver SDL_HIDAPI_JoystickDriver = { | ||
| 1707 | HIDAPI_JoystickInit, | ||
| 1708 | HIDAPI_JoystickGetCount, | ||
| 1709 | HIDAPI_JoystickDetect, | ||
| 1710 | HIDAPI_IsDevicePresent, | ||
| 1711 | HIDAPI_JoystickGetDeviceName, | ||
| 1712 | HIDAPI_JoystickGetDevicePath, | ||
| 1713 | HIDAPI_JoystickGetDeviceSteamVirtualGamepadSlot, | ||
| 1714 | HIDAPI_JoystickGetDevicePlayerIndex, | ||
| 1715 | HIDAPI_JoystickSetDevicePlayerIndex, | ||
| 1716 | HIDAPI_JoystickGetDeviceGUID, | ||
| 1717 | HIDAPI_JoystickGetDeviceInstanceID, | ||
| 1718 | HIDAPI_JoystickOpen, | ||
| 1719 | HIDAPI_JoystickRumble, | ||
| 1720 | HIDAPI_JoystickRumbleTriggers, | ||
| 1721 | HIDAPI_JoystickSetLED, | ||
| 1722 | HIDAPI_JoystickSendEffect, | ||
| 1723 | HIDAPI_JoystickSetSensorsEnabled, | ||
| 1724 | HIDAPI_JoystickUpdate, | ||
| 1725 | HIDAPI_JoystickClose, | ||
| 1726 | HIDAPI_JoystickQuit, | ||
| 1727 | HIDAPI_JoystickGetGamepadMapping | ||
| 1728 | }; | ||
| 1729 | |||
| 1730 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifndef SDL_JOYSTICK_HIDAPI_H | ||
| 24 | #define SDL_JOYSTICK_HIDAPI_H | ||
| 25 | |||
| 26 | #include "../usb_ids.h" | ||
| 27 | |||
| 28 | // This is the full set of HIDAPI drivers available | ||
| 29 | #define SDL_JOYSTICK_HIDAPI_GAMECUBE | ||
| 30 | #define SDL_JOYSTICK_HIDAPI_LUNA | ||
| 31 | #define SDL_JOYSTICK_HIDAPI_PS3 | ||
| 32 | #define SDL_JOYSTICK_HIDAPI_PS4 | ||
| 33 | #define SDL_JOYSTICK_HIDAPI_PS5 | ||
| 34 | #define SDL_JOYSTICK_HIDAPI_STADIA | ||
| 35 | #define SDL_JOYSTICK_HIDAPI_STEAM | ||
| 36 | #define SDL_JOYSTICK_HIDAPI_STEAMDECK | ||
| 37 | #define SDL_JOYSTICK_HIDAPI_SWITCH | ||
| 38 | #define SDL_JOYSTICK_HIDAPI_WII | ||
| 39 | #define SDL_JOYSTICK_HIDAPI_XBOX360 | ||
| 40 | #define SDL_JOYSTICK_HIDAPI_XBOXONE | ||
| 41 | #define SDL_JOYSTICK_HIDAPI_SHIELD | ||
| 42 | #define SDL_JOYSTICK_HIDAPI_STEAM_HORI | ||
| 43 | |||
| 44 | // Joystick capability definitions | ||
| 45 | #define SDL_JOYSTICK_CAP_MONO_LED 0x00000001 | ||
| 46 | #define SDL_JOYSTICK_CAP_RGB_LED 0x00000002 | ||
| 47 | #define SDL_JOYSTICK_CAP_PLAYER_LED 0x00000004 | ||
| 48 | #define SDL_JOYSTICK_CAP_RUMBLE 0x00000010 | ||
| 49 | #define SDL_JOYSTICK_CAP_TRIGGER_RUMBLE 0x00000020 | ||
| 50 | |||
| 51 | // Whether HIDAPI is enabled by default | ||
| 52 | #if defined(SDL_PLATFORM_ANDROID) || \ | ||
| 53 | defined(SDL_PLATFORM_IOS) || \ | ||
| 54 | defined(SDL_PLATFORM_TVOS) || \ | ||
| 55 | defined(SDL_PLATFORM_VISIONOS) | ||
| 56 | // 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. | ||
| 57 | #define SDL_HIDAPI_DEFAULT false | ||
| 58 | #else | ||
| 59 | #define SDL_HIDAPI_DEFAULT true | ||
| 60 | #endif | ||
| 61 | |||
| 62 | // The maximum size of a USB packet for HID devices | ||
| 63 | #define USB_PACKET_LENGTH 64 | ||
| 64 | |||
| 65 | // Forward declaration | ||
| 66 | struct SDL_HIDAPI_DeviceDriver; | ||
| 67 | |||
| 68 | typedef struct SDL_HIDAPI_Device | ||
| 69 | { | ||
| 70 | char *name; | ||
| 71 | char *manufacturer_string; | ||
| 72 | char *product_string; | ||
| 73 | char *path; | ||
| 74 | Uint16 vendor_id; | ||
| 75 | Uint16 product_id; | ||
| 76 | Uint16 version; | ||
| 77 | char *serial; | ||
| 78 | SDL_GUID guid; | ||
| 79 | int interface_number; // Available on Windows and Linux | ||
| 80 | int interface_class; | ||
| 81 | int interface_subclass; | ||
| 82 | int interface_protocol; | ||
| 83 | Uint16 usage_page; // Available on Windows and macOS | ||
| 84 | Uint16 usage; // Available on Windows and macOS | ||
| 85 | bool is_bluetooth; | ||
| 86 | SDL_JoystickType joystick_type; | ||
| 87 | SDL_GamepadType type; | ||
| 88 | int steam_virtual_gamepad_slot; | ||
| 89 | |||
| 90 | struct SDL_HIDAPI_DeviceDriver *driver; | ||
| 91 | void *context; | ||
| 92 | SDL_Mutex *dev_lock; | ||
| 93 | SDL_hid_device *dev; | ||
| 94 | SDL_AtomicInt rumble_pending; | ||
| 95 | int num_joysticks; | ||
| 96 | SDL_JoystickID *joysticks; | ||
| 97 | |||
| 98 | // Used during scanning for device changes | ||
| 99 | bool seen; | ||
| 100 | |||
| 101 | // Used to flag that the device is being updated | ||
| 102 | bool updating; | ||
| 103 | |||
| 104 | // Used to flag devices that failed open | ||
| 105 | // This can happen on Windows with Bluetooth devices that have turned off | ||
| 106 | bool broken; | ||
| 107 | |||
| 108 | struct SDL_HIDAPI_Device *parent; | ||
| 109 | int num_children; | ||
| 110 | struct SDL_HIDAPI_Device **children; | ||
| 111 | |||
| 112 | struct SDL_HIDAPI_Device *next; | ||
| 113 | } SDL_HIDAPI_Device; | ||
| 114 | |||
| 115 | typedef struct SDL_HIDAPI_DeviceDriver | ||
| 116 | { | ||
| 117 | const char *name; | ||
| 118 | bool enabled; | ||
| 119 | void (*RegisterHints)(SDL_HintCallback callback, void *userdata); | ||
| 120 | void (*UnregisterHints)(SDL_HintCallback callback, void *userdata); | ||
| 121 | bool (*IsEnabled)(void); | ||
| 122 | 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); | ||
| 123 | bool (*InitDevice)(SDL_HIDAPI_Device *device); | ||
| 124 | int (*GetDevicePlayerIndex)(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id); | ||
| 125 | void (*SetDevicePlayerIndex)(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index); | ||
| 126 | bool (*UpdateDevice)(SDL_HIDAPI_Device *device); | ||
| 127 | bool (*OpenJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick); | ||
| 128 | bool (*RumbleJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); | ||
| 129 | bool (*RumbleJoystickTriggers)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble); | ||
| 130 | Uint32 (*GetJoystickCapabilities)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick); | ||
| 131 | bool (*SetJoystickLED)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue); | ||
| 132 | bool (*SendJoystickEffect)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size); | ||
| 133 | bool (*SetJoystickSensorsEnabled)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled); | ||
| 134 | void (*CloseJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick); | ||
| 135 | void (*FreeDevice)(SDL_HIDAPI_Device *device); | ||
| 136 | |||
| 137 | } SDL_HIDAPI_DeviceDriver; | ||
| 138 | |||
| 139 | // HIDAPI device support | ||
| 140 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined; | ||
| 141 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube; | ||
| 142 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons; | ||
| 143 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna; | ||
| 144 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic; | ||
| 145 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3; | ||
| 146 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3ThirdParty; | ||
| 147 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3SonySixaxis; | ||
| 148 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4; | ||
| 149 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5; | ||
| 150 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield; | ||
| 151 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia; | ||
| 152 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam; | ||
| 153 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck; | ||
| 154 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch; | ||
| 155 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii; | ||
| 156 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360; | ||
| 157 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W; | ||
| 158 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne; | ||
| 159 | extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori; | ||
| 160 | |||
| 161 | // Return true if a HID device is present and supported as a joystick of the given type | ||
| 162 | extern bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type); | ||
| 163 | |||
| 164 | // Return true if a HID device is present and supported as a joystick | ||
| 165 | extern bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name); | ||
| 166 | |||
| 167 | // Return the name of a connected device, which should be freed with SDL_free(), or NULL if it's not available | ||
| 168 | extern char *HIDAPI_GetDeviceProductName(Uint16 vendor_id, Uint16 product_id); | ||
| 169 | |||
| 170 | // Return the manufacturer of a connected device, which should be freed with SDL_free(), or NULL if it's not available | ||
| 171 | extern char *HIDAPI_GetDeviceManufacturerName(Uint16 vendor_id, Uint16 product_id); | ||
| 172 | |||
| 173 | // Return the type of a joystick if it's present and supported | ||
| 174 | extern SDL_JoystickType HIDAPI_GetJoystickTypeFromGUID(SDL_GUID guid); | ||
| 175 | |||
| 176 | // Return the type of a game controller if it's present and supported | ||
| 177 | extern SDL_GamepadType HIDAPI_GetGamepadTypeFromGUID(SDL_GUID guid); | ||
| 178 | |||
| 179 | extern void HIDAPI_UpdateDevices(void); | ||
| 180 | extern void HIDAPI_SetDeviceName(SDL_HIDAPI_Device *device, const char *name); | ||
| 181 | extern void HIDAPI_SetDeviceProduct(SDL_HIDAPI_Device *device, Uint16 vendor_id, Uint16 product_id); | ||
| 182 | extern void HIDAPI_SetDeviceSerial(SDL_HIDAPI_Device *device, const char *serial); | ||
| 183 | extern bool HIDAPI_HasConnectedUSBDevice(const char *serial); | ||
| 184 | extern void HIDAPI_DisconnectBluetoothDevice(const char *serial); | ||
| 185 | extern bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID); | ||
| 186 | extern void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID); | ||
| 187 | extern void HIDAPI_UpdateDeviceProperties(SDL_HIDAPI_Device *device); | ||
| 188 | |||
| 189 | extern void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size); | ||
| 190 | |||
| 191 | extern bool HIDAPI_SupportsPlaystationDetection(Uint16 vendor, Uint16 product); | ||
| 192 | |||
| 193 | extern float HIDAPI_RemapVal(float val, float val_min, float val_max, float output_min, float output_max); | ||
| 194 | |||
| 195 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 2021 Valve Corporation | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | #ifndef _CONTROLLER_CONSTANTS_ | ||
| 23 | #define _CONTROLLER_CONSTANTS_ | ||
| 24 | |||
| 25 | #include "controller_structs.h" | ||
| 26 | |||
| 27 | #ifdef __cplusplus | ||
| 28 | extern "C" { | ||
| 29 | #endif | ||
| 30 | |||
| 31 | #define FEATURE_REPORT_SIZE 64 | ||
| 32 | |||
| 33 | #define VALVE_USB_VID 0x28DE | ||
| 34 | |||
| 35 | // Frame update rate (in ms). | ||
| 36 | #define FAST_SCAN_INTERVAL 6 | ||
| 37 | #define SLOW_SCAN_INTERVAL 9 | ||
| 38 | |||
| 39 | // Contains each of the USB PIDs for Valve controllers (only add to this enum and never change the order) | ||
| 40 | enum ValveControllerPID | ||
| 41 | { | ||
| 42 | BASTILLE_PID = 0x2202, | ||
| 43 | CHELL_PID = 0x1101, | ||
| 44 | D0G_PID = 0x1102, | ||
| 45 | ELI_PID = 0x1103, | ||
| 46 | FREEMAN_PID = 0x1104, | ||
| 47 | D0G_BLE_PID = 0x1105, | ||
| 48 | D0G_BLE2_PID = 0x1106, | ||
| 49 | D0GGLE_PID = 0x1142, | ||
| 50 | |||
| 51 | JUPITER_PID = 0x1205, | ||
| 52 | }; | ||
| 53 | |||
| 54 | // This enum contains all of the messages exchanged between the host and the target (only add to this enum and never change the order) | ||
| 55 | enum FeatureReportMessageIDs | ||
| 56 | { | ||
| 57 | ID_SET_DIGITAL_MAPPINGS = 0x80, | ||
| 58 | ID_CLEAR_DIGITAL_MAPPINGS = 0x81, | ||
| 59 | ID_GET_DIGITAL_MAPPINGS = 0x82, | ||
| 60 | ID_GET_ATTRIBUTES_VALUES = 0x83, | ||
| 61 | ID_GET_ATTRIBUTE_LABEL = 0x84, | ||
| 62 | ID_SET_DEFAULT_DIGITAL_MAPPINGS = 0x85, | ||
| 63 | ID_FACTORY_RESET = 0x86, | ||
| 64 | ID_SET_SETTINGS_VALUES = 0x87, | ||
| 65 | ID_CLEAR_SETTINGS_VALUES = 0x88, | ||
| 66 | ID_GET_SETTINGS_VALUES = 0x89, | ||
| 67 | ID_GET_SETTING_LABEL = 0x8A, | ||
| 68 | ID_GET_SETTINGS_MAXS = 0x8B, | ||
| 69 | ID_GET_SETTINGS_DEFAULTS = 0x8C, | ||
| 70 | ID_SET_CONTROLLER_MODE = 0x8D, | ||
| 71 | ID_LOAD_DEFAULT_SETTINGS = 0x8E, | ||
| 72 | ID_TRIGGER_HAPTIC_PULSE = 0x8F, | ||
| 73 | |||
| 74 | ID_TURN_OFF_CONTROLLER = 0x9F, | ||
| 75 | |||
| 76 | ID_GET_DEVICE_INFO = 0xA1, | ||
| 77 | |||
| 78 | ID_CALIBRATE_TRACKPADS = 0xA7, | ||
| 79 | ID_RESERVED_0 = 0xA8, | ||
| 80 | ID_SET_SERIAL_NUMBER = 0xA9, | ||
| 81 | ID_GET_TRACKPAD_CALIBRATION = 0xAA, | ||
| 82 | ID_GET_TRACKPAD_FACTORY_CALIBRATION = 0xAB, | ||
| 83 | ID_GET_TRACKPAD_RAW_DATA = 0xAC, | ||
| 84 | ID_ENABLE_PAIRING = 0xAD, | ||
| 85 | ID_GET_STRING_ATTRIBUTE = 0xAE, | ||
| 86 | ID_RADIO_ERASE_RECORDS = 0xAF, | ||
| 87 | ID_RADIO_WRITE_RECORD = 0xB0, | ||
| 88 | ID_SET_DONGLE_SETTING = 0xB1, | ||
| 89 | ID_DONGLE_DISCONNECT_DEVICE = 0xB2, | ||
| 90 | ID_DONGLE_COMMIT_DEVICE = 0xB3, | ||
| 91 | ID_DONGLE_GET_WIRELESS_STATE = 0xB4, | ||
| 92 | ID_CALIBRATE_GYRO = 0xB5, | ||
| 93 | ID_PLAY_AUDIO = 0xB6, | ||
| 94 | ID_AUDIO_UPDATE_START = 0xB7, | ||
| 95 | ID_AUDIO_UPDATE_DATA = 0xB8, | ||
| 96 | ID_AUDIO_UPDATE_COMPLETE = 0xB9, | ||
| 97 | ID_GET_CHIPID = 0xBA, | ||
| 98 | |||
| 99 | ID_CALIBRATE_JOYSTICK = 0xBF, | ||
| 100 | ID_CALIBRATE_ANALOG_TRIGGERS = 0xC0, | ||
| 101 | ID_SET_AUDIO_MAPPING = 0xC1, | ||
| 102 | ID_CHECK_GYRO_FW_LOAD = 0xC2, | ||
| 103 | ID_CALIBRATE_ANALOG = 0xC3, | ||
| 104 | ID_DONGLE_GET_CONNECTED_SLOTS = 0xC4, | ||
| 105 | |||
| 106 | ID_RESET_IMU = 0xCE, | ||
| 107 | |||
| 108 | // Deck only | ||
| 109 | ID_TRIGGER_HAPTIC_CMD = 0xEA, | ||
| 110 | ID_TRIGGER_RUMBLE_CMD = 0xEB, | ||
| 111 | }; | ||
| 112 | |||
| 113 | |||
| 114 | // Enumeration of all wireless dongle events | ||
| 115 | typedef enum WirelessEventTypes | ||
| 116 | { | ||
| 117 | WIRELESS_EVENT_DISCONNECT = 1, | ||
| 118 | WIRELESS_EVENT_CONNECT = 2, | ||
| 119 | WIRELESS_EVENT_PAIR = 3, | ||
| 120 | } EWirelessEventType; | ||
| 121 | |||
| 122 | |||
| 123 | // 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) | ||
| 124 | typedef enum | ||
| 125 | { | ||
| 126 | IO_DIGITAL_BUTTON_NONE = -1, | ||
| 127 | IO_DIGITAL_BUTTON_RIGHT_TRIGGER, | ||
| 128 | IO_DIGITAL_BUTTON_LEFT_TRIGGER, | ||
| 129 | IO_DIGITAL_BUTTON_1, | ||
| 130 | IO_DIGITAL_BUTTON_Y=IO_DIGITAL_BUTTON_1, | ||
| 131 | IO_DIGITAL_BUTTON_2, | ||
| 132 | IO_DIGITAL_BUTTON_B=IO_DIGITAL_BUTTON_2, | ||
| 133 | IO_DIGITAL_BUTTON_3, | ||
| 134 | IO_DIGITAL_BUTTON_X=IO_DIGITAL_BUTTON_3, | ||
| 135 | IO_DIGITAL_BUTTON_4, | ||
| 136 | IO_DIGITAL_BUTTON_A=IO_DIGITAL_BUTTON_4, | ||
| 137 | IO_DIGITAL_BUTTON_RIGHT_BUMPER, | ||
| 138 | IO_DIGITAL_BUTTON_LEFT_BUMPER, | ||
| 139 | IO_DIGITAL_BUTTON_LEFT_JOYSTICK_CLICK, | ||
| 140 | IO_DIGITAL_BUTTON_ESCAPE, | ||
| 141 | IO_DIGITAL_BUTTON_STEAM, | ||
| 142 | IO_DIGITAL_BUTTON_MENU, | ||
| 143 | IO_DIGITAL_STICK_UP, | ||
| 144 | IO_DIGITAL_STICK_DOWN, | ||
| 145 | IO_DIGITAL_STICK_LEFT, | ||
| 146 | IO_DIGITAL_STICK_RIGHT, | ||
| 147 | IO_DIGITAL_TOUCH_1, | ||
| 148 | IO_DIGITAL_BUTTON_UP=IO_DIGITAL_TOUCH_1, | ||
| 149 | IO_DIGITAL_TOUCH_2, | ||
| 150 | IO_DIGITAL_BUTTON_RIGHT=IO_DIGITAL_TOUCH_2, | ||
| 151 | IO_DIGITAL_TOUCH_3, | ||
| 152 | IO_DIGITAL_BUTTON_LEFT=IO_DIGITAL_TOUCH_3, | ||
| 153 | IO_DIGITAL_TOUCH_4, | ||
| 154 | IO_DIGITAL_BUTTON_DOWN=IO_DIGITAL_TOUCH_4, | ||
| 155 | IO_DIGITAL_BUTTON_BACK_LEFT, | ||
| 156 | IO_DIGITAL_BUTTON_BACK_RIGHT, | ||
| 157 | IO_DIGITAL_LEFT_TRACKPAD_N, | ||
| 158 | IO_DIGITAL_LEFT_TRACKPAD_NE, | ||
| 159 | IO_DIGITAL_LEFT_TRACKPAD_E, | ||
| 160 | IO_DIGITAL_LEFT_TRACKPAD_SE, | ||
| 161 | IO_DIGITAL_LEFT_TRACKPAD_S, | ||
| 162 | IO_DIGITAL_LEFT_TRACKPAD_SW, | ||
| 163 | IO_DIGITAL_LEFT_TRACKPAD_W, | ||
| 164 | IO_DIGITAL_LEFT_TRACKPAD_NW, | ||
| 165 | IO_DIGITAL_RIGHT_TRACKPAD_N, | ||
| 166 | IO_DIGITAL_RIGHT_TRACKPAD_NE, | ||
| 167 | IO_DIGITAL_RIGHT_TRACKPAD_E, | ||
| 168 | IO_DIGITAL_RIGHT_TRACKPAD_SE, | ||
| 169 | IO_DIGITAL_RIGHT_TRACKPAD_S, | ||
| 170 | IO_DIGITAL_RIGHT_TRACKPAD_SW, | ||
| 171 | IO_DIGITAL_RIGHT_TRACKPAD_W, | ||
| 172 | IO_DIGITAL_RIGHT_TRACKPAD_NW, | ||
| 173 | IO_DIGITAL_LEFT_TRACKPAD_DOUBLE_TAP, | ||
| 174 | IO_DIGITAL_RIGHT_TRACKPAD_DOUBLE_TAP, | ||
| 175 | IO_DIGITAL_LEFT_TRACKPAD_OUTER_RADIUS, | ||
| 176 | IO_DIGITAL_RIGHT_TRACKPAD_OUTER_RADIUS, | ||
| 177 | IO_DIGITAL_LEFT_TRACKPAD_CLICK, | ||
| 178 | IO_DIGITAL_RIGHT_TRACKPAD_CLICK, | ||
| 179 | IO_DIGITAL_BATTERY_LOW, | ||
| 180 | IO_DIGITAL_LEFT_TRIGGER_THRESHOLD, | ||
| 181 | IO_DIGITAL_RIGHT_TRIGGER_THRESHOLD, | ||
| 182 | IO_DIGITAL_BUTTON_BACK_LEFT2, | ||
| 183 | IO_DIGITAL_BUTTON_BACK_RIGHT2, | ||
| 184 | IO_DIGITAL_BUTTON_ALWAYS_ON, | ||
| 185 | IO_DIGITAL_BUTTON_ANCILLARY_1, | ||
| 186 | IO_DIGITAL_BUTTON_MACRO_0, | ||
| 187 | IO_DIGITAL_BUTTON_MACRO_1, | ||
| 188 | IO_DIGITAL_BUTTON_MACRO_2, | ||
| 189 | IO_DIGITAL_BUTTON_MACRO_3, | ||
| 190 | IO_DIGITAL_BUTTON_MACRO_4, | ||
| 191 | IO_DIGITAL_BUTTON_MACRO_5, | ||
| 192 | IO_DIGITAL_BUTTON_MACRO_6, | ||
| 193 | IO_DIGITAL_BUTTON_MACRO_7, | ||
| 194 | IO_DIGITAL_BUTTON_MACRO_1FINGER, | ||
| 195 | IO_DIGITAL_BUTTON_MACRO_2FINGER, | ||
| 196 | IO_DIGITAL_COUNT | ||
| 197 | } DigitalIO ; | ||
| 198 | |||
| 199 | // 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) | ||
| 200 | typedef enum | ||
| 201 | { | ||
| 202 | IO_ANALOG_LEFT_STICK_X, | ||
| 203 | IO_ANALOG_LEFT_STICK_Y, | ||
| 204 | IO_ANALOG_RIGHT_STICK_X, | ||
| 205 | IO_ANALOG_RIGHT_STICK_Y, | ||
| 206 | IO_ANALOG_LEFT_TRIGGER, | ||
| 207 | IO_ANALOG_RIGHT_TRIGGER, | ||
| 208 | IO_MOUSE1_X, | ||
| 209 | IO_MOUSE1_Y, | ||
| 210 | IO_MOUSE1_Z, | ||
| 211 | IO_ACCEL_X, | ||
| 212 | IO_ACCEL_Y, | ||
| 213 | IO_ACCEL_Z, | ||
| 214 | IO_GYRO_X, | ||
| 215 | IO_GYRO_Y, | ||
| 216 | IO_GYRO_Z, | ||
| 217 | IO_GYRO_QUAT_W, | ||
| 218 | IO_GYRO_QUAT_X, | ||
| 219 | IO_GYRO_QUAT_Y, | ||
| 220 | IO_GYRO_QUAT_Z, | ||
| 221 | IO_GYRO_STEERING_VEC, | ||
| 222 | IO_RAW_TRIGGER_LEFT, | ||
| 223 | IO_RAW_TRIGGER_RIGHT, | ||
| 224 | IO_RAW_JOYSTICK_X, | ||
| 225 | IO_RAW_JOYSTICK_Y, | ||
| 226 | IO_GYRO_TILT_VEC, | ||
| 227 | IO_PRESSURE_LEFT_PAD, | ||
| 228 | IO_PRESSURE_RIGHT_PAD, | ||
| 229 | IO_PRESSURE_LEFT_BUMPER, | ||
| 230 | IO_PRESSURE_RIGHT_BUMPER, | ||
| 231 | IO_PRESSURE_LEFT_GRIP, | ||
| 232 | IO_PRESSURE_RIGHT_GRIP, | ||
| 233 | IO_ANALOG_LEFT_TRIGGER_THRESHOLD, | ||
| 234 | IO_ANALOG_RIGHT_TRIGGER_THRESHOLD, | ||
| 235 | IO_PRESSURE_RIGHT_PAD_THRESHOLD, | ||
| 236 | IO_PRESSURE_LEFT_PAD_THRESHOLD, | ||
| 237 | IO_PRESSURE_RIGHT_BUMPER_THRESHOLD, | ||
| 238 | IO_PRESSURE_LEFT_BUMPER_THRESHOLD, | ||
| 239 | IO_PRESSURE_RIGHT_GRIP_THRESHOLD, | ||
| 240 | IO_PRESSURE_LEFT_GRIP_THRESHOLD, | ||
| 241 | IO_PRESSURE_RIGHT_PAD_RAW, | ||
| 242 | IO_PRESSURE_LEFT_PAD_RAW, | ||
| 243 | IO_PRESSURE_RIGHT_BUMPER_RAW, | ||
| 244 | IO_PRESSURE_LEFT_BUMPER_RAW, | ||
| 245 | IO_PRESSURE_RIGHT_GRIP_RAW, | ||
| 246 | IO_PRESSURE_LEFT_GRIP_RAW, | ||
| 247 | IO_PRESSURE_RIGHT_GRIP2_THRESHOLD, | ||
| 248 | IO_PRESSURE_LEFT_GRIP2_THRESHOLD, | ||
| 249 | IO_PRESSURE_LEFT_GRIP2, | ||
| 250 | IO_PRESSURE_RIGHT_GRIP2, | ||
| 251 | IO_PRESSURE_RIGHT_GRIP2_RAW, | ||
| 252 | IO_PRESSURE_LEFT_GRIP2_RAW, | ||
| 253 | IO_ANALOG_COUNT | ||
| 254 | } AnalogIO; | ||
| 255 | |||
| 256 | |||
| 257 | // Contains list of all types of devices that the controller emulates (only add to this enum and never change the order) | ||
| 258 | enum DeviceTypes | ||
| 259 | { | ||
| 260 | DEVICE_KEYBOARD, | ||
| 261 | DEVICE_MOUSE, | ||
| 262 | DEVICE_GAMEPAD, | ||
| 263 | DEVICE_MODE_ADJUST, | ||
| 264 | DEVICE_COUNT | ||
| 265 | }; | ||
| 266 | |||
| 267 | // Scan codes for HID keyboards | ||
| 268 | enum HIDKeyboardKeys | ||
| 269 | { | ||
| 270 | KEY_INVALID, | ||
| 271 | KEY_FIRST = 0x04, | ||
| 272 | 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, | ||
| 273 | 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, | ||
| 274 | 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, | ||
| 275 | 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, | ||
| 276 | 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, | ||
| 277 | 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, | ||
| 278 | KEY_KEYPAD_8, KEY_KEYPAD_9, KEY_KEYPAD_0, KEY_KEYPAD_PERIOD, | ||
| 279 | KEY_LALT, | ||
| 280 | KEY_LSHIFT, | ||
| 281 | KEY_LWIN, | ||
| 282 | KEY_LCONTROL, | ||
| 283 | KEY_RALT, | ||
| 284 | KEY_RSHIFT, | ||
| 285 | KEY_RWIN, | ||
| 286 | KEY_RCONTROL, | ||
| 287 | KEY_VOLUP, | ||
| 288 | KEY_VOLDOWN, | ||
| 289 | KEY_MUTE, | ||
| 290 | KEY_PLAY, | ||
| 291 | KEY_STOP, | ||
| 292 | KEY_NEXT, | ||
| 293 | KEY_PREV, | ||
| 294 | KEY_LAST = KEY_PREV | ||
| 295 | }; | ||
| 296 | |||
| 297 | enum ModifierMasks | ||
| 298 | { | ||
| 299 | KEY_LCONTROL_MASK = (1<<0), | ||
| 300 | KEY_LSHIFT_MASK = (1<<1), | ||
| 301 | KEY_LALT_MASK = (1<<2), | ||
| 302 | KEY_LWIN_MASK = (1<<3), | ||
| 303 | KEY_RCONTROL_MASK = (1<<4), | ||
| 304 | KEY_RSHIFT_MASK = (1<<5), | ||
| 305 | KEY_RALT_MASK = (1<<6), | ||
| 306 | KEY_RWIN_MASK = (1<<7) | ||
| 307 | }; | ||
| 308 | |||
| 309 | // Standard mouse buttons as specified in the HID mouse spec | ||
| 310 | enum MouseButtons | ||
| 311 | { | ||
| 312 | MOUSE_BTN_LEFT, | ||
| 313 | MOUSE_BTN_RIGHT, | ||
| 314 | MOUSE_BTN_MIDDLE, | ||
| 315 | MOUSE_BTN_BACK, | ||
| 316 | MOUSE_BTN_FORWARD, | ||
| 317 | MOUSE_SCROLL_UP, | ||
| 318 | MOUSE_SCROLL_DOWN, | ||
| 319 | MOUSE_BTN_COUNT | ||
| 320 | }; | ||
| 321 | |||
| 322 | // Gamepad buttons | ||
| 323 | enum GamepadButtons | ||
| 324 | { | ||
| 325 | GAMEPAD_BTN_TRIGGER_LEFT=1, | ||
| 326 | GAMEPAD_BTN_TRIGGER_RIGHT, | ||
| 327 | GAMEPAD_BTN_A, | ||
| 328 | GAMEPAD_BTN_B, | ||
| 329 | GAMEPAD_BTN_Y, | ||
| 330 | GAMEPAD_BTN_X, | ||
| 331 | GAMEPAD_BTN_SHOULDER_LEFT, | ||
| 332 | GAMEPAD_BTN_SHOULDER_RIGHT, | ||
| 333 | GAMEPAD_BTN_LEFT_JOYSTICK, | ||
| 334 | GAMEPAD_BTN_RIGHT_JOYSTICK, | ||
| 335 | GAMEPAD_BTN_START, | ||
| 336 | GAMEPAD_BTN_SELECT, | ||
| 337 | GAMEPAD_BTN_STEAM, | ||
| 338 | GAMEPAD_BTN_DPAD_UP, | ||
| 339 | GAMEPAD_BTN_DPAD_DOWN, | ||
| 340 | GAMEPAD_BTN_DPAD_LEFT, | ||
| 341 | GAMEPAD_BTN_DPAD_RIGHT, | ||
| 342 | GAMEPAD_BTN_LSTICK_UP, | ||
| 343 | GAMEPAD_BTN_LSTICK_DOWN, | ||
| 344 | GAMEPAD_BTN_LSTICK_LEFT, | ||
| 345 | GAMEPAD_BTN_LSTICK_RIGHT, | ||
| 346 | GAMEPAD_BTN_RSTICK_UP, | ||
| 347 | GAMEPAD_BTN_RSTICK_DOWN, | ||
| 348 | GAMEPAD_BTN_RSTICK_LEFT, | ||
| 349 | GAMEPAD_BTN_RSTICK_RIGHT, | ||
| 350 | GAMEPAD_BTN_COUNT | ||
| 351 | }; | ||
| 352 | |||
| 353 | // Mode adjust | ||
| 354 | enum ModeAdjustModes | ||
| 355 | { | ||
| 356 | MODE_ADJUST_SENSITITY=1, | ||
| 357 | MODE_ADJUST_LEFT_PAD_SECONDARY_MODE, | ||
| 358 | MODE_ADJUST_RIGHT_PAD_SECONDARY_MODE, | ||
| 359 | MODE_ADJUST_COUNT | ||
| 360 | }; | ||
| 361 | |||
| 362 | // Read-only attributes of controllers (only add to this enum and never change the order) | ||
| 363 | typedef enum | ||
| 364 | { | ||
| 365 | ATTRIB_UNIQUE_ID, | ||
| 366 | ATTRIB_PRODUCT_ID, | ||
| 367 | ATTRIB_PRODUCT_REVISON, // deprecated | ||
| 368 | ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISON, // intentional aliasing | ||
| 369 | ATTRIB_FIRMWARE_VERSION, // deprecated | ||
| 370 | ATTRIB_FIRMWARE_BUILD_TIME, | ||
| 371 | ATTRIB_RADIO_FIRMWARE_BUILD_TIME, | ||
| 372 | ATTRIB_RADIO_DEVICE_ID0, | ||
| 373 | ATTRIB_RADIO_DEVICE_ID1, | ||
| 374 | ATTRIB_DONGLE_FIRMWARE_BUILD_TIME, | ||
| 375 | ATTRIB_BOARD_REVISION, | ||
| 376 | ATTRIB_BOOTLOADER_BUILD_TIME, | ||
| 377 | ATTRIB_CONNECTION_INTERVAL_IN_US, | ||
| 378 | ATTRIB_COUNT | ||
| 379 | } ControllerAttributes; | ||
| 380 | |||
| 381 | // Read-only string attributes of controllers (only add to this enum and never change the order) | ||
| 382 | typedef enum | ||
| 383 | { | ||
| 384 | ATTRIB_STR_BOARD_SERIAL, | ||
| 385 | ATTRIB_STR_UNIT_SERIAL, | ||
| 386 | ATTRIB_STR_COUNT | ||
| 387 | } ControllerStringAttributes; | ||
| 388 | |||
| 389 | typedef enum | ||
| 390 | { | ||
| 391 | STATUS_CODE_NORMAL, | ||
| 392 | STATUS_CODE_CRITICAL_BATTERY, | ||
| 393 | STATUS_CODE_GYRO_INIT_ERROR, | ||
| 394 | } ControllerStatusEventCodes; | ||
| 395 | |||
| 396 | typedef enum | ||
| 397 | { | ||
| 398 | STATUS_STATE_LOW_BATTERY=0, | ||
| 399 | } ControllerStatusStateFlags; | ||
| 400 | |||
| 401 | typedef enum { | ||
| 402 | TRACKPAD_ABSOLUTE_MOUSE, | ||
| 403 | TRACKPAD_RELATIVE_MOUSE, | ||
| 404 | TRACKPAD_DPAD_FOUR_WAY_DISCRETE, | ||
| 405 | TRACKPAD_DPAD_FOUR_WAY_OVERLAP, | ||
| 406 | TRACKPAD_DPAD_EIGHT_WAY, | ||
| 407 | TRACKPAD_RADIAL_MODE, | ||
| 408 | TRACKPAD_ABSOLUTE_DPAD, | ||
| 409 | TRACKPAD_NONE, | ||
| 410 | TRACKPAD_GESTURE_KEYBOARD, | ||
| 411 | TRACKPAD_NUM_MODES | ||
| 412 | } TrackpadDPadMode; | ||
| 413 | |||
| 414 | // Read-write controller settings (only add to this enum and never change the order) | ||
| 415 | typedef enum | ||
| 416 | { | ||
| 417 | SETTING_MOUSE_SENSITIVITY, | ||
| 418 | SETTING_MOUSE_ACCELERATION, | ||
| 419 | SETTING_TRACKBALL_ROTATION_ANGLE, | ||
| 420 | SETTING_HAPTIC_INTENSITY_UNUSED, | ||
| 421 | SETTING_LEFT_GAMEPAD_STICK_ENABLED, | ||
| 422 | SETTING_RIGHT_GAMEPAD_STICK_ENABLED, | ||
| 423 | SETTING_USB_DEBUG_MODE, | ||
| 424 | SETTING_LEFT_TRACKPAD_MODE, | ||
| 425 | SETTING_RIGHT_TRACKPAD_MODE, | ||
| 426 | SETTING_MOUSE_POINTER_ENABLED, | ||
| 427 | |||
| 428 | // 10 | ||
| 429 | SETTING_DPAD_DEADZONE, | ||
| 430 | SETTING_MINIMUM_MOMENTUM_VEL, | ||
| 431 | SETTING_MOMENTUM_DECAY_AMOUNT, | ||
| 432 | SETTING_TRACKPAD_RELATIVE_MODE_TICKS_PER_PIXEL, | ||
| 433 | SETTING_HAPTIC_INCREMENT, | ||
| 434 | SETTING_DPAD_ANGLE_SIN, | ||
| 435 | SETTING_DPAD_ANGLE_COS, | ||
| 436 | SETTING_MOMENTUM_VERTICAL_DIVISOR, | ||
| 437 | SETTING_MOMENTUM_MAXIMUM_VELOCITY, | ||
| 438 | SETTING_TRACKPAD_Z_ON, | ||
| 439 | |||
| 440 | // 20 | ||
| 441 | SETTING_TRACKPAD_Z_OFF, | ||
| 442 | SETTING_SENSITIVITY_SCALE_AMOUNT, | ||
| 443 | SETTING_LEFT_TRACKPAD_SECONDARY_MODE, | ||
| 444 | SETTING_RIGHT_TRACKPAD_SECONDARY_MODE, | ||
| 445 | SETTING_SMOOTH_ABSOLUTE_MOUSE, | ||
| 446 | SETTING_STEAMBUTTON_POWEROFF_TIME, | ||
| 447 | SETTING_UNUSED_1, | ||
| 448 | SETTING_TRACKPAD_OUTER_RADIUS, | ||
| 449 | SETTING_TRACKPAD_Z_ON_LEFT, | ||
| 450 | SETTING_TRACKPAD_Z_OFF_LEFT, | ||
| 451 | |||
| 452 | // 30 | ||
| 453 | SETTING_TRACKPAD_OUTER_SPIN_VEL, | ||
| 454 | SETTING_TRACKPAD_OUTER_SPIN_RADIUS, | ||
| 455 | SETTING_TRACKPAD_OUTER_SPIN_HORIZONTAL_ONLY, | ||
| 456 | SETTING_TRACKPAD_RELATIVE_MODE_DEADZONE, | ||
| 457 | SETTING_TRACKPAD_RELATIVE_MODE_MAX_VEL, | ||
| 458 | SETTING_TRACKPAD_RELATIVE_MODE_INVERT_Y, | ||
| 459 | SETTING_TRACKPAD_DOUBLE_TAP_BEEP_ENABLED, | ||
| 460 | SETTING_TRACKPAD_DOUBLE_TAP_BEEP_PERIOD, | ||
| 461 | SETTING_TRACKPAD_DOUBLE_TAP_BEEP_COUNT, | ||
| 462 | SETTING_TRACKPAD_OUTER_RADIUS_RELEASE_ON_TRANSITION, | ||
| 463 | |||
| 464 | // 40 | ||
| 465 | SETTING_RADIAL_MODE_ANGLE, | ||
| 466 | SETTING_HAPTIC_INTENSITY_MOUSE_MODE, | ||
| 467 | SETTING_LEFT_DPAD_REQUIRES_CLICK, | ||
| 468 | SETTING_RIGHT_DPAD_REQUIRES_CLICK, | ||
| 469 | SETTING_LED_BASELINE_BRIGHTNESS, | ||
| 470 | SETTING_LED_USER_BRIGHTNESS, | ||
| 471 | SETTING_ENABLE_RAW_JOYSTICK, | ||
| 472 | SETTING_ENABLE_FAST_SCAN, | ||
| 473 | SETTING_IMU_MODE, | ||
| 474 | SETTING_WIRELESS_PACKET_VERSION, | ||
| 475 | |||
| 476 | // 50 | ||
| 477 | SETTING_SLEEP_INACTIVITY_TIMEOUT, | ||
| 478 | SETTING_TRACKPAD_NOISE_THRESHOLD, | ||
| 479 | SETTING_LEFT_TRACKPAD_CLICK_PRESSURE, | ||
| 480 | SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE, | ||
| 481 | SETTING_LEFT_BUMPER_CLICK_PRESSURE, | ||
| 482 | SETTING_RIGHT_BUMPER_CLICK_PRESSURE, | ||
| 483 | SETTING_LEFT_GRIP_CLICK_PRESSURE, | ||
| 484 | SETTING_RIGHT_GRIP_CLICK_PRESSURE, | ||
| 485 | SETTING_LEFT_GRIP2_CLICK_PRESSURE, | ||
| 486 | SETTING_RIGHT_GRIP2_CLICK_PRESSURE, | ||
| 487 | |||
| 488 | // 60 | ||
| 489 | SETTING_PRESSURE_MODE, | ||
| 490 | SETTING_CONTROLLER_TEST_MODE, | ||
| 491 | SETTING_TRIGGER_MODE, | ||
| 492 | SETTING_TRACKPAD_Z_THRESHOLD, | ||
| 493 | SETTING_FRAME_RATE, | ||
| 494 | SETTING_TRACKPAD_FILT_CTRL, | ||
| 495 | SETTING_TRACKPAD_CLIP, | ||
| 496 | SETTING_DEBUG_OUTPUT_SELECT, | ||
| 497 | SETTING_TRIGGER_THRESHOLD_PERCENT, | ||
| 498 | SETTING_TRACKPAD_FREQUENCY_HOPPING, | ||
| 499 | |||
| 500 | // 70 | ||
| 501 | SETTING_HAPTICS_ENABLED, | ||
| 502 | SETTING_STEAM_WATCHDOG_ENABLE, | ||
| 503 | SETTING_TIMP_TOUCH_THRESHOLD_ON, | ||
| 504 | SETTING_TIMP_TOUCH_THRESHOLD_OFF, | ||
| 505 | SETTING_FREQ_HOPPING, | ||
| 506 | SETTING_TEST_CONTROL, | ||
| 507 | SETTING_HAPTIC_MASTER_GAIN_DB, | ||
| 508 | SETTING_THUMB_TOUCH_THRESH, | ||
| 509 | SETTING_DEVICE_POWER_STATUS, | ||
| 510 | SETTING_HAPTIC_INTENSITY, | ||
| 511 | |||
| 512 | // 80 | ||
| 513 | SETTING_STABILIZER_ENABLED, | ||
| 514 | SETTING_TIMP_MODE_MTE, | ||
| 515 | SETTING_COUNT, | ||
| 516 | |||
| 517 | // This is a special setting value use for callbacks and should not be set/get explicitly. | ||
| 518 | SETTING_ALL=0xFF | ||
| 519 | } ControllerSettings; | ||
| 520 | |||
| 521 | typedef enum | ||
| 522 | { | ||
| 523 | SETTING_DEFAULT, | ||
| 524 | SETTING_MIN, | ||
| 525 | SETTING_MAX, | ||
| 526 | SETTING_DEFAULTMINMAXCOUNT | ||
| 527 | } SettingDefaultMinMax; | ||
| 528 | |||
| 529 | // Bitmask that define which IMU features to enable. | ||
| 530 | typedef enum | ||
| 531 | { | ||
| 532 | SETTING_GYRO_MODE_OFF = 0x0000, | ||
| 533 | SETTING_GYRO_MODE_STEERING = 0x0001, | ||
| 534 | SETTING_GYRO_MODE_TILT = 0x0002, | ||
| 535 | SETTING_GYRO_MODE_SEND_ORIENTATION = 0x0004, | ||
| 536 | SETTING_GYRO_MODE_SEND_RAW_ACCEL = 0x0008, | ||
| 537 | SETTING_GYRO_MODE_SEND_RAW_GYRO = 0x0010, | ||
| 538 | } SettingGyroMode; | ||
| 539 | |||
| 540 | // Bitmask for haptic pulse flags | ||
| 541 | typedef enum | ||
| 542 | { | ||
| 543 | HAPTIC_PULSE_NORMAL = 0x0000, | ||
| 544 | HAPTIC_PULSE_HIGH_PRIORITY = 0x0001, | ||
| 545 | HAPTIC_PULSE_VERY_HIGH_PRIORITY = 0x0002, | ||
| 546 | HAPTIC_PULSE_IGNORE_USER_PREFS = 0x0003, | ||
| 547 | } SettingHapticPulseFlags; | ||
| 548 | |||
| 549 | typedef struct | ||
| 550 | { | ||
| 551 | // default,min,max in this array in that order | ||
| 552 | short defaultminmax[SETTING_DEFAULTMINMAXCOUNT]; | ||
| 553 | } SettingValueRange_t; | ||
| 554 | |||
| 555 | // below is from controller_constants.c which should be compiled into any code that uses this | ||
| 556 | extern const SettingValueRange_t g_DefaultSettingValues[SETTING_COUNT]; | ||
| 557 | |||
| 558 | // Read-write settings for dongle (only add to this enum and never change the order) | ||
| 559 | typedef enum | ||
| 560 | { | ||
| 561 | DONGLE_SETTING_MOUSE_KEYBOARD_ENABLED, | ||
| 562 | DONGLE_SETTING_COUNT, | ||
| 563 | } DongleSettings; | ||
| 564 | |||
| 565 | typedef enum | ||
| 566 | { | ||
| 567 | AUDIO_STARTUP = 0, | ||
| 568 | AUDIO_SHUTDOWN = 1, | ||
| 569 | AUDIO_PAIR = 2, | ||
| 570 | AUDIO_PAIR_SUCCESS = 3, | ||
| 571 | AUDIO_IDENTIFY = 4, | ||
| 572 | AUDIO_LIZARDMODE = 5, | ||
| 573 | AUDIO_NORMALMODE = 6, | ||
| 574 | |||
| 575 | AUDIO_MAX_SLOT = 15 | ||
| 576 | } ControllerAudio; | ||
| 577 | |||
| 578 | #ifdef __cplusplus | ||
| 579 | } | ||
| 580 | #endif | ||
| 581 | |||
| 582 | #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 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 2020 Valve Corporation | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #ifndef _CONTROLLER_STRUCTS_ | ||
| 22 | #define _CONTROLLER_STRUCTS_ | ||
| 23 | |||
| 24 | #pragma pack(1) | ||
| 25 | |||
| 26 | #define HID_FEATURE_REPORT_BYTES 64 | ||
| 27 | |||
| 28 | // Header for all host <==> target messages | ||
| 29 | typedef struct | ||
| 30 | { | ||
| 31 | unsigned char type; | ||
| 32 | unsigned char length; | ||
| 33 | } FeatureReportHeader; | ||
| 34 | |||
| 35 | // Generic controller settings structure | ||
| 36 | typedef struct | ||
| 37 | { | ||
| 38 | unsigned char settingNum; | ||
| 39 | unsigned short settingValue; | ||
| 40 | } ControllerSetting; | ||
| 41 | |||
| 42 | // Generic controller attribute structure | ||
| 43 | typedef struct | ||
| 44 | { | ||
| 45 | unsigned char attributeTag; | ||
| 46 | uint32_t attributeValue; | ||
| 47 | } ControllerAttribute; | ||
| 48 | |||
| 49 | // Generic controller settings structure | ||
| 50 | typedef struct | ||
| 51 | { | ||
| 52 | ControllerSetting settings[ ( HID_FEATURE_REPORT_BYTES - sizeof( FeatureReportHeader ) ) / sizeof( ControllerSetting ) ]; | ||
| 53 | } MsgSetSettingsValues, MsgGetSettingsValues, MsgGetSettingsDefaults, MsgGetSettingsMaxs; | ||
| 54 | |||
| 55 | // Generic controller settings structure | ||
| 56 | typedef struct | ||
| 57 | { | ||
| 58 | ControllerAttribute attributes[ ( HID_FEATURE_REPORT_BYTES - sizeof( FeatureReportHeader ) ) / sizeof( ControllerAttribute ) ]; | ||
| 59 | } MsgGetAttributes; | ||
| 60 | |||
| 61 | typedef struct | ||
| 62 | { | ||
| 63 | unsigned char attributeTag; | ||
| 64 | char attributeValue[20]; | ||
| 65 | } MsgGetStringAttribute; | ||
| 66 | |||
| 67 | typedef struct | ||
| 68 | { | ||
| 69 | unsigned char mode; | ||
| 70 | } MsgSetControllerMode; | ||
| 71 | |||
| 72 | // Trigger a haptic pulse | ||
| 73 | typedef struct { | ||
| 74 | unsigned char which_pad; | ||
| 75 | unsigned short pulse_duration; | ||
| 76 | unsigned short pulse_interval; | ||
| 77 | unsigned short pulse_count; | ||
| 78 | short dBgain; | ||
| 79 | unsigned char priority; | ||
| 80 | } MsgFireHapticPulse; | ||
| 81 | |||
| 82 | typedef struct { | ||
| 83 | uint8_t mode; | ||
| 84 | } MsgHapticSetMode; | ||
| 85 | |||
| 86 | typedef enum { | ||
| 87 | HAPTIC_TYPE_OFF, | ||
| 88 | HAPTIC_TYPE_TICK, | ||
| 89 | HAPTIC_TYPE_CLICK, | ||
| 90 | HAPTIC_TYPE_TONE, | ||
| 91 | HAPTIC_TYPE_RUMBLE, | ||
| 92 | HAPTIC_TYPE_NOISE, | ||
| 93 | HAPTIC_TYPE_SCRIPT, | ||
| 94 | HAPTIC_TYPE_LOG_SWEEP, | ||
| 95 | } haptic_type_t; | ||
| 96 | |||
| 97 | typedef enum { | ||
| 98 | HAPTIC_INTENSITY_SYSTEM, | ||
| 99 | HAPTIC_INTENSITY_SHORT, | ||
| 100 | HAPTIC_INTENSITY_MEDIUM, | ||
| 101 | HAPTIC_INTENSITY_LONG, | ||
| 102 | HAPTIC_INTENSITY_INSANE, | ||
| 103 | } haptic_intensity_t; | ||
| 104 | |||
| 105 | typedef struct { | ||
| 106 | uint8_t side; // 0x01 = L, 0x02 = R, 0x03 = Both | ||
| 107 | uint8_t cmd; // 0 = Off, 1 = tick, 2 = click, 3 = tone, 4 = rumble, 5 = | ||
| 108 | // rumble_noise, 6 = script, 7 = sweep, | ||
| 109 | uint8_t ui_intensity; // 0-4 (0 = default) | ||
| 110 | int8_t dBgain; // dB Can be positive (reasonable clipping / limiting will apply) | ||
| 111 | uint16_t freq; // Frequency of tone (if applicable) | ||
| 112 | int16_t dur_ms; // Duration of tone / rumble (if applicable) (neg = infinite) | ||
| 113 | |||
| 114 | uint16_t noise_intensity; | ||
| 115 | uint16_t lfo_freq; // Drives both tone and rumble geneators | ||
| 116 | uint8_t lfo_depth; // percentage, typically 100 | ||
| 117 | uint8_t rand_tone_gain; // Randomize each LFO cycle's gain | ||
| 118 | uint8_t script_id; // Used w/ dBgain for scripted haptics | ||
| 119 | |||
| 120 | uint16_t lss_start_freq; // Used w/ Log Sine Sweep | ||
| 121 | uint16_t lss_end_freq; // Ditto | ||
| 122 | } MsgTriggerHaptic; | ||
| 123 | |||
| 124 | typedef struct { | ||
| 125 | uint8_t unRumbleType; | ||
| 126 | uint16_t unIntensity; | ||
| 127 | uint16_t unLeftMotorSpeed; | ||
| 128 | uint16_t unRightMotorSpeed; | ||
| 129 | int8_t nLeftGain; | ||
| 130 | int8_t nRightGain; | ||
| 131 | } MsgSimpleRumbleCmd; | ||
| 132 | |||
| 133 | // This is the only message struct that application code should use to interact with feature request messages. Any new | ||
| 134 | // messages should be added to the union. The structures defined here should correspond to the ones defined in | ||
| 135 | // ValveDeviceCore.cpp. | ||
| 136 | // | ||
| 137 | typedef struct | ||
| 138 | { | ||
| 139 | FeatureReportHeader header; | ||
| 140 | union | ||
| 141 | { | ||
| 142 | MsgSetSettingsValues setSettingsValues; | ||
| 143 | MsgGetSettingsValues getSettingsValues; | ||
| 144 | MsgGetSettingsMaxs getSettingsMaxs; | ||
| 145 | MsgGetSettingsDefaults getSettingsDefaults; | ||
| 146 | MsgGetAttributes getAttributes; | ||
| 147 | MsgSetControllerMode controllerMode; | ||
| 148 | MsgFireHapticPulse fireHapticPulse; | ||
| 149 | MsgGetStringAttribute getStringAttribute; | ||
| 150 | MsgHapticSetMode hapticMode; | ||
| 151 | MsgTriggerHaptic triggerHaptic; | ||
| 152 | MsgSimpleRumbleCmd simpleRumble; | ||
| 153 | } payload; | ||
| 154 | |||
| 155 | } FeatureReportMsg; | ||
| 156 | |||
| 157 | // Roll this version forward anytime that you are breaking compatibility of existing | ||
| 158 | // message types within ValveInReport_t or the header itself. Hopefully this should | ||
| 159 | // be super rare and instead you should just add new message payloads to the union, | ||
| 160 | // or just add fields to the end of existing payload structs which is expected to be | ||
| 161 | // safe in all code consuming these as they should just consume/copy up to the prior size | ||
| 162 | // they were aware of when processing. | ||
| 163 | #define k_ValveInReportMsgVersion 0x01 | ||
| 164 | |||
| 165 | typedef enum | ||
| 166 | { | ||
| 167 | ID_CONTROLLER_STATE = 1, | ||
| 168 | ID_CONTROLLER_DEBUG = 2, | ||
| 169 | ID_CONTROLLER_WIRELESS = 3, | ||
| 170 | ID_CONTROLLER_STATUS = 4, | ||
| 171 | ID_CONTROLLER_DEBUG2 = 5, | ||
| 172 | ID_CONTROLLER_SECONDARY_STATE = 6, | ||
| 173 | ID_CONTROLLER_BLE_STATE = 7, | ||
| 174 | ID_CONTROLLER_DECK_STATE = 9, | ||
| 175 | ID_CONTROLLER_MSG_COUNT | ||
| 176 | } ValveInReportMessageIDs; | ||
| 177 | |||
| 178 | typedef struct | ||
| 179 | { | ||
| 180 | unsigned short unReportVersion; | ||
| 181 | |||
| 182 | unsigned char ucType; | ||
| 183 | unsigned char ucLength; | ||
| 184 | |||
| 185 | } ValveInReportHeader_t; | ||
| 186 | |||
| 187 | // State payload | ||
| 188 | typedef struct | ||
| 189 | { | ||
| 190 | // If packet num matches that on your prior call, then the controller state hasn't been changed since | ||
| 191 | // your last call and there is no need to process it | ||
| 192 | Uint32 unPacketNum; | ||
| 193 | |||
| 194 | // Button bitmask and trigger data. | ||
| 195 | union | ||
| 196 | { | ||
| 197 | Uint64 ulButtons; | ||
| 198 | struct | ||
| 199 | { | ||
| 200 | unsigned char _pad0[3]; | ||
| 201 | unsigned char nLeft; | ||
| 202 | unsigned char nRight; | ||
| 203 | unsigned char _pad1[3]; | ||
| 204 | } Triggers; | ||
| 205 | } ButtonTriggerData; | ||
| 206 | |||
| 207 | // Left pad coordinates | ||
| 208 | short sLeftPadX; | ||
| 209 | short sLeftPadY; | ||
| 210 | |||
| 211 | // Right pad coordinates | ||
| 212 | short sRightPadX; | ||
| 213 | short sRightPadY; | ||
| 214 | |||
| 215 | // This is redundant, packed above, but still sent over wired | ||
| 216 | unsigned short sTriggerL; | ||
| 217 | unsigned short sTriggerR; | ||
| 218 | |||
| 219 | // FIXME figure out a way to grab this stuff over wireless | ||
| 220 | short sAccelX; | ||
| 221 | short sAccelY; | ||
| 222 | short sAccelZ; | ||
| 223 | |||
| 224 | short sGyroX; | ||
| 225 | short sGyroY; | ||
| 226 | short sGyroZ; | ||
| 227 | |||
| 228 | short sGyroQuatW; | ||
| 229 | short sGyroQuatX; | ||
| 230 | short sGyroQuatY; | ||
| 231 | short sGyroQuatZ; | ||
| 232 | |||
| 233 | } ValveControllerStatePacket_t; | ||
| 234 | |||
| 235 | // BLE State payload this has to be re-formatted from the normal state because BLE controller shows up as | ||
| 236 | //a HID device and we don't want to send all the optional parts of the message. Keep in sync with struct above. | ||
| 237 | typedef struct | ||
| 238 | { | ||
| 239 | // If packet num matches that on your prior call, then the controller state hasn't been changed since | ||
| 240 | // your last call and there is no need to process it | ||
| 241 | Uint32 unPacketNum; | ||
| 242 | |||
| 243 | // Button bitmask and trigger data. | ||
| 244 | union | ||
| 245 | { | ||
| 246 | Uint64 ulButtons; | ||
| 247 | struct | ||
| 248 | { | ||
| 249 | unsigned char _pad0[3]; | ||
| 250 | unsigned char nLeft; | ||
| 251 | unsigned char nRight; | ||
| 252 | unsigned char _pad1[3]; | ||
| 253 | } Triggers; | ||
| 254 | } ButtonTriggerData; | ||
| 255 | |||
| 256 | // Left pad coordinates | ||
| 257 | short sLeftPadX; | ||
| 258 | short sLeftPadY; | ||
| 259 | |||
| 260 | // Right pad coordinates | ||
| 261 | short sRightPadX; | ||
| 262 | short sRightPadY; | ||
| 263 | |||
| 264 | //This mimcs how the dongle reconstitutes HID packets, there will be 0-4 shorts depending on gyro mode | ||
| 265 | unsigned char ucGyroDataType; //TODO could maybe find some unused bits in the button field for this info (is only 2bits) | ||
| 266 | short sGyro[4]; | ||
| 267 | |||
| 268 | } ValveControllerBLEStatePacket_t; | ||
| 269 | |||
| 270 | // Define a payload for reporting debug information | ||
| 271 | typedef struct | ||
| 272 | { | ||
| 273 | // Left pad coordinates | ||
| 274 | short sLeftPadX; | ||
| 275 | short sLeftPadY; | ||
| 276 | |||
| 277 | // Right pad coordinates | ||
| 278 | short sRightPadX; | ||
| 279 | short sRightPadY; | ||
| 280 | |||
| 281 | // Left mouse deltas | ||
| 282 | short sLeftPadMouseDX; | ||
| 283 | short sLeftPadMouseDY; | ||
| 284 | |||
| 285 | // Right mouse deltas | ||
| 286 | short sRightPadMouseDX; | ||
| 287 | short sRightPadMouseDY; | ||
| 288 | |||
| 289 | // Left mouse filtered deltas | ||
| 290 | short sLeftPadMouseFilteredDX; | ||
| 291 | short sLeftPadMouseFilteredDY; | ||
| 292 | |||
| 293 | // Right mouse filtered deltas | ||
| 294 | short sRightPadMouseFilteredDX; | ||
| 295 | short sRightPadMouseFilteredDY; | ||
| 296 | |||
| 297 | // Pad Z values | ||
| 298 | unsigned char ucLeftZ; | ||
| 299 | unsigned char ucRightZ; | ||
| 300 | |||
| 301 | // FingerPresent | ||
| 302 | unsigned char ucLeftFingerPresent; | ||
| 303 | unsigned char ucRightFingerPresent; | ||
| 304 | |||
| 305 | // Timestamps | ||
| 306 | unsigned char ucLeftTimestamp; | ||
| 307 | unsigned char ucRightTimestamp; | ||
| 308 | |||
| 309 | // Double tap state | ||
| 310 | unsigned char ucLeftTapState; | ||
| 311 | unsigned char ucRightTapState; | ||
| 312 | |||
| 313 | unsigned int unDigitalIOStates0; | ||
| 314 | unsigned int unDigitalIOStates1; | ||
| 315 | |||
| 316 | } ValveControllerDebugPacket_t; | ||
| 317 | |||
| 318 | typedef struct | ||
| 319 | { | ||
| 320 | unsigned char ucPadNum; | ||
| 321 | unsigned char ucPad[3]; // need Data to be word aligned | ||
| 322 | short Data[20]; | ||
| 323 | unsigned short unNoise; | ||
| 324 | } ValveControllerTrackpadImage_t; | ||
| 325 | |||
| 326 | typedef struct | ||
| 327 | { | ||
| 328 | unsigned char ucPadNum; | ||
| 329 | unsigned char ucOffset; | ||
| 330 | unsigned char ucPad[2]; // need Data to be word aligned | ||
| 331 | short rgData[28]; | ||
| 332 | } ValveControllerRawTrackpadImage_t; | ||
| 333 | |||
| 334 | // Payload for wireless metadata | ||
| 335 | typedef struct | ||
| 336 | { | ||
| 337 | unsigned char ucEventType; | ||
| 338 | } SteamControllerWirelessEvent_t; | ||
| 339 | |||
| 340 | typedef struct | ||
| 341 | { | ||
| 342 | // Current packet number. | ||
| 343 | unsigned int unPacketNum; | ||
| 344 | |||
| 345 | // Event codes and state information. | ||
| 346 | unsigned short sEventCode; | ||
| 347 | unsigned short unStateFlags; | ||
| 348 | |||
| 349 | // Current battery voltage (mV). | ||
| 350 | unsigned short sBatteryVoltage; | ||
| 351 | |||
| 352 | // Current battery level (0-100). | ||
| 353 | unsigned char ucBatteryLevel; | ||
| 354 | } SteamControllerStatusEvent_t; | ||
| 355 | |||
| 356 | // Deck State payload | ||
| 357 | typedef struct | ||
| 358 | { | ||
| 359 | // If packet num matches that on your prior call, then the controller | ||
| 360 | // state hasn't been changed since your last call and there is no need to | ||
| 361 | // process it | ||
| 362 | Uint32 unPacketNum; | ||
| 363 | |||
| 364 | // Button bitmask and trigger data. | ||
| 365 | union | ||
| 366 | { | ||
| 367 | Uint64 ulButtons; | ||
| 368 | struct | ||
| 369 | { | ||
| 370 | Uint32 ulButtonsL; | ||
| 371 | Uint32 ulButtonsH; | ||
| 372 | }; | ||
| 373 | }; | ||
| 374 | |||
| 375 | // Left pad coordinates | ||
| 376 | short sLeftPadX; | ||
| 377 | short sLeftPadY; | ||
| 378 | |||
| 379 | // Right pad coordinates | ||
| 380 | short sRightPadX; | ||
| 381 | short sRightPadY; | ||
| 382 | |||
| 383 | // Accelerometer values | ||
| 384 | short sAccelX; | ||
| 385 | short sAccelY; | ||
| 386 | short sAccelZ; | ||
| 387 | |||
| 388 | // Gyroscope values | ||
| 389 | short sGyroX; | ||
| 390 | short sGyroY; | ||
| 391 | short sGyroZ; | ||
| 392 | |||
| 393 | // Gyro quaternions | ||
| 394 | short sGyroQuatW; | ||
| 395 | short sGyroQuatX; | ||
| 396 | short sGyroQuatY; | ||
| 397 | short sGyroQuatZ; | ||
| 398 | |||
| 399 | // Uncalibrated trigger values | ||
| 400 | unsigned short sTriggerRawL; | ||
| 401 | unsigned short sTriggerRawR; | ||
| 402 | |||
| 403 | // Left stick values | ||
| 404 | short sLeftStickX; | ||
| 405 | short sLeftStickY; | ||
| 406 | |||
| 407 | // Right stick values | ||
| 408 | short sRightStickX; | ||
| 409 | short sRightStickY; | ||
| 410 | |||
| 411 | // Touchpad pressures | ||
| 412 | unsigned short sPressurePadLeft; | ||
| 413 | unsigned short sPressurePadRight; | ||
| 414 | } SteamDeckStatePacket_t; | ||
| 415 | |||
| 416 | typedef struct | ||
| 417 | { | ||
| 418 | ValveInReportHeader_t header; | ||
| 419 | |||
| 420 | union | ||
| 421 | { | ||
| 422 | ValveControllerStatePacket_t controllerState; | ||
| 423 | ValveControllerBLEStatePacket_t controllerBLEState; | ||
| 424 | ValveControllerDebugPacket_t debugState; | ||
| 425 | ValveControllerTrackpadImage_t padImage; | ||
| 426 | ValveControllerRawTrackpadImage_t rawPadImage; | ||
| 427 | SteamControllerWirelessEvent_t wirelessEvent; | ||
| 428 | SteamControllerStatusEvent_t statusEvent; | ||
| 429 | SteamDeckStatePacket_t deckState; | ||
| 430 | } payload; | ||
| 431 | |||
| 432 | } ValveInReport_t; | ||
| 433 | |||
| 434 | |||
| 435 | // Enumeration for BLE packet protocol | ||
| 436 | enum EBLEPacketReportNums | ||
| 437 | { | ||
| 438 | // Skipping past 2-3 because they are escape characters in Uart protocol | ||
| 439 | k_EBLEReportState = 4, | ||
| 440 | k_EBLEReportStatus = 5, | ||
| 441 | }; | ||
| 442 | |||
| 443 | |||
| 444 | // Enumeration of data chunks in BLE state packets | ||
| 445 | enum EBLEOptionDataChunksBitmask | ||
| 446 | { | ||
| 447 | // First byte upper nibble | ||
| 448 | k_EBLEButtonChunk1 = 0x10, | ||
| 449 | k_EBLEButtonChunk2 = 0x20, | ||
| 450 | k_EBLEButtonChunk3 = 0x40, | ||
| 451 | k_EBLELeftJoystickChunk = 0x80, | ||
| 452 | |||
| 453 | // Second full byte | ||
| 454 | k_EBLELeftTrackpadChunk = 0x100, | ||
| 455 | k_EBLERightTrackpadChunk = 0x200, | ||
| 456 | k_EBLEIMUAccelChunk = 0x400, | ||
| 457 | k_EBLEIMUGyroChunk = 0x800, | ||
| 458 | k_EBLEIMUQuatChunk = 0x1000, | ||
| 459 | }; | ||
| 460 | |||
| 461 | #pragma pack() | ||
| 462 | |||
| 463 | #endif // _CONTROLLER_STRUCTS | ||
