| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| #include "SDL_internal.h" |
| |
| #ifdef SDL_JOYSTICK_HIDAPI |
| |
| #include "../../events/SDL_keyboard_c.h" |
| #include "../SDL_sysjoystick.h" |
| #include "SDL_hidapijoystick_c.h" |
| #include "SDL_hidapi_rumble.h" |
| |
| #ifdef SDL_JOYSTICK_HIDAPI_GIP |
| |
| // This driver is based on the Microsoft GIP spec at: |
| // https://aka.ms/gipdocs |
| // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gipusb/e7c90904-5e21-426e-b9ad-d82adeee0dbc |
| |
| // Define this if you want to log all packets from the controller |
| #if 0 |
| #define DEBUG_XBOX_PROTOCOL |
| #endif |
| |
| #define MAX_MESSAGE_LENGTH 0x4000 |
| #define MAX_ATTACHMENTS 8 |
| |
| #define GIP_DATA_CLASS_COMMAND (0u << 5) |
| #define GIP_DATA_CLASS_LOW_LATENCY (1u << 5) |
| #define GIP_DATA_CLASS_STANDARD_LATENCY (2u << 5) |
| #define GIP_DATA_CLASS_AUDIO (3u << 5) |
| |
| #define GIP_DATA_CLASS_SHIFT 5 |
| #define GIP_DATA_CLASS_MASK (7u << 5) |
| |
| /* System messages */ |
| #define GIP_CMD_PROTO_CONTROL 0x01 |
| #define GIP_CMD_HELLO_DEVICE 0x02 |
| #define GIP_CMD_STATUS_DEVICE 0x03 |
| #define GIP_CMD_METADATA 0x04 |
| #define GIP_CMD_SET_DEVICE_STATE 0x05 |
| #define GIP_CMD_SECURITY 0x06 |
| #define GIP_CMD_GUIDE_BUTTON 0x07 |
| #define GIP_CMD_AUDIO_CONTROL 0x08 |
| #define GIP_CMD_LED 0x0a |
| #define GIP_CMD_HID_REPORT 0x0b |
| #define GIP_CMD_FIRMWARE 0x0c |
| #define GIP_CMD_EXTENDED 0x1e |
| #define GIP_CMD_DEBUG 0x1f |
| #define GIP_AUDIO_DATA 0x60 |
| |
| /* Navigation vendor messages */ |
| #define GIP_CMD_DIRECT_MOTOR 0x09 |
| #define GIP_LL_INPUT_REPORT 0x20 |
| #define GIP_LL_OVERFLOW_INPUT_REPORT 0x26 |
| |
| /* Wheel and ArcadeStick vendor messages */ |
| #define GIP_CMD_INITIAL_REPORTS_REQUEST 0x0a |
| #define GIP_LL_STATIC_CONFIGURATION 0x21 |
| #define GIP_LL_BUTTON_INFO_REPORT 0x22 |
| |
| /* Wheel vendor messages */ |
| #define GIP_CMD_SET_APPLICATION_MEMORY 0x0b |
| #define GIP_CMD_SET_EQUATIONS_STATES 0x0c |
| #define GIP_CMD_SET_EQUATION 0x0d |
| |
| /* FlightStick vendor messages */ |
| #define GIP_CMD_DEVICE_CAPABILITIES 0x00 |
| #define GIP_CMD_LED_CAPABILITIES 0x01 |
| #define GIP_CMD_SET_LED_STATE 0x02 |
| |
| /* Undocumented Elite 2 vendor messages */ |
| #define GIP_CMD_RAW_REPORT 0x0c |
| #define GIP_CMD_GUIDE_COLOR 0x0e |
| #define GIP_SL_ELITE_CONFIG 0x4d |
| |
| #define GIP_BTN_OFFSET_XBE1 28 |
| #define GIP_BTN_OFFSET_XBE2 14 |
| |
| #define GIP_FLAG_FRAGMENT (1u << 7) |
| #define GIP_FLAG_INIT_FRAG (1u << 6) |
| #define GIP_FLAG_SYSTEM (1u << 5) |
| #define GIP_FLAG_ACME (1u << 4) |
| #define GIP_FLAG_ATTACHMENT_MASK 0x7 |
| |
| #define GIP_AUDIO_FORMAT_NULL 0 |
| #define GIP_AUDIO_FORMAT_8000HZ_1CH 1 |
| #define GIP_AUDIO_FORMAT_8000HZ_2CH 2 |
| #define GIP_AUDIO_FORMAT_12000HZ_1CH 3 |
| #define GIP_AUDIO_FORMAT_12000HZ_2CH 4 |
| #define GIP_AUDIO_FORMAT_16000HZ_1CH 5 |
| #define GIP_AUDIO_FORMAT_16000HZ_2CH 6 |
| #define GIP_AUDIO_FORMAT_20000HZ_1CH 7 |
| #define GIP_AUDIO_FORMAT_20000HZ_2CH 8 |
| #define GIP_AUDIO_FORMAT_24000HZ_1CH 9 |
| #define GIP_AUDIO_FORMAT_24000HZ_2CH 10 |
| #define GIP_AUDIO_FORMAT_32000HZ_1CH 11 |
| #define GIP_AUDIO_FORMAT_32000HZ_2CH 12 |
| #define GIP_AUDIO_FORMAT_40000HZ_1CH 13 |
| #define GIP_AUDIO_FORMAT_40000HZ_2CH 14 |
| #define GIP_AUDIO_FORMAT_48000HZ_1CH 15 |
| #define GIP_AUDIO_FORMAT_48000HZ_2CH 16 |
| #define GIP_AUDIO_FORMAT_48000HZ_6CH 32 |
| #define GIP_AUDIO_FORMAT_48000HZ_8CH 33 |
| |
| /* Protocol Control constants */ |
| #define GIP_CONTROL_CODE_ACK 0 |
| #define GIP_CONTROL_CODE_NACK 1 /* obsolete */ |
| #define GIP_CONTROL_CODE_UNK 2 /* obsolete */ |
| #define GIP_CONTROL_CODE_AB 3 /* obsolete */ |
| #define GIP_CONTROL_CODE_MPER 4 /* obsolete */ |
| #define GIP_CONTROL_CODE_STOP 5 /* obsolete */ |
| #define GIP_CONTROL_CODE_START 6 /* obsolete */ |
| #define GIP_CONTROL_CODE_ERR 7 /* obsolete */ |
| |
| /* Status Device constants */ |
| #define GIP_POWER_LEVEL_OFF 0 |
| #define GIP_POWER_LEVEL_STANDBY 1 /* obsolete */ |
| #define GIP_POWER_LEVEL_FULL 2 |
| |
| #define GIP_NOT_CHARGING 0 |
| #define GIP_CHARGING 1 |
| #define GIP_CHARGE_ERROR 2 |
| |
| #define GIP_BATTERY_ABSENT 0 |
| #define GIP_BATTERY_STANDARD 1 |
| #define GIP_BATTERY_RECHARGEABLE 2 |
| |
| #define GIP_BATTERY_CRITICAL 0 |
| #define GIP_BATTERY_LOW 1 |
| #define GIP_BATTERY_MEDIUM 2 |
| #define GIP_BATTERY_FULL 3 |
| |
| #define GIP_EVENT_FAULT 0x0002 |
| |
| #define GIP_FAULT_UNKNOWN 0 |
| #define GIP_FAULT_HARD 1 |
| #define GIP_FAULT_NMI 2 |
| #define GIP_FAULT_SVC 3 |
| #define GIP_FAULT_PEND_SV 4 |
| #define GIP_FAULT_SMART_PTR 5 |
| #define GIP_FAULT_MCU 6 |
| #define GIP_FAULT_BUS 7 |
| #define GIP_FAULT_USAGE 8 |
| #define GIP_FAULT_RADIO_HANG 9 |
| #define GIP_FAULT_WATCHDOG 10 |
| #define GIP_FAULT_LINK_STALL 11 |
| #define GIP_FAULT_ASSERTION 12 |
| |
| /* Metadata constants */ |
| #define GIP_MESSAGE_FLAG_BIG_ENDIAN (1u << 0) |
| #define GIP_MESSAGE_FLAG_RELIABLE (1u << 1) |
| #define GIP_MESSAGE_FLAG_SEQUENCED (1u << 2) |
| #define GIP_MESSAGE_FLAG_DOWNSTREAM (1u << 3) |
| #define GIP_MESSAGE_FLAG_UPSTREAM (1u << 4) |
| #define GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE (1u << 5) |
| |
| #define GIP_DATA_TYPE_CUSTOM 1 |
| #define GIP_DATA_TYPE_AUDIO 2 |
| #define GIP_DATA_TYPE_SECURITY 3 |
| #define GIP_DATA_TYPE_GIP 4 |
| |
| /* Set Device State constants */ |
| #define GIP_STATE_START 0 |
| #define GIP_STATE_STOP 1 |
| #define GIP_STATE_STANDBY 2 /* obsolete */ |
| #define GIP_STATE_FULL_POWER 3 |
| #define GIP_STATE_OFF 4 |
| #define GIP_STATE_QUIESCE 5 |
| #define GIP_STATE_UNK6 6 |
| #define GIP_STATE_RESET 7 |
| |
| /* Guide Button Status constants */ |
| #define GIP_LED_GUIDE 0 |
| #define GIP_LID_IR 1 /* deprecated */ |
| |
| #define GIP_LED_GUIDE_OFF 0 |
| #define GIP_LED_GUIDE_ON 1 |
| #define GIP_LED_GUIDE_FAST_BLINK 2 |
| #define GIP_LED_GUIDE_SLOW_BLINK 3 |
| #define GIP_LED_GUIDE_CHARGING_BLINK 4 |
| #define GIP_LED_GUIDE_RAMP_TO_LEVEL 0xd |
| |
| #define GIP_LED_IR_OFF 0 |
| #define GIP_LED_IR_ON_100MS 1 |
| #define GIP_LED_IR_PATTERN 4 |
| |
| /* Direct Motor Command constants */ |
| #define GIP_MOTOR_RIGHT_VIBRATION (1u << 0) |
| #define GIP_MOTOR_LEFT_VIBRATION (1u << 1) |
| #define GIP_MOTOR_RIGHT_IMPULSE (1u << 2) |
| #define GIP_MOTOR_LEFT_IMPULSE (1u << 3) |
| #define GIP_MOTOR_ALL 0xF |
| |
| /* Extended Command constants */ |
| #define GIP_EXTCMD_GET_CAPABILITIES 0x00 |
| #define GIP_EXTCMD_GET_TELEMETRY_DATA 0x01 |
| #define GIP_EXTCMD_GET_SERIAL_NUMBER 0x04 |
| |
| #define GIP_EXTENDED_STATUS_OK 0 |
| #define GIP_EXTENDED_STATUS_NOT_SUPPORTED 1 |
| #define GIP_EXTENDED_STATUS_NOT_READY 2 |
| #define GIP_EXTENDED_STATUS_ACCESS_DENIED 3 |
| #define GIP_EXTENDED_STATUS_FAILED 4 |
| |
| /* Internal constants, not part of protocol */ |
| #define GIP_HELLO_TIMEOUT 2000 |
| #define GIP_ACME_TIMEOUT 10 |
| |
| #define GIP_DEFAULT_IN_SYSTEM_MESSAGES 0x5e |
| #define GIP_DEFAULT_OUT_SYSTEM_MESSAGES 0x472 |
| |
| #define GIP_FEATURE_CONSOLE_FUNCTION_MAP (1u << 0) |
| #define GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW (1u << 1) |
| #define GIP_FEATURE_ELITE_BUTTONS (1u << 2) |
| #define GIP_FEATURE_DYNAMIC_LATENCY_INPUT (1u << 3) |
| #define GIP_FEATURE_SECURITY_OPT_OUT (1u << 4) |
| #define GIP_FEATURE_MOTOR_CONTROL (1u << 5) |
| #define GIP_FEATURE_GUIDE_COLOR (1u << 6) |
| #define GIP_FEATURE_EXTENDED_SET_DEVICE_STATE (1u << 7) |
| |
| #define GIP_QUIRK_NO_HELLO (1u << 0) |
| #define GIP_QUIRK_BROKEN_METADATA (1u << 1) |
| #define GIP_QUIRK_NO_IMPULSE_VIBRATION (1u << 2) |
| |
| typedef enum |
| { |
| GIP_METADATA_NONE = 0, |
| GIP_METADATA_GOT = 1, |
| GIP_METADATA_FAKED = 2, |
| GIP_METADATA_PENDING = 3, |
| } GIP_MetadataStatus; |
| |
| #ifndef VK_LWIN |
| #define VK_LWIN 0x5b |
| #endif |
| |
| typedef enum |
| { |
| GIP_TYPE_UNKNOWN = -1, |
| GIP_TYPE_GAMEPAD = 0, |
| GIP_TYPE_ARCADE_STICK = 1, |
| GIP_TYPE_WHEEL = 2, |
| GIP_TYPE_FLIGHT_STICK = 3, |
| GIP_TYPE_NAVIGATION_CONTROLLER = 4, |
| GIP_TYPE_CHATPAD = 5, |
| GIP_TYPE_HEADSET = 6, |
| } GIP_AttachmentType; |
| |
| typedef enum |
| { |
| GIP_RUMBLE_STATE_IDLE, |
| GIP_RUMBLE_STATE_QUEUED, |
| GIP_RUMBLE_STATE_BUSY, |
| } GIP_RumbleState; |
| |
| typedef enum |
| { |
| GIP_BTN_FMT_UNKNOWN, |
| GIP_BTN_FMT_XBE1, |
| GIP_BTN_FMT_XBE2_RAW, |
| GIP_BTN_FMT_XBE2_4, |
| GIP_BTN_FMT_XBE2_5, |
| } GIP_EliteButtonFormat; |
| |
| /* These come across the wire as little-endian, so let's store them in-memory as such so we can memcmp */ |
| #define MAKE_GUID(NAME, A, B, C, D0, D1, D2, D3, D4, D5, D6, D7) \ |
| static const GUID NAME = { SDL_Swap32LE(A), SDL_Swap16LE(B), SDL_Swap16LE(C), { D0, D1, D2, D3, D4, D5, D6, D7 } } |
| |
| typedef struct GUID |
| { |
| Uint32 a; |
| Uint16 b; |
| Uint16 c; |
| Uint8 d[8]; |
| } GUID; |
| SDL_COMPILE_TIME_ASSERT(GUID, sizeof(GUID) == 16); |
| |
| MAKE_GUID(GUID_ArcadeStick, 0x332054cc, 0xa34b, 0x41d5, 0xa3, 0x4a, 0xa6, 0xa6, 0x71, 0x1e, 0xc4, 0xb3); |
| MAKE_GUID(GUID_DynamicLatencyInput, 0x87f2e56b, 0xc3bb, 0x49b1, 0x82, 0x65, 0xff, 0xff, 0xf3, 0x77, 0x99, 0xee); |
| MAKE_GUID(GUID_FlightStick, 0x03f1a011, 0xefe9, 0x4cc1, 0x96, 0x9c, 0x38, 0xdc, 0x55, 0xf4, 0x04, 0xd0); |
| MAKE_GUID(GUID_IHeadset, 0xbc25d1a3, 0xc24e, 0x4992, 0x9d, 0xda, 0xef, 0x4f, 0x12, 0x3e, 0xf5, 0xdc); |
| MAKE_GUID(GUID_IConsoleFunctionMap_InputReport, 0xecddd2fe, 0xd387, 0x4294, 0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d); |
| MAKE_GUID(GUID_IConsoleFunctionMap_OverflowInputReport, 0x137d4bd0, 0x9347, 0x4472, 0xaa, 0x26, 0x8c, 0x34, 0xa0, 0x8f, 0xf9, 0xbd); |
| MAKE_GUID(GUID_IController, 0x9776ff56, 0x9bfd, 0x4581, 0xad, 0x45, 0xb6, 0x45, 0xbb, 0xa5, 0x26, 0xd6); |
| MAKE_GUID(GUID_IDevAuthPCOptOut, 0x7a34ce77, 0x7de2, 0x45c6, 0x8c, 0xa4, 0x00, 0x42, 0xc0, 0x8b, 0xd9, 0x4a); |
| MAKE_GUID(GUID_IEliteButtons, 0x37d19ff7, 0xb5c6, 0x49d1, 0xa7, 0x5e, 0x03, 0xb2, 0x4b, 0xef, 0x8c, 0x89); |
| MAKE_GUID(GUID_IGamepad, 0x082e402c, 0x07df, 0x45e1, 0xa5, 0xab, 0xa3, 0x12, 0x7a, 0xf1, 0x97, 0xb5); |
| MAKE_GUID(GUID_NavigationController, 0xb8f31fe7, 0x7386, 0x40e9, 0xa9, 0xf8, 0x2f, 0x21, 0x26, 0x3a, 0xcf, 0xb7); |
| MAKE_GUID(GUID_Wheel, 0x646979cf, 0x6b71, 0x4e96, 0x8d, 0xf9, 0x59, 0xe3, 0x98, 0xd7, 0x42, 0x0c); |
| |
| /* |
| * The following GUIDs are observed, but the exact meanings aren't known, so |
| * for now we document them but don't use them anywhere. |
| * |
| * MAKE_GUID(GUID_GamepadEmu, 0xe2e5f1bc, 0xa6e6, 0x41a2, 0x8f, 0x43, 0x33, 0xcf, 0xa2, 0x51, 0x09, 0x81); |
| * MAKE_GUID(GUID_IAudioOnly, 0x92844cd1, 0xf7c8, 0x49ef, 0x97, 0x77, 0x46, 0x7d, 0xa7, 0x08, 0xad, 0x10); |
| * MAKE_GUID(GUID_IControllerProfileModeState, 0xf758dc66, 0x022c, 0x48b8, 0xa4, 0xf6, 0x45, 0x7b, 0xa8, 0x0e, 0x2a, 0x5b); |
| * MAKE_GUID(GUID_ICustomAudio, 0x63fd9cc9, 0x94ee, 0x4b5d, 0x9c, 0x4d, 0x8b, 0x86, 0x4c, 0x14, 0x9c, 0xac); |
| * MAKE_GUID(GUID_IExtendedDeviceFlags, 0x34ad9b1e, 0x36ad, 0x4fb5, 0x8a, 0xc7, 0x17, 0x23, 0x4c, 0x9f, 0x54, 0x6f); |
| * MAKE_GUID(GUID_IProgrammableGamepad, 0x31c1034d, 0xb5b7, 0x4551, 0x98, 0x13, 0x87, 0x69, 0xd4, 0xa0, 0xe4, 0xf9); |
| * MAKE_GUID(GUID_IVirtualDevice, 0xdfd26825, 0x110a, 0x4e94, 0xb9, 0x37, 0xb2, 0x7c, 0xe4, 0x7b, 0x25, 0x40); |
| * MAKE_GUID(GUID_OnlineDevAuth, 0x632b1fd1, 0xa3e9, 0x44f9, 0x84, 0x20, 0x5c, 0xe3, 0x44, 0xa0, 0x64, 0x04); |
| * |
| * Seen on Elite Controller, Adaptive Controller: 9ebd00a3-b5e6-4c08-a33b-673126459ec4 |
| * Seen on Adaptive Controller: ce1e58c5-221c-4bdb-9c24-bf3941601320 |
| * Seen on Elite 2 Controller: f758dc66-022c-48b8-a4f6-457ba80e2a5b (IControllerProfileModeState) |
| * Seen on Elite 2 Controller: 31c1034d-b5b7-4551-9813-8769d4a0e4f9 (IProgrammableGamepad) |
| * Seen on Elite 2 Controller: 34ad9b1e-36ad-4fb5-8ac7-17234c9f546f (IExtendedDeviceFlags) |
| * Seen on Elite 2 Controller: 88e0b694-6bd9-4416-a560-e7fafdfa528f |
| * Seen on Elite 2 Controller: ea96c8c0-b216-448b-be80-7e5deb0698e2 |
| */ |
| |
| static const int GIP_DataClassMtu[8] = { 64, 64, 64, 2048, 0, 0, 0, 0 }; |
| |
| typedef struct GIP_Quirks |
| { |
| Uint16 vendor_id; |
| Uint16 product_id; |
| Uint8 attachment_index; |
| Uint32 added_features; |
| Uint32 filtered_features; |
| Uint32 quirks; |
| Uint32 extra_in_system[8]; |
| Uint32 extra_out_system[8]; |
| GIP_AttachmentType device_type; |
| Uint8 extra_buttons; |
| Uint8 extra_axes; |
| } GIP_Quirks; |
| |
| static const GIP_Quirks quirks[] = { |
| { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1, 0, |
| .added_features = GIP_FEATURE_ELITE_BUTTONS, |
| .filtered_features = GIP_FEATURE_CONSOLE_FUNCTION_MAP }, |
| |
| { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2, 0, |
| .added_features = GIP_FEATURE_ELITE_BUTTONS | GIP_FEATURE_DYNAMIC_LATENCY_INPUT | GIP_FEATURE_CONSOLE_FUNCTION_MAP | GIP_FEATURE_GUIDE_COLOR | GIP_FEATURE_EXTENDED_SET_DEVICE_STATE, |
| .extra_in_system = { 1 << GIP_CMD_FIRMWARE }, |
| .extra_out_system = { 1 << GIP_CMD_FIRMWARE } }, |
| |
| { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_SERIES_X, 0, |
| .added_features = GIP_FEATURE_DYNAMIC_LATENCY_INPUT }, |
| |
| { USB_VENDOR_PDP, USB_PRODUCT_PDP_ROCK_CANDY, 0, |
| .quirks = GIP_QUIRK_NO_HELLO }, |
| |
| { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_FIGHTPAD, 0, |
| .filtered_features = GIP_FEATURE_MOTOR_CONTROL }, |
| |
| { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_CLASSIC, 0, |
| .quirks = GIP_QUIRK_NO_IMPULSE_VIBRATION }, |
| |
| { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_SPECTRA_PRO, 0, |
| .quirks = GIP_QUIRK_NO_IMPULSE_VIBRATION }, |
| |
| { USB_VENDOR_RAZER, USB_PRODUCT_RAZER_ATROX, 0, |
| .filtered_features = GIP_FEATURE_MOTOR_CONTROL, |
| .device_type = GIP_TYPE_ARCADE_STICK }, |
| |
| { USB_VENDOR_THRUSTMASTER, USB_PRODUCT_THRUSTMASTER_T_FLIGHT_HOTAS_ONE, 0, |
| .filtered_features = GIP_FEATURE_MOTOR_CONTROL, |
| .device_type = GIP_TYPE_FLIGHT_STICK, |
| .extra_buttons = 5, |
| .extra_axes = 3 }, |
| |
| {0}, |
| }; |
| |
| typedef struct GIP_Header |
| { |
| Uint8 message_type; |
| Uint8 flags; |
| Uint8 sequence_id; |
| Uint64 length; |
| } GIP_Header; |
| |
| typedef struct GIP_AudioFormat |
| { |
| Uint8 inbound; |
| Uint8 outbound; |
| } GIP_AudioFormat; |
| |
| typedef struct GIP_DeviceMetadata |
| { |
| Uint8 num_audio_formats; |
| Uint8 num_preferred_types; |
| Uint8 num_supported_interfaces; |
| Uint8 hid_descriptor_size; |
| |
| Uint32 in_system_messages[8]; |
| Uint32 out_system_messages[8]; |
| |
| GIP_AudioFormat *audio_formats; |
| char **preferred_types; |
| GUID *supported_interfaces; |
| Uint8 *hid_descriptor; |
| |
| GIP_AttachmentType device_type; |
| } GIP_DeviceMetadata; |
| |
| typedef struct GIP_MessageMetadata |
| { |
| Uint8 type; |
| Uint16 length; |
| Uint16 data_type; |
| Uint32 flags; |
| Uint16 period; |
| Uint16 persistence_timeout; |
| } GIP_MessageMetadata; |
| |
| typedef struct GIP_Metadata |
| { |
| Uint16 version_major; |
| Uint16 version_minor; |
| |
| GIP_DeviceMetadata device; |
| |
| Uint8 num_messages; |
| GIP_MessageMetadata *message_metadata; |
| } GIP_Metadata; |
| |
| struct GIP_Device; |
| typedef struct GIP_Attachment |
| { |
| struct GIP_Device *device; |
| Uint8 attachment_index; |
| SDL_JoystickID joystick; |
| SDL_KeyboardID keyboard; |
| |
| Uint8 fragment_message; |
| Uint16 total_length; |
| Uint8 *fragment_data; |
| Uint32 fragment_offset; |
| Uint64 fragment_timer; |
| int fragment_retries; |
| |
| Uint16 firmware_major_version; |
| Uint16 firmware_minor_version; |
| |
| GIP_MetadataStatus got_metadata; |
| Uint64 metadata_next; |
| int metadata_retries; |
| GIP_Metadata metadata; |
| |
| Uint8 seq_system; |
| Uint8 seq_security; |
| Uint8 seq_extended; |
| Uint8 seq_audio; |
| Uint8 seq_vendor; |
| |
| int device_state; |
| |
| GIP_RumbleState rumble_state; |
| Uint64 rumble_time; |
| bool rumble_pending; |
| Uint8 left_impulse_level; |
| Uint8 right_impulse_level; |
| Uint8 left_vibration_level; |
| Uint8 right_vibration_level; |
| |
| Uint8 last_input[64]; |
| |
| Uint8 last_modifiers; |
| bool capslock; |
| SDL_Keycode last_key; |
| Uint32 altcode; |
| int altcode_digit; |
| |
| GIP_AttachmentType attachment_type; |
| GIP_EliteButtonFormat xbe_format; |
| Uint32 features; |
| Uint32 quirks; |
| Uint8 share_button_idx; |
| Uint8 paddle_idx; |
| |
| Uint8 extra_button_idx; |
| int extra_buttons; |
| int extra_axes; |
| } GIP_Attachment; |
| |
| typedef struct GIP_Device |
| { |
| SDL_HIDAPI_Device *device; |
| |
| Uint64 hello_deadline; |
| bool got_hello; |
| bool reset_for_metadata; |
| int timeout; |
| |
| GIP_Attachment *attachments[MAX_ATTACHMENTS]; |
| } GIP_Device; |
| |
| typedef struct GIP_HelloDevice |
| { |
| Uint64 device_id; |
| Uint16 vendor_id; |
| Uint16 product_id; |
| Uint16 firmware_major_version; |
| Uint16 firmware_minor_version; |
| Uint16 firmware_build_version; |
| Uint16 firmware_revision; |
| Uint8 hardware_major_version; |
| Uint8 hardware_minor_version; |
| Uint8 rf_proto_major_version; |
| Uint8 rf_proto_minor_version; |
| Uint8 security_major_version; |
| Uint8 security_minor_version; |
| Uint8 gip_major_version; |
| Uint8 gip_minor_version; |
| } GIP_HelloDevice; |
| |
| typedef struct GIP_Status |
| { |
| int power_level; |
| int charge; |
| int battery_type; |
| int battery_level; |
| } GIP_Status; |
| |
| typedef struct GIP_StatusEvent |
| { |
| Uint16 event_type; |
| Uint32 fault_tag; |
| Uint32 fault_address; |
| } GIP_StatusEvent; |
| |
| typedef struct GIP_ExtendedStatus |
| { |
| GIP_Status base; |
| bool device_active; |
| |
| int num_events; |
| GIP_StatusEvent events[5]; |
| } GIP_ExtendedStatus; |
| |
| typedef struct GIP_DirectMotor |
| { |
| Uint8 motor_bitmap; |
| Uint8 left_impulse_level; |
| Uint8 right_impulse_level; |
| Uint8 left_vibration_level; |
| Uint8 right_vibration_level; |
| Uint8 duration; |
| Uint8 delay; |
| Uint8 repeat; |
| } GIP_DirectMotor; |
| |
| typedef struct GIP_InitialReportsRequest |
| { |
| Uint8 type; |
| Uint8 data[2]; |
| } GIP_InitialReportsRequest; |
| |
| static bool GIP_SetMetadataDefaults(GIP_Attachment *attachment); |
| |
| static int GIP_DecodeLength(Uint64 *length, const Uint8 *bytes, int num_bytes) |
| { |
| *length = 0; |
| int offset; |
| |
| for (offset = 0; offset < num_bytes; offset++) { |
| Uint8 byte = bytes[offset]; |
| *length |= (byte & 0x7full) << (offset * 7); |
| if (!(byte & 0x80)) { |
| offset++; |
| break; |
| } |
| } |
| return offset; |
| } |
| |
| static int GIP_EncodeLength(Uint64 length, Uint8 *bytes, int num_bytes) |
| { |
| int offset; |
| |
| for (offset = 0; offset < num_bytes; offset++) { |
| Uint8 byte = length & 0x7f; |
| length >>= 7; |
| if (length) { |
| byte |= 0x80; |
| } |
| bytes[offset] = byte; |
| if (!length) { |
| offset++; |
| break; |
| } |
| } |
| return offset; |
| } |
| |
| static bool GIP_SupportsSystemMessage(GIP_Attachment *attachment, Uint8 command, bool upstream) |
| { |
| if (upstream) { |
| return attachment->metadata.device.in_system_messages[command >> 5] & (1u << command); |
| } else { |
| return attachment->metadata.device.out_system_messages[command >> 5] & (1u << command); |
| } |
| } |
| |
| static bool GIP_SupportsVendorMessage(GIP_Attachment *attachment, Uint8 command, bool upstream) |
| { |
| size_t i; |
| for (i = 0; i < attachment->metadata.num_messages; i++) { |
| GIP_MessageMetadata *metadata = &attachment->metadata.message_metadata[i]; |
| if (metadata->type != command) { |
| continue; |
| } |
| if (metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE) { |
| return true; |
| } |
| if (upstream) { |
| return metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM; |
| } else { |
| return metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM; |
| } |
| } |
| return false; |
| } |
| |
| static Uint8 GIP_SequenceNext(GIP_Attachment *attachment, Uint8 command, bool system) |
| { |
| Uint8 seq; |
| |
| if (system) { |
| switch (command) { |
| case GIP_CMD_SECURITY: |
| seq = attachment->seq_security++; |
| if (!seq) { |
| seq = attachment->seq_security++; |
| } |
| break; |
| case GIP_CMD_EXTENDED: |
| seq = attachment->seq_extended++; |
| if (!seq) { |
| seq = attachment->seq_extended++; |
| } |
| break; |
| case GIP_AUDIO_DATA: |
| seq = attachment->seq_audio++; |
| if (!seq) { |
| seq = attachment->seq_audio++; |
| } |
| break; |
| default: |
| seq = attachment->seq_system++; |
| if (!seq) { |
| seq = attachment->seq_system++; |
| } |
| break; |
| } |
| } else { |
| if (command == GIP_CMD_DIRECT_MOTOR) { |
| // The motor sequence number is optional and always works with 0 |
| return 0; |
| } |
| |
| seq = attachment->seq_vendor++; |
| if (!seq) { |
| seq = attachment->seq_vendor++; |
| } |
| } |
| return seq; |
| } |
| |
| static void GIP_HandleQuirks(GIP_Attachment *attachment) |
| { |
| size_t i, j; |
| for (i = 0; quirks[i].vendor_id; i++) { |
| if (quirks[i].vendor_id != attachment->device->device->vendor_id) { |
| continue; |
| } |
| if (quirks[i].product_id != attachment->device->device->product_id) { |
| continue; |
| } |
| if (quirks[i].attachment_index != attachment->attachment_index) { |
| continue; |
| } |
| attachment->features |= quirks[i].added_features; |
| attachment->features &= ~quirks[i].filtered_features; |
| attachment->quirks = quirks[i].quirks; |
| attachment->attachment_type = quirks[i].device_type; |
| |
| for (j = 0; j < 8; ++j) { |
| attachment->metadata.device.in_system_messages[j] |= quirks[i].extra_in_system[j]; |
| attachment->metadata.device.out_system_messages[j] |= quirks[i].extra_out_system[j]; |
| } |
| |
| attachment->extra_buttons = quirks[i].extra_buttons; |
| attachment->extra_axes = quirks[i].extra_axes; |
| break; |
| } |
| } |
| |
| static bool GIP_SendRawMessage( |
| GIP_Device *device, |
| Uint8 message_type, |
| Uint8 flags, |
| Uint8 seq, |
| const Uint8 *bytes, |
| int num_bytes, |
| bool async, |
| SDL_HIDAPI_RumbleSentCallback callback, |
| void *userdata) |
| { |
| Uint8 buffer[2054] = { message_type, flags, seq }; |
| int offset = 3; |
| |
| if (num_bytes < 0) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Invalid message length %d", num_bytes); |
| return false; |
| } |
| |
| if (num_bytes > GIP_DataClassMtu[message_type >> GIP_DATA_CLASS_SHIFT]) { |
| SDL_LogError(SDL_LOG_CATEGORY_INPUT, |
| "Attempted to send a message that requires fragmenting, which is not yet supported."); |
| return false; |
| } |
| |
| offset += GIP_EncodeLength(num_bytes, &buffer[offset], sizeof(buffer) - offset); |
| |
| if (num_bytes > 0) { |
| SDL_memcpy(&buffer[offset], bytes, num_bytes); |
| } |
| num_bytes += offset; |
| #ifdef DEBUG_XBOX_PROTOCOL |
| HIDAPI_DumpPacket("GIP sending message: size = %d", buffer, num_bytes); |
| #endif |
| |
| if (async) { |
| if (!SDL_HIDAPI_LockRumble()) { |
| return false; |
| } |
| |
| return SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device->device, buffer, num_bytes, callback, userdata) == num_bytes; |
| } else { |
| return SDL_hid_write(device->device->dev, buffer, num_bytes) == num_bytes; |
| } |
| } |
| |
| static bool GIP_SendSystemMessage( |
| GIP_Attachment *attachment, |
| Uint8 message_type, |
| Uint8 flags, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| return GIP_SendRawMessage(attachment->device, |
| message_type, |
| GIP_FLAG_SYSTEM | attachment->attachment_index | flags, |
| GIP_SequenceNext(attachment, message_type, true), |
| bytes, |
| num_bytes, |
| false, |
| NULL, |
| NULL); |
| } |
| |
| static bool GIP_SendVendorMessage( |
| GIP_Attachment *attachment, |
| Uint8 message_type, |
| Uint8 flags, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| return GIP_SendRawMessage(attachment->device, |
| message_type, |
| flags, |
| GIP_SequenceNext(attachment, message_type, false), |
| bytes, |
| num_bytes, |
| true, |
| NULL, |
| NULL); |
| } |
| |
| static bool GIP_AttachmentIsController(GIP_Attachment *attachment) |
| { |
| return attachment->attachment_type != GIP_TYPE_CHATPAD && |
| attachment->attachment_type != GIP_TYPE_HEADSET; |
| } |
| |
| static void GIP_MetadataFree(GIP_Metadata *metadata) |
| { |
| SDL_free(metadata->device.audio_formats); |
| if (metadata->device.preferred_types) { |
| int i; |
| for (i = 0; i < metadata->device.num_preferred_types; i++) { |
| SDL_free(metadata->device.preferred_types[i]); |
| } |
| SDL_free(metadata->device.preferred_types); |
| } |
| SDL_free(metadata->device.supported_interfaces); |
| SDL_free(metadata->device.hid_descriptor); |
| |
| SDL_free(metadata->message_metadata); |
| SDL_zerop(metadata); |
| } |
| |
| static bool GIP_ParseDeviceMetadata(GIP_Metadata *metadata, const Uint8 *bytes, int num_bytes, int *offset) |
| { |
| GIP_DeviceMetadata *device = &metadata->device; |
| int buffer_offset; |
| int count; |
| int length; |
| int i; |
| |
| bytes = &bytes[*offset]; |
| num_bytes -= *offset; |
| if (num_bytes < 16) { |
| return false; |
| } |
| |
| length = bytes[0]; |
| length |= bytes[1] << 8; |
| if (num_bytes < length) { |
| return false; |
| } |
| |
| /* Skip supported firmware versions for now */ |
| |
| buffer_offset = bytes[4]; |
| buffer_offset |= bytes[5] << 8; |
| if (buffer_offset >= length) { |
| return false; |
| } |
| if (buffer_offset > 0) { |
| device->num_audio_formats = bytes[buffer_offset]; |
| if (buffer_offset + device->num_audio_formats + 1 > length) { |
| return false; |
| } |
| device->audio_formats = SDL_malloc(device->num_audio_formats); |
| SDL_memcpy(device->audio_formats, &bytes[buffer_offset + 1], device->num_audio_formats); |
| } |
| |
| buffer_offset = bytes[6]; |
| buffer_offset |= bytes[7] << 8; |
| if (buffer_offset >= length) { |
| return false; |
| } |
| if (buffer_offset > 0) { |
| count = bytes[buffer_offset]; |
| if (buffer_offset + count + 1 > length) { |
| return false; |
| } |
| |
| for (i = 0; i < count; i++) { |
| Uint8 message = bytes[buffer_offset + 1 + i]; |
| #ifdef DEBUG_XBOX_PROTOCOL |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Supported upstream system message %02x", |
| message); |
| #endif |
| device->in_system_messages[message >> 5] |= 1u << (message & 0x1F); |
| } |
| } |
| |
| buffer_offset = bytes[8]; |
| buffer_offset |= bytes[9] << 8; |
| if (buffer_offset >= length) { |
| return false; |
| } |
| if (buffer_offset > 0) { |
| count = bytes[buffer_offset]; |
| if (buffer_offset + count + 1 > length) { |
| return false; |
| } |
| |
| for (i = 0; i < count; i++) { |
| Uint8 message = bytes[buffer_offset + 1 + i]; |
| #ifdef DEBUG_XBOX_PROTOCOL |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Supported downstream system message %02x", |
| message); |
| #endif |
| device->out_system_messages[message >> 5] |= 1u << (message & 0x1F); |
| } |
| } |
| |
| buffer_offset = bytes[10]; |
| buffer_offset |= bytes[11] << 8; |
| if (buffer_offset >= length) { |
| return false; |
| } |
| if (buffer_offset > 0) { |
| device->num_preferred_types = bytes[buffer_offset]; |
| device->preferred_types = SDL_calloc(device->num_preferred_types, sizeof(char *)); |
| buffer_offset++; |
| for (i = 0; i < device->num_preferred_types; i++) { |
| if (buffer_offset + 2 >= length) { |
| return false; |
| } |
| |
| count = bytes[buffer_offset]; |
| count |= bytes[buffer_offset]; |
| buffer_offset += 2; |
| if (buffer_offset + count > length) { |
| return false; |
| } |
| |
| device->preferred_types[i] = SDL_calloc(count + 1, sizeof(char)); |
| SDL_memcpy(device->preferred_types[i], &bytes[buffer_offset], count); |
| buffer_offset += count; |
| } |
| } |
| |
| buffer_offset = bytes[12]; |
| buffer_offset |= bytes[13] << 8; |
| if (buffer_offset >= length) { |
| return false; |
| } |
| if (buffer_offset > 0) { |
| device->num_supported_interfaces = bytes[buffer_offset]; |
| if (buffer_offset + 1 + (Sint32) (device->num_supported_interfaces * sizeof(GUID)) > length) { |
| return false; |
| } |
| device->supported_interfaces = SDL_calloc(device->num_supported_interfaces, sizeof(GUID)); |
| SDL_memcpy(device->supported_interfaces, |
| &bytes[buffer_offset + 1], |
| sizeof(GUID) * device->num_supported_interfaces); |
| } |
| |
| if (metadata->version_major > 1 || metadata->version_minor >= 1) { |
| /* HID descriptor support added in metadata version 1.1 */ |
| buffer_offset = bytes[14]; |
| buffer_offset |= bytes[15] << 8; |
| if (buffer_offset >= length) { |
| return false; |
| } |
| if (buffer_offset > 0) { |
| device->hid_descriptor_size = bytes[buffer_offset]; |
| if (buffer_offset + 1 + device->hid_descriptor_size > length) { |
| return false; |
| } |
| device->hid_descriptor = SDL_malloc(device->hid_descriptor_size); |
| SDL_memcpy(device->hid_descriptor, &bytes[buffer_offset + 1], device->hid_descriptor_size); |
| #ifdef DEBUG_XBOX_PROTOCOL |
| HIDAPI_DumpPacket("GIP received HID descriptor: size = %d", device->hid_descriptor, device->hid_descriptor_size); |
| #endif |
| } |
| } |
| |
| *offset += length; |
| return true; |
| } |
| |
| static bool GIP_ParseMessageMetadata(GIP_MessageMetadata *metadata, const Uint8 *bytes, int num_bytes, int *offset) |
| { |
| Uint16 length; |
| |
| bytes = &bytes[*offset]; |
| num_bytes -= *offset; |
| |
| if (num_bytes < 2) { |
| return false; |
| } |
| length = bytes[0]; |
| length |= bytes[1] << 8; |
| if (num_bytes < length) { |
| return false; |
| } |
| |
| if (length < 15) { |
| return false; |
| } |
| |
| metadata->type = bytes[2]; |
| metadata->length = bytes[3]; |
| metadata->length |= bytes[4] << 8; |
| metadata->data_type = bytes[5]; |
| metadata->data_type |= bytes[6] << 8; |
| metadata->flags = bytes[7]; |
| metadata->flags |= bytes[8] << 8; |
| metadata->flags |= bytes[9] << 16; |
| metadata->flags |= bytes[10] << 24; |
| metadata->period = bytes[11]; |
| metadata->period |= bytes[12] << 8; |
| metadata->persistence_timeout = bytes[13]; |
| metadata->persistence_timeout |= bytes[14] << 8; |
| |
| #ifdef DEBUG_XBOX_PROTOCOL |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Supported vendor message type %02x of length %d, %s, %s, %s", |
| metadata->type, |
| metadata->length, |
| metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM ? |
| (metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "bidirectional" : "upstream") : |
| metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "downstream" : |
| metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE ? "downstream request response" : |
| "unknown direction", |
| metadata->flags & GIP_MESSAGE_FLAG_SEQUENCED ? "sequenced" : "not sequenced", |
| metadata->flags & GIP_MESSAGE_FLAG_RELIABLE ? "reliable" : "unreliable"); |
| #endif |
| |
| *offset += length; |
| return true; |
| } |
| |
| static bool GIP_ParseMetadata(GIP_Metadata *metadata, const Uint8 *bytes, int num_bytes) |
| { |
| int header_size; |
| int metadata_size; |
| int offset = 0; |
| int i; |
| |
| if (num_bytes < 16) { |
| return false; |
| } |
| |
| #ifdef DEBUG_XBOX_PROTOCOL |
| HIDAPI_DumpPacket("GIP received metadata: size = %d", bytes, num_bytes); |
| #endif |
| |
| header_size = bytes[0]; |
| header_size |= bytes[1] << 8; |
| if (num_bytes < header_size || header_size < 16) { |
| return false; |
| } |
| metadata->version_major = bytes[2]; |
| metadata->version_major |= bytes[3] << 8; |
| metadata->version_minor = bytes[4]; |
| metadata->version_minor |= bytes[5] << 8; |
| /* Middle bytes are reserved */ |
| metadata_size = bytes[14]; |
| metadata_size |= bytes[15] << 8; |
| |
| if (num_bytes < metadata_size || metadata_size < header_size) { |
| return false; |
| } |
| offset = header_size; |
| |
| if (!GIP_ParseDeviceMetadata(metadata, bytes, num_bytes, &offset)) { |
| goto err; |
| } |
| |
| if (offset >= num_bytes) { |
| goto err; |
| } |
| metadata->num_messages = bytes[offset]; |
| offset++; |
| if (metadata->num_messages > 0) { |
| metadata->message_metadata = SDL_calloc(metadata->num_messages, sizeof(*metadata->message_metadata)); |
| for (i = 0; i < metadata->num_messages; i++) { |
| if (!GIP_ParseMessageMetadata(&metadata->message_metadata[i], bytes, num_bytes, &offset)) { |
| goto err; |
| } |
| } |
| } |
| |
| return true; |
| |
| err: |
| GIP_MetadataFree(metadata); |
| return false; |
| } |
| |
| static bool GIP_Acknowledge( |
| GIP_Device *device, |
| const GIP_Header *header, |
| Uint32 fragment_offset, |
| Uint16 bytes_remaining) |
| { |
| Uint8 buffer[] = { |
| GIP_CONTROL_CODE_ACK, |
| header->message_type, |
| header->flags & GIP_FLAG_SYSTEM, |
| (Uint8) fragment_offset, |
| (Uint8) (fragment_offset >> 8), |
| (Uint8) (fragment_offset >> 16), |
| fragment_offset >> 24, |
| (Uint8) bytes_remaining, |
| bytes_remaining >> 8, |
| }; |
| |
| return GIP_SendRawMessage(device, |
| GIP_CMD_PROTO_CONTROL, |
| GIP_FLAG_SYSTEM | (header->flags & GIP_FLAG_ATTACHMENT_MASK), |
| header->sequence_id, |
| buffer, |
| sizeof(buffer), |
| false, |
| NULL, |
| NULL); |
| } |
| |
| static bool GIP_FragmentFailed(GIP_Attachment *attachment, const GIP_Header *header) |
| { |
| attachment->fragment_retries++; |
| if (attachment->fragment_retries > 8) { |
| if (attachment->fragment_data) { |
| SDL_free(attachment->fragment_data); |
| attachment->fragment_data = NULL; |
| } |
| attachment->fragment_message = 0; |
| } |
| return GIP_Acknowledge(attachment->device, |
| header, |
| attachment->fragment_offset, |
| (Uint16) (attachment->total_length - attachment->fragment_offset)); |
| } |
| |
| static bool GIP_EnableEliteButtons(GIP_Attachment *attachment) { |
| if (attachment->device->device->vendor_id == USB_VENDOR_MICROSOFT) { |
| if (attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1) { |
| attachment->xbe_format = GIP_BTN_FMT_XBE1; |
| } else if (attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) { |
| if (attachment->firmware_major_version == 4) { |
| attachment->xbe_format = GIP_BTN_FMT_XBE2_4; |
| } else if (attachment->firmware_major_version == 5) { |
| /* |
| * The exact range for this being necessary is unknown, but it |
| * starts at 5.11 and at either 5.16 or 5.17. This approach |
| * still works on 5.21, even if it's not necessary, so having |
| * a loose upper limit is fine. |
| */ |
| if (attachment->firmware_minor_version >= 11 && |
| attachment->firmware_minor_version < 17) |
| { |
| attachment->xbe_format = GIP_BTN_FMT_XBE2_RAW; |
| } else { |
| attachment->xbe_format = GIP_BTN_FMT_XBE2_5; |
| } |
| } |
| } |
| } |
| if (attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) { |
| /* |
| * The meaning of this packet is unknown and not documented, but it's |
| * needed for the Elite 2 controller to send raw reports |
| */ |
| static const Uint8 enable_raw_report[] = { 7, 0 }; |
| |
| return GIP_SendVendorMessage(attachment, |
| GIP_SL_ELITE_CONFIG, |
| 0, |
| enable_raw_report, |
| sizeof(enable_raw_report)); |
| } |
| |
| return true; |
| } |
| |
| static bool GIP_SendGuideButtonLED(GIP_Attachment *attachment, Uint8 pattern, Uint8 intensity) |
| { |
| Uint8 buffer[] = { |
| GIP_LED_GUIDE, |
| pattern, |
| intensity, |
| }; |
| |
| if (!GIP_SupportsSystemMessage(attachment, GIP_CMD_LED, false)) { |
| return true; |
| } |
| return GIP_SendSystemMessage(attachment, GIP_CMD_LED, 0, buffer, sizeof(buffer)); |
| } |
| |
| static bool GIP_SendQueryFirmware(GIP_Attachment *attachment, Uint8 slot) |
| { |
| /* The "slot" variable might not be correct; the packet format is still unclear */ |
| Uint8 buffer[] = { 0x1, slot, 0, 0, 0 }; |
| |
| return GIP_SendSystemMessage(attachment, GIP_CMD_FIRMWARE, 0, buffer, sizeof(buffer)); |
| } |
| |
| static bool GIP_SendSetDeviceState(GIP_Attachment *attachment, Uint8 state) |
| { |
| Uint8 buffer[] = { state }; |
| return GIP_SendSystemMessage(attachment, |
| GIP_CMD_SET_DEVICE_STATE, |
| attachment->attachment_index, |
| buffer, |
| sizeof(buffer)); |
| } |
| |
| static bool GIP_SendInitSequence(GIP_Attachment *attachment) |
| { |
| if (attachment->features & GIP_FEATURE_EXTENDED_SET_DEVICE_STATE) { |
| /* |
| * The meaning of this packet is unknown and not documented, but it's |
| * needed for the Elite 2 controller to start up on older firmwares |
| */ |
| static const Uint8 set_device_state[] = { GIP_STATE_UNK6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x55, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; |
| |
| if (!GIP_SendSystemMessage(attachment, |
| GIP_CMD_SET_DEVICE_STATE, |
| 0, |
| set_device_state, |
| sizeof(set_device_state))) |
| { |
| return false; |
| } |
| } |
| if (!GIP_EnableEliteButtons(attachment)) { |
| return false; |
| } |
| if (!GIP_SendSetDeviceState(attachment, GIP_STATE_START)) { |
| return false; |
| } |
| attachment->device_state = GIP_STATE_START; |
| |
| if (!GIP_SendGuideButtonLED(attachment, GIP_LED_GUIDE_ON, 20)) { |
| return false; |
| } |
| |
| if (GIP_SupportsSystemMessage(attachment, GIP_CMD_SECURITY, false) && |
| !(attachment->features & GIP_FEATURE_SECURITY_OPT_OUT)) |
| { |
| /* TODO: Implement Security command property */ |
| Uint8 buffer[] = { 0x1, 0x0 }; |
| GIP_SendSystemMessage(attachment, GIP_CMD_SECURITY, 0, buffer, sizeof(buffer)); |
| } |
| |
| if (GIP_SupportsVendorMessage(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, false)) { |
| GIP_InitialReportsRequest request = { 0 }; |
| GIP_SendVendorMessage(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, 0, (const Uint8 *)&request, sizeof(request)); |
| } |
| |
| if (GIP_SupportsVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, false)) { |
| GIP_SendVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, 0, NULL, 0); |
| } |
| |
| if ((!attachment->attachment_index || GIP_AttachmentIsController(attachment)) && !attachment->joystick) { |
| return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick); |
| } |
| if (attachment->attachment_type == GIP_TYPE_CHATPAD && !attachment->keyboard) { |
| attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment; |
| SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad"); |
| } |
| return true; |
| } |
| |
| static bool GIP_EnsureMetadata(GIP_Attachment *attachment) |
| { |
| switch (attachment->got_metadata) { |
| case GIP_METADATA_GOT: |
| case GIP_METADATA_FAKED: |
| return true; |
| case GIP_METADATA_NONE: |
| if (attachment->device->got_hello) { |
| attachment->device->timeout = GIP_ACME_TIMEOUT; |
| attachment->got_metadata = GIP_METADATA_PENDING; |
| attachment->metadata_next = SDL_GetTicks() + 500; |
| attachment->metadata_retries = 0; |
| return GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); |
| } else { |
| return GIP_SetMetadataDefaults(attachment); |
| } |
| default: |
| return true; |
| } |
| } |
| |
| static bool GIP_SetMetadataDefaults(GIP_Attachment *attachment) |
| { |
| if (attachment->attachment_index == 0) { |
| /* Some decent default settings */ |
| attachment->features |= GIP_FEATURE_MOTOR_CONTROL; |
| attachment->attachment_type = GIP_TYPE_GAMEPAD; |
| attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_GUIDE_BUTTON); |
| |
| if (SDL_IsJoystickXboxSeriesX(attachment->device->device->vendor_id, attachment->device->device->product_id)) { |
| attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP; |
| } |
| } |
| |
| GIP_HandleQuirks(attachment); |
| |
| if (GIP_SupportsSystemMessage(attachment, GIP_CMD_FIRMWARE, false)) { |
| GIP_SendQueryFirmware(attachment, 2); |
| } |
| |
| attachment->got_metadata = GIP_METADATA_FAKED; |
| attachment->device->hello_deadline = 0; |
| if (!attachment->joystick) { |
| return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick); |
| } |
| return true; |
| } |
| |
| static bool GIP_HandleCommandProtocolControl( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| // TODO |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Protocol Control message"); |
| return false; |
| } |
| |
| static bool GIP_HandleCommandHelloDevice( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| GIP_HelloDevice message = {0}; |
| |
| if (num_bytes != 28) { |
| return false; |
| } |
| |
| message.device_id = (Uint64) bytes[0]; |
| message.device_id |= (Uint64) bytes[1] << 8; |
| message.device_id |= (Uint64) bytes[2] << 16; |
| message.device_id |= (Uint64) bytes[3] << 24; |
| message.device_id |= (Uint64) bytes[4] << 32; |
| message.device_id |= (Uint64) bytes[5] << 40; |
| message.device_id |= (Uint64) bytes[6] << 48; |
| message.device_id |= (Uint64) bytes[7] << 56; |
| |
| message.vendor_id = bytes[8]; |
| message.vendor_id |= bytes[9] << 8; |
| |
| message.product_id = bytes[10]; |
| message.product_id |= bytes[11] << 8; |
| |
| message.firmware_major_version = bytes[12]; |
| message.firmware_major_version |= bytes[13] << 8; |
| |
| message.firmware_minor_version = bytes[14]; |
| message.firmware_minor_version |= bytes[15] << 8; |
| |
| message.firmware_build_version = bytes[16]; |
| message.firmware_build_version |= bytes[17] << 8; |
| |
| message.firmware_revision = bytes[18]; |
| message.firmware_revision |= bytes[19] << 8; |
| |
| message.hardware_major_version = bytes[20]; |
| message.hardware_minor_version = bytes[21]; |
| |
| message.rf_proto_major_version = bytes[22]; |
| message.rf_proto_minor_version = bytes[23]; |
| |
| message.security_major_version = bytes[24]; |
| message.security_minor_version = bytes[25]; |
| |
| message.gip_major_version = bytes[26]; |
| message.gip_minor_version = bytes[27]; |
| |
| SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Device hello from %" SDL_PRIx64 " (%04x:%04x)", |
| message.device_id, message.vendor_id, message.product_id); |
| SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Firmware version %d.%d.%d rev %d", |
| message.firmware_major_version, |
| message.firmware_minor_version, |
| message.firmware_build_version, |
| message.firmware_revision); |
| |
| /* |
| * The GIP spec specifies that the host should reject the device if any of these are wrong. |
| * I don't know if Windows or an Xbox do, however, so let's just log warnings instead. |
| */ |
| if (message.rf_proto_major_version != 1 && message.rf_proto_minor_version != 0) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Invalid RF protocol version %d.%d, expected 1.0", |
| message.rf_proto_major_version, message.rf_proto_minor_version); |
| } |
| |
| if (message.security_major_version != 1 && message.security_minor_version != 0) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Invalid security protocol version %d.%d, expected 1.0", |
| message.security_major_version, message.security_minor_version); |
| } |
| |
| if (message.gip_major_version != 1 && message.gip_minor_version != 0) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Invalid GIP version %d.%d, expected 1.0", |
| message.gip_major_version, message.gip_minor_version); |
| } |
| |
| if (header->flags & GIP_FLAG_ATTACHMENT_MASK) { |
| return GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); |
| } else { |
| attachment->firmware_major_version = message.firmware_major_version; |
| attachment->firmware_minor_version = message.firmware_minor_version; |
| |
| if (attachment->attachment_index == 0) { |
| attachment->device->hello_deadline = 0; |
| attachment->device->got_hello = true; |
| } |
| if (attachment->got_metadata == GIP_METADATA_FAKED) { |
| attachment->got_metadata = GIP_METADATA_NONE; |
| } |
| GIP_EnsureMetadata(attachment); |
| } |
| return true; |
| } |
| |
| static bool GIP_HandleCommandStatusDevice( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| GIP_ExtendedStatus status; |
| SDL_Joystick *joystick = NULL; |
| SDL_PowerState power_state; |
| int power_percent = 0; |
| int i; |
| |
| if (num_bytes < 1) { |
| return false; |
| } |
| SDL_zero(status); |
| status.base.battery_level = bytes[0] & 3; |
| status.base.battery_type = (bytes[0] >> 2) & 3; |
| status.base.charge = (bytes[0] >> 4) & 3; |
| status.base.power_level = (bytes[0] >> 6) & 3; |
| |
| if (attachment->joystick) { |
| joystick = SDL_GetJoystickFromID(attachment->joystick); |
| } |
| if (joystick) { |
| switch (status.base.battery_level) { |
| case GIP_BATTERY_CRITICAL: |
| power_percent = 1; |
| break; |
| case GIP_BATTERY_LOW: |
| power_percent = 25; |
| break; |
| case GIP_BATTERY_MEDIUM: |
| power_percent = 50; |
| break; |
| case GIP_BATTERY_FULL: |
| power_percent = 100; |
| break; |
| } |
| switch (status.base.charge) { |
| case GIP_CHARGING: |
| if (status.base.battery_level == GIP_BATTERY_FULL) { |
| power_state = SDL_POWERSTATE_CHARGED; |
| } else { |
| power_state = SDL_POWERSTATE_CHARGING; |
| } |
| break; |
| case GIP_NOT_CHARGING: |
| power_state = SDL_POWERSTATE_ON_BATTERY; |
| break; |
| case GIP_CHARGE_ERROR: |
| default: |
| power_state = SDL_POWERSTATE_UNKNOWN; |
| break; |
| } |
| |
| switch (status.base.battery_type) { |
| case GIP_BATTERY_ABSENT: |
| power_state = SDL_POWERSTATE_NO_BATTERY; |
| break; |
| case GIP_BATTERY_STANDARD: |
| case GIP_BATTERY_RECHARGEABLE: |
| break; |
| default: |
| power_state = SDL_POWERSTATE_UNKNOWN; |
| break; |
| } |
| |
| SDL_SendJoystickPowerInfo(joystick, power_state, power_percent); |
| } |
| |
| if (num_bytes >= 4) { |
| status.device_active = bytes[1] & 1; |
| if (bytes[1] & 2) { |
| /* Events present */ |
| if (num_bytes < 5) { |
| return false; |
| } |
| status.num_events = bytes[4]; |
| if (status.num_events > 5) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Device reported too many events, %d > 5", |
| status.num_events); |
| return false; |
| } |
| if (5 + status.num_events * 10 > num_bytes) { |
| return false; |
| } |
| for (i = 0; i < status.num_events; i++) { |
| status.events[i].event_type = bytes[i * 10 + 5]; |
| status.events[i].event_type |= bytes[i * 10 + 6] << 8; |
| status.events[i].fault_tag = bytes[i * 10 + 7]; |
| status.events[i].fault_tag |= bytes[i * 10 + 8] << 8; |
| status.events[i].fault_tag |= bytes[i * 10 + 9] << 16; |
| status.events[i].fault_tag |= bytes[i * 10 + 10] << 24; |
| status.events[i].fault_tag = bytes[i * 10 + 11]; |
| status.events[i].fault_tag |= bytes[i * 10 + 12] << 8; |
| status.events[i].fault_tag |= bytes[i * 10 + 13] << 16; |
| status.events[i].fault_tag |= bytes[i * 10 + 14] << 24; |
| } |
| } |
| } |
| |
| GIP_EnsureMetadata(attachment); |
| return true; |
| } |
| |
| static bool GIP_HandleCommandMetadataRespose( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| GIP_Metadata metadata = {0}; |
| const GUID *expected_guid = NULL; |
| bool found_expected_guid; |
| bool found_controller_guid = false; |
| int i; |
| |
| if (!GIP_ParseMetadata(&metadata, bytes, num_bytes)) { |
| return false; |
| } |
| |
| if (attachment->got_metadata == GIP_METADATA_GOT) { |
| GIP_MetadataFree(&attachment->metadata); |
| } |
| attachment->metadata = metadata; |
| attachment->got_metadata = GIP_METADATA_GOT; |
| attachment->features = 0; |
| |
| attachment->attachment_type = GIP_TYPE_UNKNOWN; |
| #ifdef DEBUG_XBOX_PROTOCOL |
| for (i = 0; i < metadata.device.num_preferred_types; i++) { |
| const char *type = metadata.device.preferred_types[i]; |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Device preferred type: %s", type); |
| } |
| #endif |
| for (i = 0; i < metadata.device.num_preferred_types; i++) { |
| const char *type = metadata.device.preferred_types[i]; |
| if (SDL_strcmp(type, "Windows.Xbox.Input.Gamepad") == 0) { |
| attachment->attachment_type = GIP_TYPE_GAMEPAD; |
| expected_guid = &GUID_IGamepad; |
| break; |
| } |
| if (SDL_strcmp(type, "Microsoft.Xbox.Input.ArcadeStick") == 0) { |
| attachment->attachment_type = GIP_TYPE_ARCADE_STICK; |
| expected_guid = &GUID_ArcadeStick; |
| break; |
| } |
| if (SDL_strcmp(type, "Windows.Xbox.Input.ArcadeStick") == 0) { |
| attachment->attachment_type = GIP_TYPE_ARCADE_STICK; |
| expected_guid = &GUID_ArcadeStick; |
| break; |
| } |
| if (SDL_strcmp(type, "Microsoft.Xbox.Input.FlightStick") == 0) { |
| attachment->attachment_type = GIP_TYPE_FLIGHT_STICK; |
| expected_guid = &GUID_FlightStick; |
| break; |
| } |
| if (SDL_strcmp(type, "Windows.Xbox.Input.FlightStick") == 0) { |
| attachment->attachment_type = GIP_TYPE_FLIGHT_STICK; |
| expected_guid = &GUID_FlightStick; |
| break; |
| } |
| if (SDL_strcmp(type, "Microsoft.Xbox.Input.Wheel") == 0) { |
| attachment->attachment_type = GIP_TYPE_WHEEL; |
| expected_guid = &GUID_Wheel; |
| break; |
| } |
| if (SDL_strcmp(type, "Windows.Xbox.Input.Wheel") == 0) { |
| attachment->attachment_type = GIP_TYPE_WHEEL; |
| expected_guid = &GUID_Wheel; |
| break; |
| } |
| if (SDL_strcmp(type, "Windows.Xbox.Input.NavigationController") == 0) { |
| attachment->attachment_type = GIP_TYPE_NAVIGATION_CONTROLLER; |
| expected_guid = &GUID_NavigationController; |
| break; |
| } |
| if (SDL_strcmp(type, "Windows.Xbox.Input.Chatpad") == 0) { |
| attachment->attachment_type = GIP_TYPE_CHATPAD; |
| break; |
| } |
| if (SDL_strcmp(type, "Windows.Xbox.Input.Headset") == 0) { |
| attachment->attachment_type = GIP_TYPE_HEADSET; |
| expected_guid = &GUID_IHeadset; |
| break; |
| } |
| } |
| |
| found_expected_guid = !expected_guid; |
| for (i = 0; i < metadata.device.num_supported_interfaces; i++) { |
| const GUID* guid = &metadata.device.supported_interfaces[i]; |
| #ifdef DEBUG_XBOX_PROTOCOL |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Supported interface: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", |
| guid->a, guid->b, guid->c, guid->d[0], guid->d[1], |
| guid->d[2], guid->d[3], guid->d[4], guid->d[5], guid->d[6], guid->d[7]); |
| #endif |
| if (expected_guid && SDL_memcmp(expected_guid, guid, sizeof(GUID)) == 0) { |
| found_expected_guid = true; |
| } |
| if (SDL_memcmp(&GUID_IController, guid, sizeof(GUID)) == 0) { |
| found_controller_guid = true; |
| continue; |
| } |
| if (SDL_memcmp(&GUID_IDevAuthPCOptOut, guid, sizeof(GUID)) == 0) { |
| attachment->features |= GIP_FEATURE_SECURITY_OPT_OUT; |
| continue; |
| } |
| if (SDL_memcmp(&GUID_IConsoleFunctionMap_InputReport, guid, sizeof(GUID)) == 0) { |
| attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP; |
| continue; |
| } |
| if (SDL_memcmp(&GUID_IConsoleFunctionMap_OverflowInputReport, guid, sizeof(GUID)) == 0) { |
| attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW; |
| continue; |
| } |
| if (SDL_memcmp(&GUID_IEliteButtons, guid, sizeof(GUID)) == 0) { |
| attachment->features |= GIP_FEATURE_ELITE_BUTTONS; |
| continue; |
| } |
| if (SDL_memcmp(&GUID_DynamicLatencyInput, guid, sizeof(GUID)) == 0) { |
| attachment->features |= GIP_FEATURE_DYNAMIC_LATENCY_INPUT; |
| continue; |
| } |
| } |
| |
| for (i = 0; i < metadata.num_messages; i++) { |
| GIP_MessageMetadata *message = &metadata.message_metadata[i]; |
| if (message->type == GIP_CMD_DIRECT_MOTOR && message->length >= 9 && |
| (message->flags & GIP_MESSAGE_FLAG_DOWNSTREAM)) { |
| attachment->features |= GIP_FEATURE_MOTOR_CONTROL; |
| } |
| } |
| |
| if (!found_expected_guid || (GIP_AttachmentIsController(attachment) && !found_controller_guid)) { |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Controller was missing expected GUID. This controller probably won't work on an actual Xbox."); |
| } |
| |
| if ((attachment->features & GIP_FEATURE_GUIDE_COLOR) && |
| !GIP_SupportsVendorMessage(attachment, GIP_CMD_GUIDE_COLOR, false)) |
| { |
| attachment->features &= ~GIP_FEATURE_GUIDE_COLOR; |
| } |
| |
| GIP_HandleQuirks(attachment); |
| |
| return GIP_SendInitSequence(attachment); |
| } |
| |
| static bool GIP_HandleCommandSecurity( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| // TODO |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Security message"); |
| return false; |
| } |
| |
| static bool GIP_HandleCommandGuideButtonStatus( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| Uint64 timestamp = SDL_GetTicksNS(); |
| SDL_Joystick *joystick = NULL; |
| |
| if (attachment->device->device->num_joysticks < 1) { |
| return true; |
| } |
| |
| joystick = SDL_GetJoystickFromID(attachment->joystick); |
| if (!joystick) { |
| return false; |
| } |
| if (bytes[1] == VK_LWIN) { |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (bytes[0] & 0x03) != 0); |
| } |
| |
| return true; |
| } |
| |
| static bool GIP_HandleCommandAudioControl( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| // TODO |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Audio Control message"); |
| return false; |
| } |
| |
| static bool GIP_HandleCommandFirmware( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| if (num_bytes < 1) { |
| return false; |
| } |
| if (bytes[0] == 1) { |
| Uint16 major, minor, build, rev; |
| |
| if (num_bytes < 14) { |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short firmware message"); |
| |
| return false; |
| } |
| major = bytes[6]; |
| major |= bytes[7] << 8; |
| minor = bytes[8]; |
| minor |= bytes[9] << 8; |
| build = bytes[10]; |
| build |= bytes[11] << 8; |
| rev = bytes[12]; |
| rev |= bytes[13] << 8; |
| |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Firmware version: %d.%d.%d rev %d", major, minor, build, rev); |
| |
| attachment->firmware_major_version = major; |
| attachment->firmware_minor_version = minor; |
| |
| if (attachment->device->device->vendor_id == USB_VENDOR_MICROSOFT && |
| attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) |
| { |
| return GIP_EnableEliteButtons(attachment); |
| } |
| return true; |
| } else { |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Firmware message"); |
| |
| return false; |
| } |
| } |
| |
| static bool GIP_HandleCommandRawReport( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| Uint64 timestamp = SDL_GetTicksNS(); |
| SDL_Joystick *joystick = NULL; |
| |
| if (attachment->device->device->num_joysticks < 1) { |
| return true; |
| } |
| |
| joystick = SDL_GetJoystickFromID(attachment->joystick); |
| if (!joystick) { |
| return true; |
| } |
| |
| if (num_bytes < 17) { |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short raw report"); |
| return false; |
| } |
| |
| if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS) && attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) { |
| if (bytes[15] & 3) { |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx, |
| 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 1, |
| 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 2, |
| 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 3, |
| 0); |
| } else { |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx, |
| (bytes[GIP_BTN_OFFSET_XBE2] & 0x01) != 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 1, |
| (bytes[GIP_BTN_OFFSET_XBE2] & 0x02) != 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 2, |
| (bytes[GIP_BTN_OFFSET_XBE2] & 0x04) != 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 3, |
| (bytes[GIP_BTN_OFFSET_XBE2] & 0x08) != 0); |
| } |
| } |
| return true; |
| } |
| |
| static bool GIP_HandleCommandHidReport( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| Uint64 timestamp = SDL_GetTicksNS(); |
| // SDL doesn't have HID descriptor parsing, so we have to hardcode for the Chatpad descriptor instead. |
| // I don't know of any other devices that emit HID reports, so this should be safe. |
| if (attachment->attachment_type != GIP_TYPE_CHATPAD || !attachment->keyboard || num_bytes != 8) { |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented HID Report message"); |
| return false; |
| } |
| |
| Uint8 modifiers = bytes[0]; |
| Uint8 changed_modifiers = modifiers ^ attachment->last_modifiers; |
| if (changed_modifiers & 0x02) { |
| if (modifiers & 0x02) { |
| SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_LSHIFT, true); |
| } else { |
| SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_LSHIFT, false); |
| } |
| } |
| // The chatpad has several non-ASCII characters that it sends as Alt codes |
| if (changed_modifiers & 0x04) { |
| if (modifiers & 0x04) { |
| attachment->altcode_digit = 0; |
| attachment->altcode = 0; |
| } else { |
| if (attachment->altcode_digit == 4) { |
| char utf8[4] = {0}; |
| // Some Alt codes don't match their Unicode codepoint for some reason |
| switch (attachment->altcode) { |
| case 128: |
| SDL_UCS4ToUTF8(0x20AC, utf8); |
| break; |
| case 138: |
| SDL_UCS4ToUTF8(0x0160, utf8); |
| break; |
| case 140: |
| SDL_UCS4ToUTF8(0x0152, utf8); |
| break; |
| case 154: |
| SDL_UCS4ToUTF8(0x0161, utf8); |
| break; |
| case 156: |
| SDL_UCS4ToUTF8(0x0153, utf8); |
| break; |
| default: |
| SDL_UCS4ToUTF8(attachment->altcode, utf8); |
| break; |
| } |
| SDL_SendKeyboardText(utf8); |
| } |
| attachment->altcode_digit = -1; |
| SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_NUMLOCKCLEAR, true); |
| SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_NUMLOCKCLEAR, false); |
| } |
| } |
| |
| if (!bytes[2] && attachment->last_key) { |
| if (attachment->last_key == SDL_SCANCODE_CAPSLOCK) { |
| attachment->capslock = !attachment->capslock; |
| } |
| SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, attachment->last_key, false); |
| if (!(attachment->last_modifiers & 0xfd)) { |
| SDL_Keycode keycode = SDL_GetKeymapKeycode(NULL, |
| attachment->last_key, |
| ((attachment->last_modifiers & 0x02) || attachment->capslock) ? SDL_KMOD_SHIFT : 0); |
| if (keycode && keycode < 0x80) { |
| char text[2] = { (char)keycode }; |
| SDL_SendKeyboardText(text); |
| } |
| } |
| attachment->last_key = 0; |
| } else { |
| SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, bytes[2], true); |
| attachment->last_key = bytes[2]; |
| |
| if ((modifiers & 0x04) && attachment->altcode_digit >= 0) { |
| int digit = bytes[2] - SDL_SCANCODE_KP_1 + 1; |
| if (digit < 1 || digit > 10) { |
| attachment->altcode_digit = -1; |
| } else { |
| attachment->altcode_digit++; |
| attachment->altcode *= 10; |
| if (digit < 10) { |
| attachment->altcode += digit; |
| } |
| } |
| } |
| } |
| |
| attachment->last_modifiers = modifiers; |
| return true; |
| } |
| |
| static bool GIP_HandleCommandExtended( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| char serial[33] = {0}; |
| |
| if (num_bytes < 2) { |
| return false; |
| } |
| |
| switch (bytes[0]) { |
| case GIP_EXTCMD_GET_SERIAL_NUMBER: |
| if (bytes[1] != GIP_EXTENDED_STATUS_OK) { |
| return true; |
| } |
| if (header->flags & GIP_FLAG_ATTACHMENT_MASK) { |
| return true; |
| } |
| SDL_memcpy(serial, &bytes[2], SDL_min(sizeof(serial) - 1, num_bytes - 2)); |
| HIDAPI_SetDeviceSerial(attachment->device->device, serial); |
| break; |
| default: |
| // TODO |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Extended message type %02x", bytes[0]); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static void GIP_HandleNavigationReport( |
| GIP_Attachment *attachment, |
| SDL_Joystick *joystick, |
| Uint64 timestamp, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| if (attachment->last_input[0] != bytes[0]) { |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((bytes[0] & 0x04) != 0)); |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((bytes[0] & 0x08) != 0)); |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((bytes[0] & 0x10) != 0)); |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((bytes[0] & 0x20) != 0)); |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((bytes[0] & 0x40) != 0)); |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((bytes[0] & 0x80) != 0)); |
| } |
| |
| if (attachment->last_input[1] != bytes[1]) { |
| Uint8 hat = 0; |
| |
| if (bytes[1] & 0x01) { |
| hat |= SDL_HAT_UP; |
| } |
| if (bytes[1] & 0x02) { |
| hat |= SDL_HAT_DOWN; |
| } |
| if (bytes[1] & 0x04) { |
| hat |= SDL_HAT_LEFT; |
| } |
| if (bytes[1] & 0x08) { |
| hat |= SDL_HAT_RIGHT; |
| } |
| SDL_SendJoystickHat(timestamp, joystick, 0, hat); |
| |
| if (attachment->attachment_type == GIP_TYPE_ARCADE_STICK) { |
| /* Previous */ |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((bytes[1] & 0x10) != 0)); |
| /* Next */ |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((bytes[1] & 0x20) != 0)); |
| } else { |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((bytes[1] & 0x10) != 0)); |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((bytes[1] & 0x20) != 0)); |
| } |
| } |
| } |
| |
| static void GIP_HandleGamepadReport( |
| GIP_Attachment *attachment, |
| SDL_Joystick *joystick, |
| Uint64 timestamp, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| Sint16 axis; |
| |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((bytes[1] & 0x40) != 0)); |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((bytes[1] & 0x80) != 0)); |
| |
| axis = bytes[2]; |
| axis |= bytes[3] << 8; |
| axis = SDL_clamp(axis, 0, 1023); |
| axis = (axis - 512) * 64; |
| if (axis == 32704) { |
| axis = 32767; |
| } |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); |
| |
| axis = bytes[4]; |
| axis |= bytes[5] << 8; |
| axis = SDL_clamp(axis, 0, 1023); |
| axis = (axis - 512) * 64; |
| if (axis == 32704) { |
| axis = 32767; |
| } |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); |
| |
| axis = bytes[6]; |
| axis |= bytes[7] << 8; |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); |
| axis = bytes[8]; |
| axis |= bytes[9] << 8; |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); |
| axis = bytes[10]; |
| axis |= bytes[11] << 8; |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); |
| axis = bytes[12]; |
| axis |= bytes[13] << 8; |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); |
| } |
| |
| static void GIP_HandleArcadeStickReport( |
| GIP_Attachment *attachment, |
| SDL_Joystick *joystick, |
| Uint64 timestamp, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| Sint16 axis; |
| axis = bytes[2]; |
| axis |= bytes[3] << 8; |
| axis = SDL_clamp(axis, 0, 1023); |
| axis = (axis - 512) * 64; |
| if (axis == 32704) { |
| axis = 32767; |
| } |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); |
| |
| axis = bytes[4]; |
| axis |= bytes[5] << 8; |
| axis = SDL_clamp(axis, 0, 1023); |
| axis = (axis - 512) * 64; |
| if (axis == 32704) { |
| axis = 32767; |
| } |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); |
| |
| if (num_bytes >= 19) { |
| /* Extra button 6 */ |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (bytes[18] & 0x40) ? 32767 : -32768); |
| /* Extra button 7 */ |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (bytes[18] & 0x80) ? 32767 : -32768); |
| } |
| } |
| |
| static void GIP_HandleFlightStickReport( |
| GIP_Attachment *attachment, |
| SDL_Joystick *joystick, |
| Uint64 timestamp, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| Sint16 axis; |
| int i; |
| |
| if (num_bytes < 19) { |
| return; |
| } |
| |
| if (attachment->last_input[2] != bytes[2]) { |
| /* Fire 1 and 2 */ |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((bytes[2] & 0x01) != 0)); |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((bytes[2] & 0x02) != 0)); |
| } |
| for (i = 0; i < attachment->extra_buttons;) { |
| if (attachment->last_input[i / 8 + 3] != bytes[i / 8 + 3]) { |
| for (; i < attachment->extra_buttons; i++) { |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| (Uint8) (attachment->extra_button_idx + i), |
| ((bytes[i / 8 + 3] & (1u << i)) != 0)); |
| } |
| } else { |
| i += 8; |
| } |
| } |
| |
| /* Roll, pitch and yaw are signed. Throttle and any extra axes are unsigned. All values are full-range. */ |
| axis = bytes[11]; |
| axis |= bytes[12] << 8; |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); |
| |
| axis = bytes[13]; |
| axis |= bytes[14] << 8; |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); |
| |
| axis = bytes[15]; |
| axis |= bytes[16] << 8; |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); |
| |
| /* There are no more signed values, so skip RIGHTY */ |
| |
| axis = (bytes[18] << 8) - 0x8000; |
| axis |= bytes[17]; |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); |
| |
| for (i = 0; i < attachment->extra_axes; i++) { |
| if (20 + i * 2 >= num_bytes) { |
| return; |
| } |
| axis = (bytes[20 + i * 2] << 8) - 0x8000; |
| axis |= bytes[19 + i * 2]; |
| SDL_SendJoystickAxis(timestamp, joystick, (Uint8) (SDL_GAMEPAD_AXIS_RIGHT_TRIGGER + i), axis); |
| } |
| } |
| |
| static bool GIP_HandleLLInputReport( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| Uint64 timestamp = SDL_GetTicksNS(); |
| SDL_Joystick *joystick = NULL; |
| |
| if (attachment->device->device->num_joysticks < 1) { |
| GIP_EnsureMetadata(attachment); |
| if (attachment->got_metadata != GIP_METADATA_GOT && attachment->got_metadata != GIP_METADATA_FAKED) { |
| return true; |
| } |
| } |
| |
| joystick = SDL_GetJoystickFromID(attachment->joystick); |
| if (!joystick) { |
| return false; |
| } |
| |
| if (attachment->device_state != GIP_STATE_START) { |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding early input report"); |
| attachment->device_state = GIP_STATE_START; |
| return true; |
| } |
| |
| if (num_bytes < 14) { |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short input report"); |
| return false; |
| } |
| |
| GIP_HandleNavigationReport(attachment, joystick, timestamp, bytes, num_bytes); |
| |
| switch (attachment->attachment_type) { |
| case GIP_TYPE_GAMEPAD: |
| default: |
| GIP_HandleGamepadReport(attachment, joystick, timestamp, bytes, num_bytes); |
| break; |
| case GIP_TYPE_ARCADE_STICK: |
| GIP_HandleArcadeStickReport(attachment, joystick, timestamp, bytes, num_bytes); |
| break; |
| case GIP_TYPE_FLIGHT_STICK: |
| GIP_HandleFlightStickReport(attachment, joystick, timestamp, bytes, num_bytes); |
| break; |
| } |
| |
| if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) { |
| bool clear = false; |
| if (attachment->xbe_format == GIP_BTN_FMT_XBE1 && |
| num_bytes > GIP_BTN_OFFSET_XBE1 && |
| attachment->last_input[GIP_BTN_OFFSET_XBE1] != bytes[GIP_BTN_OFFSET_XBE1] && |
| (bytes[GIP_BTN_OFFSET_XBE1] & 0x10)) |
| { |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx, |
| (bytes[GIP_BTN_OFFSET_XBE1] & 0x02) != 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 1, |
| (bytes[GIP_BTN_OFFSET_XBE1] & 0x08) != 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 2, |
| (bytes[GIP_BTN_OFFSET_XBE1] & 0x01) != 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 3, |
| (bytes[GIP_BTN_OFFSET_XBE1] & 0x04) != 0); |
| } else if ((attachment->xbe_format == GIP_BTN_FMT_XBE2_4 || |
| attachment->xbe_format == GIP_BTN_FMT_XBE2_5) && |
| num_bytes > GIP_BTN_OFFSET_XBE2) |
| { |
| int profile_offset = attachment->xbe_format == GIP_BTN_FMT_XBE2_4 ? 15 : 20; |
| if (attachment->last_input[GIP_BTN_OFFSET_XBE2] != bytes[GIP_BTN_OFFSET_XBE2] || |
| attachment->last_input[profile_offset] != bytes[profile_offset]) |
| { |
| if (bytes[profile_offset] & 3) { |
| clear = true; |
| } else { |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx, |
| (bytes[GIP_BTN_OFFSET_XBE2] & 0x01) != 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 1, |
| (bytes[GIP_BTN_OFFSET_XBE2] & 0x02) != 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 2, |
| (bytes[GIP_BTN_OFFSET_XBE2] & 0x04) != 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 3, |
| (bytes[GIP_BTN_OFFSET_XBE2] & 0x08) != 0); |
| } |
| } |
| } else { |
| clear = true; |
| } |
| if (clear) { |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx, |
| 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 1, |
| 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 2, |
| 0); |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->paddle_idx + 3, |
| 0); |
| } |
| } |
| |
| if ((attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) && num_bytes >= 32) { |
| int function_map_offset = -1; |
| if (attachment->features & GIP_FEATURE_DYNAMIC_LATENCY_INPUT) { |
| /* The dynamic latency input bytes are after the console function map */ |
| if (num_bytes >= 40) { |
| function_map_offset = num_bytes - 26; |
| } |
| } else { |
| function_map_offset = num_bytes - 18; |
| } |
| if (function_map_offset >= 14) { |
| if (attachment->last_input[function_map_offset] != bytes[function_map_offset]) { |
| SDL_SendJoystickButton(timestamp, |
| joystick, |
| attachment->share_button_idx, |
| (bytes[function_map_offset] & 0x01) != 0); |
| } |
| } |
| } |
| |
| SDL_memcpy(attachment->last_input, bytes, SDL_min(num_bytes, sizeof(attachment->last_input))); |
| |
| return true; |
| } |
| |
| static bool GIP_HandleLLStaticConfiguration( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| // TODO |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Static Configuration message"); |
| return false; |
| } |
| |
| static bool GIP_HandleLLButtonInfoReport( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| // TODO |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Button Info Report message"); |
| return false; |
| } |
| |
| static bool GIP_HandleLLOverflowInputReport( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| // TODO |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Overflow Input Report message"); |
| return false; |
| } |
| |
| static bool GIP_HandleAudioData( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| // TODO |
| SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Audio Data message"); |
| return false; |
| } |
| |
| static bool GIP_HandleSystemMessage( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| if (attachment->attachment_index > 0 && attachment->attachment_type == GIP_TYPE_UNKNOWN) { |
| // XXX If we reattach to a controller after it's been initialized, it might have |
| // attachments we don't know about. Try to figure out what this one is. |
| if (header->message_type == GIP_CMD_HID_REPORT && num_bytes == 8) { |
| if (!attachment->keyboard) { |
| attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment; |
| SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad"); |
| } |
| attachment->attachment_type = GIP_TYPE_CHATPAD; |
| attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_HID_REPORT); |
| } |
| } |
| if (!GIP_SupportsSystemMessage(attachment, header->message_type, true)) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Received claimed-unsupported system message type %02x", |
| header->message_type); |
| return false; |
| } |
| switch (header->message_type) { |
| case GIP_CMD_PROTO_CONTROL: |
| return GIP_HandleCommandProtocolControl(attachment, header, bytes, num_bytes); |
| case GIP_CMD_HELLO_DEVICE: |
| return GIP_HandleCommandHelloDevice(attachment, header, bytes, num_bytes); |
| case GIP_CMD_STATUS_DEVICE: |
| return GIP_HandleCommandStatusDevice(attachment, header, bytes, num_bytes); |
| case GIP_CMD_METADATA: |
| return GIP_HandleCommandMetadataRespose(attachment, header, bytes, num_bytes); |
| case GIP_CMD_SECURITY: |
| return GIP_HandleCommandSecurity(attachment, header, bytes, num_bytes); |
| case GIP_CMD_GUIDE_BUTTON: |
| return GIP_HandleCommandGuideButtonStatus(attachment, header, bytes, num_bytes); |
| case GIP_CMD_AUDIO_CONTROL: |
| return GIP_HandleCommandAudioControl(attachment, header, bytes, num_bytes); |
| case GIP_CMD_FIRMWARE: |
| return GIP_HandleCommandFirmware(attachment, header, bytes, num_bytes); |
| case GIP_CMD_HID_REPORT: |
| return GIP_HandleCommandHidReport(attachment, header, bytes, num_bytes); |
| case GIP_CMD_EXTENDED: |
| return GIP_HandleCommandExtended(attachment, header, bytes, num_bytes); |
| case GIP_AUDIO_DATA: |
| return GIP_HandleAudioData(attachment, header, bytes, num_bytes); |
| default: |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Received unknown system message type %02x", |
| header->message_type); |
| return false; |
| } |
| } |
| |
| static GIP_Attachment *GIP_EnsureAttachment(GIP_Device *device, Uint8 attachment_index) |
| { |
| GIP_Attachment *attachment = device->attachments[attachment_index]; |
| if (!attachment) { |
| attachment = SDL_calloc(1, sizeof(*attachment)); |
| attachment->attachment_index = attachment_index; |
| if (attachment_index > 0) { |
| attachment->attachment_type = GIP_TYPE_UNKNOWN; |
| } |
| attachment->device = device; |
| attachment->metadata.device.in_system_messages[0] = GIP_DEFAULT_IN_SYSTEM_MESSAGES; |
| attachment->metadata.device.out_system_messages[0] = GIP_DEFAULT_OUT_SYSTEM_MESSAGES; |
| device->attachments[attachment_index] = attachment; |
| } |
| return attachment; |
| } |
| |
| static bool GIP_HandleMessage( |
| GIP_Attachment *attachment, |
| const GIP_Header *header, |
| const Uint8 *bytes, |
| int num_bytes) |
| { |
| if (header->flags & GIP_FLAG_SYSTEM) { |
| return GIP_HandleSystemMessage(attachment, header, bytes, num_bytes); |
| } else { |
| switch (header->message_type) { |
| case GIP_CMD_RAW_REPORT: |
| if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) { |
| return GIP_HandleCommandRawReport(attachment, header, bytes, num_bytes); |
| } |
| break; |
| case GIP_LL_INPUT_REPORT: |
| return GIP_HandleLLInputReport(attachment, header, bytes, num_bytes); |
| case GIP_LL_STATIC_CONFIGURATION: |
| return GIP_HandleLLStaticConfiguration(attachment, header, bytes, num_bytes); |
| case GIP_LL_BUTTON_INFO_REPORT: |
| return GIP_HandleLLButtonInfoReport(attachment, header, bytes, num_bytes); |
| case GIP_LL_OVERFLOW_INPUT_REPORT: |
| return GIP_HandleLLOverflowInputReport(attachment, header, bytes, num_bytes); |
| } |
| } |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Received unknown vendor message type %02x", |
| header->message_type); |
| return false; |
| } |
| |
| static void GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_bytes) |
| { |
| GIP_Header header; |
| int offset = 3; |
| bool ok = true; |
| Uint64 fragment_offset = 0; |
| Uint16 bytes_remaining = 0; |
| bool is_fragment; |
| Uint8 attachment_index; |
| GIP_Attachment *attachment; |
| |
| if (num_bytes < 5) { |
| return; |
| } |
| |
| header.message_type = bytes[0]; |
| header.flags = bytes[1]; |
| header.sequence_id = bytes[2]; |
| offset += GIP_DecodeLength(&header.length, &bytes[offset], num_bytes - offset); |
| |
| is_fragment = header.flags & GIP_FLAG_FRAGMENT; |
| attachment_index = header.flags & GIP_FLAG_ATTACHMENT_MASK; |
| attachment = GIP_EnsureAttachment(device, attachment_index); |
| |
| #ifdef DEBUG_XBOX_PROTOCOL |
| HIDAPI_DumpPacket("GIP received message: size = %d", bytes, num_bytes); |
| #endif |
| |
| /* Handle coalescing fragmented messages */ |
| if (is_fragment) { |
| if (header.flags & GIP_FLAG_INIT_FRAG) { |
| Uint64 total_length; |
| if (attachment->fragment_message) { |
| /* |
| * Reset fragment buffer if we get a new initial |
| * fragment before finishing the last message. |
| * TODO: Is this the correct behavior? |
| */ |
| if (attachment->fragment_data) { |
| SDL_free(attachment->fragment_data); |
| attachment->fragment_data = NULL; |
| } |
| } |
| offset += GIP_DecodeLength(&total_length, &bytes[offset], num_bytes - offset); |
| if (total_length > MAX_MESSAGE_LENGTH) { |
| return; |
| } |
| attachment->total_length = (Uint16) total_length; |
| attachment->fragment_message = header.message_type; |
| if (header.length > num_bytes - offset) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Received fragment that claims to be %" SDL_PRIu64 " bytes, expected %i", |
| header.length, num_bytes - offset); |
| return; |
| } |
| if (header.length > total_length) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Received too long fragment, %" SDL_PRIu64 " bytes, exceeds %d", |
| header.length, attachment->total_length); |
| return; |
| } |
| attachment->fragment_data = SDL_malloc(attachment->total_length); |
| SDL_memcpy(attachment->fragment_data, &bytes[offset], (size_t) header.length); |
| fragment_offset = header.length; |
| attachment->fragment_offset = (Uint32) fragment_offset; |
| bytes_remaining = (Uint16) (attachment->total_length - fragment_offset); |
| } else { |
| if (header.message_type != attachment->fragment_message) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Received out of sequence message type %02x, expected %02x", |
| header.message_type, attachment->fragment_message); |
| GIP_FragmentFailed(attachment, &header); |
| return; |
| } |
| |
| offset += GIP_DecodeLength(&fragment_offset, &bytes[offset], num_bytes - offset); |
| if (fragment_offset != attachment->fragment_offset) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Received out of sequence fragment, (claimed %" SDL_PRIu64 ", expected %d)", |
| fragment_offset, attachment->fragment_offset); |
| GIP_Acknowledge(device, |
| &header, |
| attachment->fragment_offset, |
| (Uint16) (attachment->total_length - attachment->fragment_offset)); |
| return; |
| } else if (fragment_offset + header.length > attachment->total_length) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Received too long fragment, %" SDL_PRIu64 " exceeds %d", |
| fragment_offset + header.length, attachment->total_length); |
| GIP_FragmentFailed(attachment, &header); |
| return; |
| } |
| |
| bytes_remaining = attachment->total_length - (Uint16) (fragment_offset + header.length); |
| if (header.length != 0) { |
| SDL_memcpy(&attachment->fragment_data[fragment_offset], &bytes[offset], (size_t) header.length); |
| } else { |
| ok = GIP_HandleMessage(attachment, &header, attachment->fragment_data, attachment->total_length); |
| if (attachment->fragment_data) { |
| SDL_free(attachment->fragment_data); |
| attachment->fragment_data = NULL; |
| } |
| attachment->fragment_message = 0; |
| } |
| fragment_offset += header.length; |
| attachment->fragment_offset = (Uint16) fragment_offset; |
| } |
| attachment->fragment_timer = SDL_GetTicks(); |
| } else if (header.length + offset > num_bytes) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, |
| "GIP: Received message with erroneous length (claimed %" SDL_PRIu64 ", actual %d), discarding", |
| header.length + offset, num_bytes); |
| return; |
| } else { |
| num_bytes -= offset; |
| bytes += offset; |
| fragment_offset = header.length; |
| ok = GIP_HandleMessage(attachment, &header, bytes, num_bytes); |
| } |
| |
| if (ok && (header.flags & GIP_FLAG_ACME)) { |
| GIP_Acknowledge(device, &header, (Uint32) fragment_offset, bytes_remaining); |
| } |
| } |
| |
| static void HIDAPI_DriverGIP_RumbleSent(void *userdata) |
| { |
| GIP_Attachment *ctx = (GIP_Attachment *)userdata; |
| ctx->rumble_time = SDL_GetTicks(); |
| } |
| |
| static bool HIDAPI_DriverGIP_UpdateRumble(GIP_Attachment *attachment) |
| { |
| GIP_DirectMotor motor; |
| |
| if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL)) { |
| return true; |
| } |
| |
| if (attachment->rumble_state == GIP_RUMBLE_STATE_QUEUED && attachment->rumble_time) { |
| attachment->rumble_state = GIP_RUMBLE_STATE_BUSY; |
| } |
| |
| if (attachment->rumble_state == GIP_RUMBLE_STATE_BUSY) { |
| const int RUMBLE_BUSY_TIME_MS = 10; |
| if (SDL_GetTicks() >= (attachment->rumble_time + RUMBLE_BUSY_TIME_MS)) { |
| attachment->rumble_time = 0; |
| attachment->rumble_state = GIP_RUMBLE_STATE_IDLE; |
| } |
| } |
| |
| if (!attachment->rumble_pending) { |
| return true; |
| } |
| |
| if (attachment->rumble_state != GIP_RUMBLE_STATE_IDLE) { |
| return true; |
| } |
| |
| // We're no longer pending, even if we fail to send the rumble below |
| attachment->rumble_pending = false; |
| |
| motor.motor_bitmap = GIP_MOTOR_ALL; |
| motor.left_impulse_level = attachment->left_impulse_level; |
| motor.right_impulse_level = attachment->right_impulse_level; |
| motor.left_vibration_level = attachment->left_vibration_level; |
| motor.right_vibration_level = attachment->right_vibration_level; |
| motor.duration = SDL_RUMBLE_RESEND_MS / 10 + 5; // Add a 50ms leniency, just in case |
| motor.delay = 0; |
| motor.repeat = 0; |
| |
| Uint8 message[9] = {0}; |
| SDL_memcpy(&message[1], &motor, sizeof(motor)); |
| if (!GIP_SendRawMessage(attachment->device, |
| GIP_CMD_DIRECT_MOTOR, |
| attachment->attachment_index, |
| GIP_SequenceNext(attachment, GIP_CMD_DIRECT_MOTOR, false), |
| message, |
| sizeof(message), |
| true, |
| HIDAPI_DriverGIP_RumbleSent, |
| attachment)) |
| { |
| return SDL_SetError("Couldn't send rumble packet"); |
| } |
| |
| attachment->rumble_state = GIP_RUMBLE_STATE_QUEUED; |
| |
| return true; |
| } |
| |
| static void HIDAPI_DriverGIP_RegisterHints(SDL_HintCallback callback, void *userdata) |
| { |
| SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP, callback, userdata); |
| SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, callback, userdata); |
| } |
| |
| static void HIDAPI_DriverGIP_UnregisterHints(SDL_HintCallback callback, void *userdata) |
| { |
| SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP, callback, userdata); |
| SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, callback, userdata); |
| } |
| |
| static bool HIDAPI_DriverGIP_IsEnabled(void) |
| { |
| return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GIP, |
| SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, |
| SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, |
| SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)))); |
| } |
| |
| static bool HIDAPI_DriverGIP_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) |
| { |
| // Xbox One controllers speak HID over bluetooth instead of GIP |
| if (device && device->is_bluetooth) { |
| return false; |
| } |
| #if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI) |
| if (!SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) { |
| // On macOS we get a shortened version of the real report and |
| // you can't write output reports for wired controllers, so |
| // we'll just use the GCController support instead. |
| return false; |
| } |
| #endif |
| return (type == SDL_GAMEPAD_TYPE_XBOXONE); |
| } |
| |
| static bool HIDAPI_DriverGIP_InitDevice(SDL_HIDAPI_Device *device) |
| { |
| GIP_Device *ctx; |
| GIP_Attachment *attachment; |
| |
| ctx = (GIP_Device *)SDL_calloc(1, sizeof(*ctx)); |
| if (!ctx) { |
| return false; |
| } |
| ctx->device = device; |
| ctx->reset_for_metadata = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, false); |
| |
| attachment = GIP_EnsureAttachment(ctx, 0); |
| GIP_HandleQuirks(attachment); |
| |
| if (attachment->quirks & GIP_QUIRK_NO_HELLO) { |
| ctx->got_hello = true; |
| GIP_EnsureMetadata(attachment); |
| } else { |
| ctx->hello_deadline = SDL_GetTicks() + GIP_HELLO_TIMEOUT; |
| } |
| |
| device->context = ctx; |
| device->type = SDL_GAMEPAD_TYPE_XBOXONE; |
| |
| return true; |
| } |
| |
| static int HIDAPI_DriverGIP_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) |
| { |
| return -1; |
| } |
| |
| static void HIDAPI_DriverGIP_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) |
| { |
| } |
| |
| static GIP_Attachment * HIDAPI_DriverGIP_FindAttachment(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) |
| { |
| GIP_Device *ctx = (GIP_Device *)device->context; |
| int i; |
| |
| SDL_AssertJoysticksLocked(); |
| |
| for (i = 0; i < MAX_ATTACHMENTS; i++) { |
| if (ctx->attachments[i] && ctx->attachments[i]->joystick == joystick->instance_id) { |
| return ctx->attachments[i]; |
| } |
| } |
| return NULL; |
| } |
| |
| static bool HIDAPI_DriverGIP_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) |
| { |
| GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); |
| if (!attachment) { |
| return SDL_SetError("Invalid joystick"); |
| } |
| |
| SDL_AssertJoysticksLocked(); |
| |
| attachment->left_impulse_level = 0; |
| attachment->right_impulse_level = 0; |
| attachment->left_vibration_level = 0; |
| attachment->right_vibration_level = 0; |
| attachment->rumble_state = GIP_RUMBLE_STATE_IDLE; |
| attachment->rumble_time = 0; |
| attachment->rumble_pending = false; |
| SDL_zeroa(attachment->last_input); |
| |
| // Initialize the joystick capabilities |
| joystick->nbuttons = 11; |
| GIP_EnableEliteButtons(attachment); |
| if (attachment->xbe_format != GIP_BTN_FMT_UNKNOWN || |
| (device->vendor_id == USB_VENDOR_MICROSOFT && |
| device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2)) |
| { |
| attachment->paddle_idx = (Uint8) joystick->nbuttons; |
| joystick->nbuttons += 4; |
| } |
| if (attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) { |
| attachment->share_button_idx = (Uint8) joystick->nbuttons; |
| joystick->nbuttons++; |
| } |
| if (attachment->extra_buttons > 0) { |
| attachment->extra_button_idx = (Uint8) joystick->nbuttons; |
| joystick->nbuttons += attachment->extra_buttons; |
| } |
| |
| joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; |
| if (attachment->attachment_type == GIP_TYPE_FLIGHT_STICK) { |
| /* Flight sticks have at least 4 axes, but only 3 are signed values, so we leave RIGHTY unused */ |
| joystick->naxes += attachment->extra_axes - 1; |
| } |
| |
| joystick->nhats = 1; |
| |
| return true; |
| } |
| |
| static bool HIDAPI_DriverGIP_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) |
| { |
| GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); |
| if (!attachment) { |
| return SDL_SetError("Invalid joystick"); |
| } |
| |
| if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL)) { |
| return SDL_Unsupported(); |
| } |
| |
| // Magnitude is 1..100 so scale the 16-bit input here |
| attachment->left_vibration_level = (Uint8)(low_frequency_rumble / 655); |
| attachment->right_vibration_level = (Uint8)(high_frequency_rumble / 655); |
| attachment->rumble_pending = true; |
| |
| return HIDAPI_DriverGIP_UpdateRumble(attachment); |
| } |
| |
| static bool HIDAPI_DriverGIP_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) |
| { |
| GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); |
| if (!attachment) { |
| return SDL_SetError("Invalid joystick"); |
| } |
| |
| if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL) || (attachment->quirks & GIP_QUIRK_NO_IMPULSE_VIBRATION)) { |
| return SDL_Unsupported(); |
| } |
| |
| // Magnitude is 1..100 so scale the 16-bit input here |
| attachment->left_impulse_level = (Uint8)(left_rumble / 655); |
| attachment->right_impulse_level = (Uint8)(right_rumble / 655); |
| attachment->rumble_pending = true; |
| |
| return HIDAPI_DriverGIP_UpdateRumble(attachment); |
| } |
| |
| static Uint32 HIDAPI_DriverGIP_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) |
| { |
| GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); |
| Uint32 result = 0; |
| if (!attachment) { |
| return 0; |
| } |
| |
| if (attachment->features & GIP_FEATURE_MOTOR_CONTROL) { |
| result |= SDL_JOYSTICK_CAP_RUMBLE; |
| if (!(attachment->quirks & GIP_QUIRK_NO_IMPULSE_VIBRATION)) { |
| result |= SDL_JOYSTICK_CAP_TRIGGER_RUMBLE; |
| } |
| } |
| |
| if (attachment->features & GIP_FEATURE_GUIDE_COLOR) { |
| result |= SDL_JOYSTICK_CAP_RGB_LED; |
| } |
| |
| return result; |
| } |
| |
| static bool HIDAPI_DriverGIP_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) |
| { |
| GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); |
| Uint8 buffer[] = { 0x00, 0x00, 0x00, 0x00, 0x00 }; |
| |
| if (!attachment) { |
| return SDL_SetError("Invalid joystick"); |
| } |
| |
| if (!(attachment->features & GIP_FEATURE_GUIDE_COLOR)) { |
| return SDL_Unsupported(); |
| } |
| |
| buffer[1] = 0x00; // Whiteness? Sets white intensity when RGB is 0, seems additive |
| buffer[2] = red; |
| buffer[3] = green; |
| buffer[4] = blue; |
| |
| if (!GIP_SendVendorMessage(attachment, GIP_CMD_GUIDE_COLOR, 0, buffer, sizeof(buffer))) { |
| return SDL_SetError("Couldn't send LED packet"); |
| } |
| return true; |
| } |
| |
| static bool HIDAPI_DriverGIP_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) |
| { |
| return SDL_Unsupported(); |
| } |
| |
| |
| static bool HIDAPI_DriverGIP_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) |
| { |
| return SDL_Unsupported(); |
| } |
| |
| static bool HIDAPI_DriverGIP_UpdateDevice(SDL_HIDAPI_Device *device) |
| { |
| GIP_Device *ctx = (GIP_Device *)device->context; |
| Uint8 bytes[USB_PACKET_LENGTH]; |
| int i; |
| int num_bytes; |
| bool perform_reset = false; |
| Uint64 timestamp; |
| |
| while ((num_bytes = SDL_hid_read_timeout(device->dev, bytes, sizeof(bytes), ctx->timeout)) > 0) { |
| ctx->timeout = 0; |
| GIP_ReceivePacket(ctx, bytes, num_bytes); |
| } |
| |
| timestamp = SDL_GetTicks(); |
| if (ctx->hello_deadline && timestamp >= ctx->hello_deadline) { |
| ctx->hello_deadline = 0; |
| perform_reset = true; |
| } |
| for (i = 0; i < MAX_ATTACHMENTS; i++) { |
| GIP_Attachment *attachment = ctx->attachments[i]; |
| if (!attachment) { |
| continue; |
| } |
| if (attachment->fragment_message && timestamp >= attachment->fragment_timer + 1000) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Reliable message transfer failed"); |
| attachment->fragment_message = 0; |
| } |
| if (!perform_reset && |
| attachment->got_metadata == GIP_METADATA_PENDING && |
| timestamp >= attachment->metadata_next && |
| attachment->fragment_message != GIP_CMD_METADATA) |
| { |
| if (attachment->metadata_retries < 3) { |
| SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Retrying metadata request"); |
| attachment->metadata_retries++; |
| attachment->metadata_next = timestamp + 500; |
| GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); |
| } else { |
| perform_reset = true; |
| } |
| } |
| if (perform_reset) { |
| if (ctx->reset_for_metadata) { |
| GIP_SendSetDeviceState(attachment, GIP_STATE_RESET); |
| } else { |
| GIP_SetMetadataDefaults(attachment); |
| GIP_SendInitSequence(attachment); |
| } |
| perform_reset = false; |
| } |
| HIDAPI_DriverGIP_UpdateRumble(attachment); |
| } |
| |
| if (num_bytes < 0 && device->num_joysticks > 0) { |
| // Read error, device is disconnected |
| for (i = 0; i < MAX_ATTACHMENTS; i++) { |
| GIP_Attachment *attachment = ctx->attachments[i]; |
| if (attachment) { |
| HIDAPI_JoystickDisconnected(device, attachment->joystick); |
| } |
| } |
| } |
| return (num_bytes >= 0); |
| } |
| static void HIDAPI_DriverGIP_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) |
| { |
| } |
| |
| static void HIDAPI_DriverGIP_FreeDevice(SDL_HIDAPI_Device *device) |
| { |
| GIP_Device *context = (GIP_Device *)device->context; |
| int i; |
| |
| for (i = 0; i < MAX_ATTACHMENTS; i++) { |
| GIP_Attachment *attachment = context->attachments[i]; |
| if (!attachment) { |
| continue; |
| } |
| if (attachment->fragment_data) { |
| SDL_free(attachment->fragment_data); |
| attachment->fragment_data = NULL; |
| } |
| if (attachment->keyboard) { |
| SDL_RemoveKeyboard(attachment->keyboard); |
| } |
| GIP_MetadataFree(&attachment->metadata); |
| SDL_free(attachment); |
| context->attachments[i] = NULL; |
| } |
| } |
| |
| SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGIP = { |
| SDL_HINT_JOYSTICK_HIDAPI_GIP, |
| true, |
| HIDAPI_DriverGIP_RegisterHints, |
| HIDAPI_DriverGIP_UnregisterHints, |
| HIDAPI_DriverGIP_IsEnabled, |
| HIDAPI_DriverGIP_IsSupportedDevice, |
| HIDAPI_DriverGIP_InitDevice, |
| HIDAPI_DriverGIP_GetDevicePlayerIndex, |
| HIDAPI_DriverGIP_SetDevicePlayerIndex, |
| HIDAPI_DriverGIP_UpdateDevice, |
| HIDAPI_DriverGIP_OpenJoystick, |
| HIDAPI_DriverGIP_RumbleJoystick, |
| HIDAPI_DriverGIP_RumbleJoystickTriggers, |
| HIDAPI_DriverGIP_GetJoystickCapabilities, |
| HIDAPI_DriverGIP_SetJoystickLED, |
| HIDAPI_DriverGIP_SendJoystickEffect, |
| HIDAPI_DriverGIP_SetJoystickSensorsEnabled, |
| HIDAPI_DriverGIP_CloseJoystick, |
| HIDAPI_DriverGIP_FreeDevice, |
| }; |
| |
| #endif // SDL_JOYSTICK_HIDAPI_GIP |
| |
| #endif // SDL_JOYSTICK_HIDAPI |