mirror of https://github.com/libsdl-org/SDL.git
2837 lines
93 KiB
C
2837 lines
93 KiB
C
/*
|
|
Simple DirectMedia Layer
|
|
Copyright (C) 1997-2025 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_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 Comand 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_AttachmentType;
|
|
|
|
typedef enum
|
|
{
|
|
GIP_RUMBLE_STATE_IDLE,
|
|
GIP_RUMBLE_STATE_QUEUED,
|
|
GIP_RUMBLE_STATE_BUSY,
|
|
} GIP_RumbleState;
|
|
|
|
typedef enum
|
|
{
|
|
GIP_PADDLES_UNKNOWN,
|
|
GIP_PADDLES_XBE1,
|
|
GIP_PADDLES_XBE2_RAW,
|
|
GIP_PADDLES_XBE2,
|
|
} GIP_PaddleFormat;
|
|
|
|
/* 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_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_IHeadset, 0xbc25d1a3, 0xc24e, 0x4992, 0x9d, 0xda, 0xef, 0x4f, 0x12, 0x3e, 0xf5, 0xdc);
|
|
* 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);
|
|
*/
|
|
|
|
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_PaddleFormat paddle_format;
|
|
Uint32 features;
|
|
Uint32 quirks;
|
|
Uint8 share_button_idx;
|
|
Uint8 paddle_idx;
|
|
int paddle_offset;
|
|
|
|
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;
|
|
}
|
|
|
|
static void GIP_MetadataFree(GIP_Metadata *metadata)
|
|
{
|
|
if (metadata->device.audio_formats) {
|
|
SDL_free(metadata->device.audio_formats);
|
|
}
|
|
if (metadata->device.preferred_types) {
|
|
int i;
|
|
for (i = 0; i < metadata->device.num_preferred_types; i++) {
|
|
if (metadata->device.preferred_types[i]) {
|
|
SDL_free(metadata->device.preferred_types[i]);
|
|
}
|
|
}
|
|
SDL_free(metadata->device.preferred_types);
|
|
}
|
|
if (metadata->device.supported_interfaces) {
|
|
SDL_free(metadata->device.supported_interfaces);
|
|
}
|
|
if (metadata->device.hid_descriptor) {
|
|
SDL_free(metadata->device.hid_descriptor);
|
|
}
|
|
|
|
if (metadata->message_metadata) {
|
|
SDL_free(metadata->message_metadata);
|
|
}
|
|
SDL_memset(metadata, 0, sizeof(*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->paddle_format == GIP_PADDLES_XBE2_RAW ||
|
|
(attachment->firmware_major_version != 4 && attachment->firmware_minor_version < 17))
|
|
{
|
|
/*
|
|
* 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", true);
|
|
}
|
|
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 = {{0}};
|
|
int i;
|
|
|
|
if (num_bytes < 1) {
|
|
return false;
|
|
}
|
|
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 (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;
|
|
}
|
|
}
|
|
|
|
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] & 0x01) != 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)
|
|
{
|
|
if (attachment->firmware_major_version == 5 && attachment->firmware_minor_version < 17) {
|
|
attachment->paddle_format = GIP_PADDLES_XBE2_RAW;
|
|
} else {
|
|
attachment->paddle_format = GIP_PADDLES_XBE2;
|
|
}
|
|
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 || num_bytes <= attachment->paddle_offset) {
|
|
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short raw report");
|
|
return false;
|
|
}
|
|
|
|
if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS) && attachment->paddle_format == GIP_PADDLES_XBE2_RAW) {
|
|
SDL_SendJoystickButton(timestamp,
|
|
joystick,
|
|
attachment->paddle_idx,
|
|
(bytes[attachment->paddle_offset] & 0x01) != 0);
|
|
SDL_SendJoystickButton(timestamp,
|
|
joystick,
|
|
attachment->paddle_idx + 1,
|
|
(bytes[attachment->paddle_offset] & 0x02) != 0);
|
|
SDL_SendJoystickButton(timestamp,
|
|
joystick,
|
|
attachment->paddle_idx + 2,
|
|
(bytes[attachment->paddle_offset] & 0x04) != 0);
|
|
SDL_SendJoystickButton(timestamp,
|
|
joystick,
|
|
attachment->paddle_idx + 3,
|
|
(bytes[attachment->paddle_offset] & 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) &&
|
|
num_bytes > attachment->paddle_offset &&
|
|
attachment->last_input[attachment->paddle_offset] != bytes[attachment->paddle_offset])
|
|
{
|
|
if (attachment->paddle_format == GIP_PADDLES_XBE1) {
|
|
if (bytes[attachment->paddle_offset] & 0x10) {
|
|
SDL_SendJoystickButton(timestamp,
|
|
joystick,
|
|
attachment->paddle_idx,
|
|
(bytes[attachment->paddle_offset] & 0x02) != 0);
|
|
SDL_SendJoystickButton(timestamp,
|
|
joystick,
|
|
attachment->paddle_idx + 1,
|
|
(bytes[attachment->paddle_offset] & 0x08) != 0);
|
|
SDL_SendJoystickButton(timestamp,
|
|
joystick,
|
|
attachment->paddle_idx + 2,
|
|
(bytes[attachment->paddle_offset] & 0x01) != 0);
|
|
SDL_SendJoystickButton(timestamp,
|
|
joystick,
|
|
attachment->paddle_idx + 3,
|
|
(bytes[attachment->paddle_offset] & 0x04) != 0);
|
|
}
|
|
} else if (attachment->paddle_format == GIP_PADDLES_XBE2) {
|
|
SDL_SendJoystickButton(timestamp,
|
|
joystick,
|
|
attachment->paddle_idx,
|
|
(bytes[attachment->paddle_offset] & 0x01) != 0);
|
|
SDL_SendJoystickButton(timestamp,
|
|
joystick,
|
|
attachment->paddle_idx + 1,
|
|
(bytes[attachment->paddle_offset] & 0x02) != 0);
|
|
SDL_SendJoystickButton(timestamp,
|
|
joystick,
|
|
attachment->paddle_idx + 2,
|
|
(bytes[attachment->paddle_offset] & 0x04) != 0);
|
|
SDL_SendJoystickButton(timestamp,
|
|
joystick,
|
|
attachment->paddle_idx + 3,
|
|
(bytes[attachment->paddle_offset] & 0x08) != 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", true);
|
|
}
|
|
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;
|
|
|
|
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;
|
|
if (device->vendor_id == USB_VENDOR_MICROSOFT) {
|
|
if (device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1) {
|
|
attachment->paddle_offset = 28;
|
|
attachment->paddle_format = GIP_PADDLES_XBE1;
|
|
} else if (device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) {
|
|
attachment->paddle_offset = 14;
|
|
attachment->paddle_format = GIP_PADDLES_XBE2;
|
|
if (attachment->firmware_major_version == 5 && attachment->firmware_minor_version < 17) {
|
|
attachment->paddle_format = GIP_PADDLES_XBE2_RAW;
|
|
}
|
|
}
|
|
}
|
|
if (attachment->paddle_offset > 0) {
|
|
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, true);
|
|
}
|
|
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
|