Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_xboxone.c
9913 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#ifdef SDL_JOYSTICK_HIDAPI2324#include "../../SDL_hints_c.h"25#include "../SDL_sysjoystick.h"26#include "SDL_hidapijoystick_c.h"27#include "SDL_hidapi_rumble.h"2829#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE3031// Define this if you want verbose logging of the init sequence32// #define DEBUG_JOYSTICK3334// Define this if you want to log all packets from the controller35// #define DEBUG_XBOX_PROTOCOL3637#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK)38#define XBOX_ONE_DRIVER_ACTIVE 139#else40#define XBOX_ONE_DRIVER_ACTIVE 041#endif4243#define CONTROLLER_IDENTIFY_TIMEOUT_MS 10044#define CONTROLLER_PREPARE_INPUT_TIMEOUT_MS 504546// Deadzone thresholds47#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 784948#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 868949#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD -25058 // Uint8 30 scaled to Sint16 full range5051enum52{53SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON = 1154};5556// Power on57static const Uint8 xbox_init_power_on[] = {580x05, 0x20, 0x00, 0x01, 0x0059};60// Enable LED61static const Uint8 xbox_init_enable_led[] = {620x0A, 0x20, 0x00, 0x03, 0x00, 0x01, 0x1463};64// This controller passed security check65static const Uint8 xbox_init_security_passed[] = {660x06, 0x20, 0x00, 0x02, 0x01, 0x0067};68// Some PowerA controllers need to actually start the rumble motors69static const Uint8 xbox_init_powera_rumble[] = {700x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00,710x1D, 0x1D, 0xFF, 0x00, 0x0072};73// Setup rumble (not needed for Microsoft controllers, but it doesn't hurt)74static const Uint8 xbox_init_rumble[] = {750x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00,760x00, 0x00, 0xFF, 0x00, 0xEB77};7879/*80* This specifies the selection of init packets that a gamepad81* will be sent on init *and* the order in which they will be82* sent. The correct sequence number will be added when the83* packet is going to be sent.84*/85typedef struct86{87Uint16 vendor_id;88Uint16 product_id;89const Uint8 *data;90int size;91} SDL_DriverXboxOne_InitPacket;9293static const SDL_DriverXboxOne_InitPacket xboxone_init_packets[] = {94{ 0x0000, 0x0000, xbox_init_power_on, sizeof(xbox_init_power_on) },95{ 0x0000, 0x0000, xbox_init_enable_led, sizeof(xbox_init_enable_led) },96{ 0x0000, 0x0000, xbox_init_security_passed, sizeof(xbox_init_security_passed) },97{ 0x24c6, 0x541a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) },98{ 0x24c6, 0x542a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) },99{ 0x24c6, 0x543a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) },100{ 0x0000, 0x0000, xbox_init_rumble, sizeof(xbox_init_rumble) },101};102103typedef enum104{105XBOX_ONE_INIT_STATE_ANNOUNCED,106XBOX_ONE_INIT_STATE_IDENTIFYING,107XBOX_ONE_INIT_STATE_STARTUP,108XBOX_ONE_INIT_STATE_PREPARE_INPUT,109XBOX_ONE_INIT_STATE_COMPLETE,110} SDL_XboxOneInitState;111112typedef enum113{114XBOX_ONE_RUMBLE_STATE_IDLE,115XBOX_ONE_RUMBLE_STATE_QUEUED,116XBOX_ONE_RUMBLE_STATE_BUSY117} SDL_XboxOneRumbleState;118119typedef struct120{121SDL_HIDAPI_Device *device;122Uint16 vendor_id;123Uint16 product_id;124SDL_XboxOneInitState init_state;125Uint64 start_time;126Uint8 sequence;127Uint64 send_time;128bool has_guide_packet;129bool has_color_led;130bool has_paddles;131bool has_unmapped_state;132bool has_trigger_rumble;133bool has_share_button;134Uint8 last_paddle_state;135Uint8 low_frequency_rumble;136Uint8 high_frequency_rumble;137Uint8 left_trigger_rumble;138Uint8 right_trigger_rumble;139SDL_XboxOneRumbleState rumble_state;140Uint64 rumble_time;141bool rumble_pending;142Uint8 last_state[USB_PACKET_LENGTH];143Uint8 *chunk_buffer;144Uint32 chunk_length;145} SDL_DriverXboxOne_Context;146147static bool ControllerHasColorLED(Uint16 vendor_id, Uint16 product_id)148{149return vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2;150}151152static bool ControllerHasPaddles(Uint16 vendor_id, Uint16 product_id)153{154return SDL_IsJoystickXboxOneElite(vendor_id, product_id);155}156157static bool ControllerHasTriggerRumble(Uint16 vendor_id, Uint16 product_id)158{159// All the Microsoft Xbox One controllers have trigger rumble160if (vendor_id == USB_VENDOR_MICROSOFT) {161return true;162}163164/* It turns out other controllers a mixed bag as to whether they support165trigger rumble or not, and when they do it's often a buzz rather than166the vibration of the Microsoft trigger rumble, so for now just pretend167that it is not available.168*/169return false;170}171172static bool ControllerHasShareButton(Uint16 vendor_id, Uint16 product_id)173{174return SDL_IsJoystickXboxSeriesX(vendor_id, product_id);175}176177static int GetHomeLEDBrightness(const char *hint)178{179const int MAX_VALUE = 50;180int value = 20;181182if (hint && *hint) {183if (SDL_strchr(hint, '.') != NULL) {184value = (int)(MAX_VALUE * SDL_atof(hint));185} else if (!SDL_GetStringBoolean(hint, true)) {186value = 0;187}188}189return value;190}191192static void SetHomeLED(SDL_DriverXboxOne_Context *ctx, int value)193{194Uint8 led_packet[] = { 0x0A, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00 };195196if (value > 0) {197led_packet[5] = 0x01;198led_packet[6] = (Uint8)value;199}200SDL_HIDAPI_SendRumble(ctx->device, led_packet, sizeof(led_packet));201}202203static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)204{205SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)userdata;206207if (hint && *hint) {208SetHomeLED(ctx, GetHomeLEDBrightness(hint));209}210}211212static void SetInitState(SDL_DriverXboxOne_Context *ctx, SDL_XboxOneInitState state)213{214#ifdef DEBUG_JOYSTICK215SDL_Log("Setting init state %d", state);216#endif217ctx->init_state = state;218}219220static Uint8 GetNextPacketSequence(SDL_DriverXboxOne_Context *ctx)221{222++ctx->sequence;223if (!ctx->sequence) {224ctx->sequence = 1;225}226return ctx->sequence;227}228229static bool SendProtocolPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)230{231#ifdef DEBUG_XBOX_PROTOCOL232HIDAPI_DumpPacket("Xbox One sending packet: size = %d", data, size);233#endif234235ctx->send_time = SDL_GetTicks();236237if (!SDL_HIDAPI_LockRumble()) {238return false;239}240if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size) != size) {241return false;242}243return true;244}245246#if 0247static bool SendSerialRequest(SDL_DriverXboxOne_Context *ctx)248{249Uint8 packet[] = { 0x1E, 0x20, 0x00, 0x01, 0x04 };250251packet[2] = GetNextPacketSequence(ctx);252253/* Request the serial number254* Sending this should be done only after startup is complete.255* It will cancel the announce packet if sent before that, and will be256* ignored if sent during the startup sequence.257*/258if (!SendProtocolPacket(ctx, packet, sizeof(packet))) {259SDL_SetError("Couldn't send serial request packet");260return false;261}262return true;263}264#endif265266static bool ControllerSendsAnnouncement(Uint16 vendor_id, Uint16 product_id)267{268if (vendor_id == USB_VENDOR_PDP && product_id == 0x0246) {269// The PDP Rock Candy (PID 0x0246) doesn't send the announce packet on Linux for some reason270return false;271}272return true;273}274275static bool SendIdentificationRequest(SDL_DriverXboxOne_Context *ctx)276{277// Request identification, sent in response to announce packet278Uint8 packet[] = {2790x04, 0x20, 0x00, 0x00280};281282packet[2] = GetNextPacketSequence(ctx);283284if (!SendProtocolPacket(ctx, packet, sizeof(packet))) {285SDL_SetError("Couldn't send identification request packet");286return false;287}288return true;289}290291static bool SendControllerStartup(SDL_DriverXboxOne_Context *ctx)292{293Uint16 vendor_id = ctx->vendor_id;294Uint16 product_id = ctx->product_id;295Uint8 init_packet[USB_PACKET_LENGTH];296size_t i;297298for (i = 0; i < SDL_arraysize(xboxone_init_packets); ++i) {299const SDL_DriverXboxOne_InitPacket *packet = &xboxone_init_packets[i];300301if (packet->vendor_id && (vendor_id != packet->vendor_id)) {302continue;303}304305if (packet->product_id && (product_id != packet->product_id)) {306continue;307}308309SDL_memcpy(init_packet, packet->data, packet->size);310init_packet[2] = GetNextPacketSequence(ctx);311312if (init_packet[0] == 0x0A) {313// Get the initial brightness value314int brightness = GetHomeLEDBrightness(SDL_GetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED));315init_packet[5] = (brightness > 0) ? 0x01 : 0x00;316init_packet[6] = (Uint8)brightness;317}318319if (!SendProtocolPacket(ctx, init_packet, packet->size)) {320SDL_SetError("Couldn't send initialization packet");321return false;322}323324// Wait to process the rumble packet325if (packet->data == xbox_init_powera_rumble) {326SDL_Delay(10);327}328}329return true;330}331332static void HIDAPI_DriverXboxOne_RegisterHints(SDL_HintCallback callback, void *userdata)333{334SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);335SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, callback, userdata);336}337338static void HIDAPI_DriverXboxOne_UnregisterHints(SDL_HintCallback callback, void *userdata)339{340SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);341SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, callback, userdata);342}343344static bool HIDAPI_DriverXboxOne_IsEnabled(void)345{346return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE,347SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)));348}349350static bool HIDAPI_DriverXboxOne_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)351{352#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI)353if (!SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) {354// On macOS we get a shortened version of the real report and355// you can't write output reports for wired controllers, so356// we'll just use the GCController support instead.357return false;358}359#endif360return (type == SDL_GAMEPAD_TYPE_XBOXONE);361}362363static bool HIDAPI_DriverXboxOne_InitDevice(SDL_HIDAPI_Device *device)364{365SDL_DriverXboxOne_Context *ctx;366367ctx = (SDL_DriverXboxOne_Context *)SDL_calloc(1, sizeof(*ctx));368if (!ctx) {369return false;370}371ctx->device = device;372373device->context = ctx;374375ctx->vendor_id = device->vendor_id;376ctx->product_id = device->product_id;377ctx->start_time = SDL_GetTicks();378ctx->sequence = 0;379ctx->has_color_led = ControllerHasColorLED(ctx->vendor_id, ctx->product_id);380ctx->has_paddles = ControllerHasPaddles(ctx->vendor_id, ctx->product_id);381ctx->has_trigger_rumble = ControllerHasTriggerRumble(ctx->vendor_id, ctx->product_id);382ctx->has_share_button = ControllerHasShareButton(ctx->vendor_id, ctx->product_id);383384// Assume that the controller is correctly initialized when we start385if (!ControllerSendsAnnouncement(device->vendor_id, device->product_id)) {386// Jump into the startup sequence for this controller387ctx->init_state = XBOX_ONE_INIT_STATE_STARTUP;388} else {389ctx->init_state = XBOX_ONE_INIT_STATE_COMPLETE;390}391392#ifdef DEBUG_JOYSTICK393SDL_Log("Controller version: %d (0x%.4x)", device->version, device->version);394#endif395396device->type = SDL_GAMEPAD_TYPE_XBOXONE;397398return HIDAPI_JoystickConnected(device, NULL);399}400401static int HIDAPI_DriverXboxOne_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)402{403return -1;404}405406static void HIDAPI_DriverXboxOne_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)407{408}409410static bool HIDAPI_DriverXboxOne_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)411{412SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;413414SDL_AssertJoysticksLocked();415416ctx->low_frequency_rumble = 0;417ctx->high_frequency_rumble = 0;418ctx->left_trigger_rumble = 0;419ctx->right_trigger_rumble = 0;420ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_IDLE;421ctx->rumble_time = 0;422ctx->rumble_pending = false;423SDL_zeroa(ctx->last_state);424425// Initialize the joystick capabilities426joystick->nbuttons = 11;427if (ctx->has_share_button) {428joystick->nbuttons += 1;429}430if (ctx->has_paddles) {431joystick->nbuttons += 4;432}433joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;434joystick->nhats = 1;435436SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED,437SDL_HomeLEDHintChanged, ctx);438return true;439}440441static void HIDAPI_DriverXboxOne_RumbleSent(void *userdata)442{443SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)userdata;444ctx->rumble_time = SDL_GetTicks();445}446447static bool HIDAPI_DriverXboxOne_UpdateRumble(SDL_DriverXboxOne_Context *ctx)448{449if (ctx->rumble_state == XBOX_ONE_RUMBLE_STATE_QUEUED) {450if (ctx->rumble_time) {451ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_BUSY;452}453}454455if (ctx->rumble_state == XBOX_ONE_RUMBLE_STATE_BUSY) {456const int RUMBLE_BUSY_TIME_MS = ctx->device->is_bluetooth ? 50 : 10;457if (SDL_GetTicks() >= (ctx->rumble_time + RUMBLE_BUSY_TIME_MS)) {458ctx->rumble_time = 0;459ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_IDLE;460}461}462463if (!ctx->rumble_pending) {464return true;465}466467if (ctx->rumble_state != XBOX_ONE_RUMBLE_STATE_IDLE) {468return true;469}470471// We're no longer pending, even if we fail to send the rumble below472ctx->rumble_pending = false;473474if (!SDL_HIDAPI_LockRumble()) {475return false;476}477478if (ctx->device->is_bluetooth) {479Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB };480481rumble_packet[2] = ctx->left_trigger_rumble;482rumble_packet[3] = ctx->right_trigger_rumble;483rumble_packet[4] = ctx->low_frequency_rumble;484rumble_packet[5] = ctx->high_frequency_rumble;485486if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(ctx->device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) {487return SDL_SetError("Couldn't send rumble packet");488}489} else {490Uint8 rumble_packet[] = { 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB };491492rumble_packet[6] = ctx->left_trigger_rumble;493rumble_packet[7] = ctx->right_trigger_rumble;494rumble_packet[8] = ctx->low_frequency_rumble;495rumble_packet[9] = ctx->high_frequency_rumble;496497if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(ctx->device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) {498return SDL_SetError("Couldn't send rumble packet");499}500}501502ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_QUEUED;503504return true;505}506507static bool HIDAPI_DriverXboxOne_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)508{509SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;510511// Magnitude is 1..100 so scale the 16-bit input here512ctx->low_frequency_rumble = (Uint8)(low_frequency_rumble / 655);513ctx->high_frequency_rumble = (Uint8)(high_frequency_rumble / 655);514ctx->rumble_pending = true;515516return HIDAPI_DriverXboxOne_UpdateRumble(ctx);517}518519static bool HIDAPI_DriverXboxOne_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)520{521SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;522523if (!ctx->has_trigger_rumble) {524return SDL_Unsupported();525}526527// Magnitude is 1..100 so scale the 16-bit input here528ctx->left_trigger_rumble = (Uint8)(left_rumble / 655);529ctx->right_trigger_rumble = (Uint8)(right_rumble / 655);530ctx->rumble_pending = true;531532return HIDAPI_DriverXboxOne_UpdateRumble(ctx);533}534535static Uint32 HIDAPI_DriverXboxOne_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)536{537SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;538Uint32 result = 0;539540result |= SDL_JOYSTICK_CAP_RUMBLE;541if (ctx->has_trigger_rumble) {542result |= SDL_JOYSTICK_CAP_TRIGGER_RUMBLE;543}544545if (ctx->has_color_led) {546result |= SDL_JOYSTICK_CAP_RGB_LED;547}548549return result;550}551552static bool HIDAPI_DriverXboxOne_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)553{554SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;555556if (ctx->has_color_led) {557Uint8 led_packet[] = { 0x0E, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 };558559led_packet[5] = 0x00; // Whiteness? Sets white intensity when RGB is 0, seems additive560led_packet[6] = red;561led_packet[7] = green;562led_packet[8] = blue;563564if (SDL_HIDAPI_SendRumble(device, led_packet, sizeof(led_packet)) != sizeof(led_packet)) {565return SDL_SetError("Couldn't send LED packet");566}567return true;568} else {569return SDL_Unsupported();570}571}572573static bool HIDAPI_DriverXboxOne_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)574{575return SDL_Unsupported();576}577578static bool HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)579{580return SDL_Unsupported();581}582583/*584* The Xbox One Elite controller with 5.13+ firmware sends the unmapped state in a separate packet.585* We can use this to send the paddle state when they aren't mapped586*/587static void HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)588{589Uint8 profile;590int paddle_index;591int button1_bit;592int button2_bit;593int button3_bit;594int button4_bit;595bool paddles_mapped;596Uint64 timestamp = SDL_GetTicksNS();597598if (size == 17) {599// XBox One Elite Series 2600paddle_index = 14;601button1_bit = 0x01;602button2_bit = 0x02;603button3_bit = 0x04;604button4_bit = 0x08;605profile = data[15];606607if (profile == 0) {608paddles_mapped = false;609} else if (SDL_memcmp(&data[0], &ctx->last_state[0], 14) == 0) {610// We're using a profile, but paddles aren't mapped611paddles_mapped = false;612} else {613// Something is mapped, we can't use the paddles614paddles_mapped = true;615}616617} else {618// Unknown format619return;620}621#ifdef DEBUG_XBOX_PROTOCOL622SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s",623(data[paddle_index] & button1_bit) ? 1 : 0,624(data[paddle_index] & button2_bit) ? 1 : 0,625(data[paddle_index] & button3_bit) ? 1 : 0,626(data[paddle_index] & button4_bit) ? 1 : 0,627paddles_mapped ? "TRUE" : "FALSE");628#endif629630if (paddles_mapped) {631// Respect that the paddles are being used for other controls and don't pass them on to the app632data[paddle_index] = 0;633}634635if (ctx->last_paddle_state != data[paddle_index]) {636Uint8 nButton = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); // Next available button637SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button1_bit) != 0));638SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button2_bit) != 0));639SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button3_bit) != 0));640SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button4_bit) != 0));641ctx->last_paddle_state = data[paddle_index];642}643ctx->has_unmapped_state = true;644}645646static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)647{648Sint16 axis;649Uint64 timestamp = SDL_GetTicksNS();650651// Enable paddles on the Xbox Elite controller when connected over USB652if (ctx->has_paddles && !ctx->has_unmapped_state && size == 46) {653Uint8 packet[] = { 0x4d, 0x00, 0x00, 0x02, 0x07, 0x00 };654655#ifdef DEBUG_JOYSTICK656SDL_Log("Enabling paddles on XBox Elite 2");657#endif658SDL_HIDAPI_SendRumble(ctx->device, packet, sizeof(packet));659}660661if (ctx->last_state[0] != data[0]) {662SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[0] & 0x04) != 0));663SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[0] & 0x08) != 0));664SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x10) != 0));665SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x20) != 0));666SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x40) != 0));667SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x80) != 0));668}669670if (ctx->last_state[1] != data[1]) {671Uint8 hat = 0;672673if (data[1] & 0x01) {674hat |= SDL_HAT_UP;675}676if (data[1] & 0x02) {677hat |= SDL_HAT_DOWN;678}679if (data[1] & 0x04) {680hat |= SDL_HAT_LEFT;681}682if (data[1] & 0x08) {683hat |= SDL_HAT_RIGHT;684}685SDL_SendJoystickHat(timestamp, joystick, 0, hat);686687if (ctx->vendor_id == USB_VENDOR_RAZER && ctx->product_id == USB_PRODUCT_RAZER_ATROX) {688// The Razer Atrox has the right and left shoulder bits reversed689SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x20) != 0));690SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x10) != 0));691} else {692SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0));693SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0));694}695SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x40) != 0));696SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x80) != 0));697}698699if (ctx->has_share_button) {700/* Xbox Series X firmware version 5.0, report is 32 bytes, share button is in byte 14701* Xbox Series X firmware version 5.1, report is 40 bytes, share button is in byte 14702* Xbox Series X firmware version 5.5, report is 44 bytes, share button is in byte 18703* Victrix Gambit Tournament Controller, report is 46 bytes, share button is in byte 28704* ThrustMaster eSwap PRO Controller Xbox, report is 60 bytes, share button is in byte 42705*/706if (size < 44) {707if (ctx->last_state[14] != data[14]) {708SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[14] & 0x01) != 0));709}710} else if (size == 44) {711if (ctx->last_state[18] != data[18]) {712SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[18] & 0x01) != 0));713}714} else if (size == 46) {715if (ctx->last_state[28] != data[28]) {716SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[28] & 0x01) != 0));717}718} else if (size == 60) {719if (ctx->last_state[42] != data[42]) {720SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[42] & 0x01) != 0));721}722}723}724725/* Xbox One S report is 14 bytes726Xbox One Elite Series 1 report is 29 bytes, paddles in data[28], mode in data[28] & 0x10, both modes have mapped paddles by default727Paddle bits:728P3: 0x01 (A) P1: 0x02 (B)729P4: 0x04 (X) P2: 0x08 (Y)730Xbox One Elite Series 2 4.x firmware report is 34 bytes, paddles in data[14], mode in data[15], mode 0 has no mapped paddles by default731Paddle bits:732P3: 0x04 (A) P1: 0x01 (B)733P4: 0x08 (X) P2: 0x02 (Y)734Xbox One Elite Series 2 5.x firmware report is 46 bytes, paddles in data[18], mode in data[19], mode 0 has no mapped paddles by default735Paddle bits:736P3: 0x04 (A) P1: 0x01 (B)737P4: 0x08 (X) P2: 0x02 (Y)738Xbox One Elite Series 2 5.17+ firmware report is 47 bytes, paddles in data[14], mode in data[20], mode 0 has no mapped paddles by default739Paddle bits:740P3: 0x04 (A) P1: 0x01 (B)741P4: 0x08 (X) P2: 0x02 (Y)742*/743if (ctx->has_paddles && !ctx->has_unmapped_state && (size == 29 || size == 34 || size == 46 || size == 47)) {744int paddle_index;745int button1_bit;746int button2_bit;747int button3_bit;748int button4_bit;749bool paddles_mapped;750751if (size == 29) {752// XBox One Elite Series 1753paddle_index = 28;754button1_bit = 0x02;755button2_bit = 0x08;756button3_bit = 0x01;757button4_bit = 0x04;758759// The mapped controller state is at offset 0, the raw state is at offset 14, compare them to see if the paddles are mapped760paddles_mapped = (SDL_memcmp(&data[0], &data[14], 2) != 0);761762} else if (size == 34) {763// XBox One Elite Series 2764paddle_index = 14;765button1_bit = 0x01;766button2_bit = 0x02;767button3_bit = 0x04;768button4_bit = 0x08;769paddles_mapped = (data[15] != 0);770771} else if (size == 46) {772// XBox One Elite Series 2773paddle_index = 18;774button1_bit = 0x01;775button2_bit = 0x02;776button3_bit = 0x04;777button4_bit = 0x08;778paddles_mapped = (data[19] != 0);779} else /* if (size == 47) */ {780// XBox One Elite Series 2781paddle_index = 14;782button1_bit = 0x01;783button2_bit = 0x02;784button3_bit = 0x04;785button4_bit = 0x08;786paddles_mapped = (data[20] != 0);787}788#ifdef DEBUG_XBOX_PROTOCOL789SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s",790(data[paddle_index] & button1_bit) ? 1 : 0,791(data[paddle_index] & button2_bit) ? 1 : 0,792(data[paddle_index] & button3_bit) ? 1 : 0,793(data[paddle_index] & button4_bit) ? 1 : 0,794paddles_mapped ? "TRUE" : "FALSE");795#endif796797if (paddles_mapped) {798// Respect that the paddles are being used for other controls and don't pass them on to the app799data[paddle_index] = 0;800}801802if (ctx->last_paddle_state != data[paddle_index]) {803Uint8 nButton = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); // Next available button804SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button1_bit) != 0));805SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button2_bit) != 0));806SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button3_bit) != 0));807SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button4_bit) != 0));808ctx->last_paddle_state = data[paddle_index];809}810}811812axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[2])) * 64) - 32768;813if (axis == 32704) {814axis = 32767;815}816if (axis == -32768 && size == 26 && (data[18] & 0x80)) {817axis = 32767;818}819SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);820821axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[4])) * 64) - 32768;822if (axis == -32768 && size == 26 && (data[18] & 0x40)) {823axis = 32767;824}825if (axis == 32704) {826axis = 32767;827}828SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);829830axis = SDL_Swap16LE(*(Sint16 *)(&data[6]));831SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);832axis = SDL_Swap16LE(*(Sint16 *)(&data[8]));833SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis);834axis = SDL_Swap16LE(*(Sint16 *)(&data[10]));835SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);836axis = SDL_Swap16LE(*(Sint16 *)(&data[12]));837SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis);838839SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));840841// We don't have the unmapped state for this packet842ctx->has_unmapped_state = false;843}844845static void HIDAPI_DriverXboxOne_HandleStatusPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)846{847if (ctx->init_state < XBOX_ONE_INIT_STATE_COMPLETE) {848SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE);849}850}851852static void HIDAPI_DriverXboxOne_HandleModePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)853{854Uint64 timestamp = SDL_GetTicksNS();855856SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[0] & 0x01) != 0));857}858859/*860* Xbox One S with firmware 3.1.1221 uses a 16 byte packet and the GUIDE button in a separate packet861*/862static void HIDAPI_DriverXboxOneBluetooth_HandleButtons16(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)863{864if (ctx->last_state[14] != data[14]) {865SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0));866SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0));867SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x04) != 0));868SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x08) != 0));869SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x10) != 0));870SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x20) != 0));871SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[14] & 0x40) != 0));872SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[14] & 0x80) != 0));873}874875if (ctx->last_state[15] != data[15]) {876SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x01) != 0));877SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x02) != 0));878}879}880881/*882* Xbox One S with firmware 4.8.1923 uses a 17 byte packet with BACK button in byte 16 and the GUIDE button in a separate packet (on Windows), or in byte 15 (on Linux)883* Xbox One S with firmware 5.x uses a 17 byte packet with BACK and GUIDE buttons in byte 15884* Xbox One Elite Series 2 with firmware 4.7.1872 uses a 55 byte packet with BACK button in byte 16, paddles starting at byte 33, and the GUIDE button in a separate packet885* Xbox One Elite Series 2 with firmware 4.8.1908 uses a 33 byte packet with BACK button in byte 16, paddles starting at byte 17, and the GUIDE button in a separate packet886* Xbox One Elite Series 2 with firmware 5.11.3112 uses a 19 byte packet with BACK and GUIDE buttons in byte 15887* Xbox Series X with firmware 5.5.2641 uses a 17 byte packet with BACK and GUIDE buttons in byte 15, and SHARE button in byte 17888*/889static void HIDAPI_DriverXboxOneBluetooth_HandleButtons(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)890{891if (ctx->last_state[14] != data[14]) {892SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0));893SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0));894SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x08) != 0));895SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x10) != 0));896SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x40) != 0));897SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x80) != 0));898}899900if (ctx->last_state[15] != data[15]) {901if (!ctx->has_guide_packet) {902SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[15] & 0x10) != 0));903}904SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[15] & 0x08) != 0));905SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x20) != 0));906SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x40) != 0));907}908909if (ctx->has_share_button) {910SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[15] & 0x04) != 0));911SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[16] & 0x01) != 0));912} else {913SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[15] & 0x04) || ((data[16] & 0x01)) != 0));914}915916/*917Paddle bits:918P3: 0x04 (A) P1: 0x01 (B)919P4: 0x08 (X) P2: 0x02 (Y)920*/921if (ctx->has_paddles && (size == 20 || size == 39 || size == 55)) {922int paddle_index;923int button1_bit;924int button2_bit;925int button3_bit;926int button4_bit;927bool paddles_mapped;928929if (size == 55) {930// Initial firmware for the Xbox Elite Series 2 controller931paddle_index = 33;932button1_bit = 0x01;933button2_bit = 0x02;934button3_bit = 0x04;935button4_bit = 0x08;936paddles_mapped = (data[35] != 0);937} else if (size == 39) {938// Updated firmware for the Xbox Elite Series 2 controller939paddle_index = 17;940button1_bit = 0x01;941button2_bit = 0x02;942button3_bit = 0x04;943button4_bit = 0x08;944paddles_mapped = (data[19] != 0);945} else /* if (size == 20) */ {946// Updated firmware for the Xbox Elite Series 2 controller (5.13+)947paddle_index = 19;948button1_bit = 0x01;949button2_bit = 0x02;950button3_bit = 0x04;951button4_bit = 0x08;952paddles_mapped = (data[17] != 0);953}954955#ifdef DEBUG_XBOX_PROTOCOL956SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s",957(data[paddle_index] & button1_bit) ? 1 : 0,958(data[paddle_index] & button2_bit) ? 1 : 0,959(data[paddle_index] & button3_bit) ? 1 : 0,960(data[paddle_index] & button4_bit) ? 1 : 0,961paddles_mapped ? "TRUE" : "FALSE");962#endif963964if (paddles_mapped) {965// Respect that the paddles are being used for other controls and don't pass them on to the app966data[paddle_index] = 0;967}968969if (ctx->last_paddle_state != data[paddle_index]) {970Uint8 nButton = SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON; // Next available button971SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button1_bit) != 0));972SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button2_bit) != 0));973SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button3_bit) != 0));974SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button4_bit) != 0));975ctx->last_paddle_state = data[paddle_index];976}977}978}979980static void HIDAPI_DriverXboxOneBluetooth_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)981{982Sint16 axis;983Uint64 timestamp = SDL_GetTicksNS();984985if (size == 16) {986// Original Xbox One S, with separate report for guide button987HIDAPI_DriverXboxOneBluetooth_HandleButtons16(timestamp, joystick, ctx, data, size);988} else if (size > 16) {989HIDAPI_DriverXboxOneBluetooth_HandleButtons(timestamp, joystick, ctx, data, size);990} else {991#ifdef DEBUG_XBOX_PROTOCOL992SDL_Log("Unknown Bluetooth state packet format");993#endif994return;995}996997if (ctx->last_state[13] != data[13]) {998Uint8 hat;9991000switch (data[13]) {1001case 1:1002hat = SDL_HAT_UP;1003break;1004case 2:1005hat = SDL_HAT_RIGHTUP;1006break;1007case 3:1008hat = SDL_HAT_RIGHT;1009break;1010case 4:1011hat = SDL_HAT_RIGHTDOWN;1012break;1013case 5:1014hat = SDL_HAT_DOWN;1015break;1016case 6:1017hat = SDL_HAT_LEFTDOWN;1018break;1019case 7:1020hat = SDL_HAT_LEFT;1021break;1022case 8:1023hat = SDL_HAT_LEFTUP;1024break;1025default:1026hat = SDL_HAT_CENTERED;1027break;1028}1029SDL_SendJoystickHat(timestamp, joystick, 0, hat);1030}10311032axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[9])) * 64) - 32768;1033if (axis == 32704) {1034axis = 32767;1035}1036SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);10371038axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[11])) * 64) - 32768;1039if (axis == 32704) {1040axis = 32767;1041}1042SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);10431044axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[1])) - 0x8000;1045SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);1046axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[3])) - 0x8000;1047SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);1048axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[5])) - 0x8000;1049SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);1050axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[7])) - 0x8000;1051SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);10521053SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));1054}10551056static void HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)1057{1058Uint64 timestamp = SDL_GetTicksNS();10591060ctx->has_guide_packet = true;1061SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x01) != 0));1062}10631064static void HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)1065{1066Uint8 flags = data[1];1067bool on_usb = (((flags & 0x0C) >> 2) == 0);1068SDL_PowerState state;1069int percent = 0;10701071// Mapped percentage value from:1072// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getbatterystate1073switch (flags & 0x03) {1074case 0:1075percent = 10;1076break;1077case 1:1078percent = 40;1079break;1080case 2:1081percent = 70;1082break;1083case 3:1084percent = 100;1085break;1086}1087if (on_usb) {1088state = SDL_POWERSTATE_CHARGING;1089} else {1090state = SDL_POWERSTATE_ON_BATTERY;1091}1092SDL_SendJoystickPowerInfo(joystick, state, percent);1093}10941095static void HIDAPI_DriverXboxOne_HandleSerialIDPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)1096{1097char serial[29];1098int i;10991100for (i = 0; i < 14; ++i) {1101SDL_uitoa(data[2 + i], &serial[i * 2], 16);1102}1103serial[i * 2] = '\0';11041105#ifdef DEBUG_JOYSTICK1106SDL_Log("Setting serial number to %s", serial);1107#endif1108HIDAPI_SetDeviceSerial(ctx->device, serial);1109}11101111static bool HIDAPI_DriverXboxOne_UpdateInitState(SDL_DriverXboxOne_Context *ctx)1112{1113SDL_XboxOneInitState prev_state;1114do {1115prev_state = ctx->init_state;11161117switch (ctx->init_state) {1118case XBOX_ONE_INIT_STATE_ANNOUNCED:1119if (XBOX_ONE_DRIVER_ACTIVE) {1120// The driver is taking care of identification1121SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE);1122} else {1123SendIdentificationRequest(ctx);1124SetInitState(ctx, XBOX_ONE_INIT_STATE_IDENTIFYING);1125}1126break;1127case XBOX_ONE_INIT_STATE_IDENTIFYING:1128if (SDL_GetTicks() >= (ctx->send_time + CONTROLLER_IDENTIFY_TIMEOUT_MS)) {1129// We haven't heard anything, let's move on1130#ifdef DEBUG_JOYSTICK1131SDL_Log("Identification request timed out after %llu ms", (SDL_GetTicks() - ctx->send_time));1132#endif1133SetInitState(ctx, XBOX_ONE_INIT_STATE_STARTUP);1134}1135break;1136case XBOX_ONE_INIT_STATE_STARTUP:1137if (XBOX_ONE_DRIVER_ACTIVE) {1138// The driver is taking care of startup1139SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE);1140} else {1141SendControllerStartup(ctx);1142SetInitState(ctx, XBOX_ONE_INIT_STATE_PREPARE_INPUT);1143}1144break;1145case XBOX_ONE_INIT_STATE_PREPARE_INPUT:1146if (SDL_GetTicks() >= (ctx->send_time + CONTROLLER_PREPARE_INPUT_TIMEOUT_MS)) {1147#ifdef DEBUG_JOYSTICK1148SDL_Log("Prepare input complete after %llu ms", (SDL_GetTicks() - ctx->send_time));1149#endif1150SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE);1151}1152break;1153case XBOX_ONE_INIT_STATE_COMPLETE:1154break;1155}11561157} while (ctx->init_state != prev_state);11581159return true;1160}11611162/* GIP protocol handling adapted under the Zlib license with permission from @medusalix:1163* https://github.com/medusalix/xone/blob/master/bus/protocol.h1164* https://github.com/medusalix/xone/blob/master/bus/protocol.c1165*/1166#define GIP_HEADER_MIN_LENGTH 311671168// Internal commands1169#define GIP_CMD_ACKNOWLEDGE 0x011170#define GIP_CMD_ANNOUNCE 0x021171#define GIP_CMD_STATUS 0x031172#define GIP_CMD_IDENTIFY 0x041173#define GIP_CMD_POWER 0x051174#define GIP_CMD_AUTHENTICATE 0x061175#define GIP_CMD_VIRTUAL_KEY 0x071176#define GIP_CMD_AUDIO_CONTROL 0x081177#define GIP_CMD_LED 0x0A1178#define GIP_CMD_HID_REPORT 0x0B1179#define GIP_CMD_FIRMWARE 0x0C1180#define GIP_CMD_SERIAL_NUMBER 0x1E1181#define GIP_CMD_AUDIO_SAMPLES 0x6011821183// External commands1184#define GIP_CMD_RUMBLE 0x091185#define GIP_CMD_UNMAPPED_STATE 0x0C1186#define GIP_CMD_INPUT 0x2011871188// Header option flags1189#define GIP_OPT_ACKNOWLEDGE 0x101190#define GIP_OPT_INTERNAL 0x201191#define GIP_OPT_CHUNK_START 0x401192#define GIP_OPT_CHUNK 0x8011931194#pragma pack(push, 1)11951196struct gip_header {1197Uint8 command;1198Uint8 options;1199Uint8 sequence;1200Uint32 packet_length;1201Uint32 chunk_offset;1202};12031204struct gip_pkt_acknowledge {1205Uint8 unknown;1206Uint8 command;1207Uint8 options;1208Uint16 length;1209Uint8 padding[2];1210Uint16 remaining;1211};12121213#pragma pack(pop)12141215static int EncodeVariableInt(Uint8 *buf, Uint32 val)1216{1217int i;12181219for (i = 0; i < sizeof(val); i++) {1220buf[i] = (Uint8)val;1221if (val > 0x7F) {1222buf[i] |= 0x80;1223}12241225val >>= 7;1226if (!val) {1227break;1228}1229}1230return i + 1;1231}12321233static int DecodeVariableInt(const Uint8 *data, int len, void *out)1234{1235int i;1236Uint32 val = 0;12371238for (i = 0; i < sizeof(val) && i < len; i++) {1239val |= (data[i] & 0x7F) << (i * 7);12401241if (!(data[i] & 0x80)) {1242break;1243}1244}1245SDL_memcpy(out, &val, sizeof(val));1246return i + 1;1247}12481249static int HIDAPI_GIP_GetActualHeaderLength(struct gip_header *hdr)1250{1251Uint32 pkt_len = hdr->packet_length;1252Uint32 chunk_offset = hdr->chunk_offset;1253int len = GIP_HEADER_MIN_LENGTH;12541255do {1256len++;1257pkt_len >>= 7;1258} while (pkt_len);12591260if (hdr->options & GIP_OPT_CHUNK) {1261while (chunk_offset) {1262len++;1263chunk_offset >>= 7;1264}1265}12661267return len;1268}12691270static int HIDAPI_GIP_GetHeaderLength(struct gip_header *hdr)1271{1272int len = HIDAPI_GIP_GetActualHeaderLength(hdr);12731274// Header length must be even1275return len + (len % 2);1276}12771278static void HIDAPI_GIP_EncodeHeader(struct gip_header *hdr, Uint8 *buf)1279{1280int hdr_len = 0;12811282buf[hdr_len++] = hdr->command;1283buf[hdr_len++] = hdr->options;1284buf[hdr_len++] = hdr->sequence;12851286hdr_len += EncodeVariableInt(buf + hdr_len, hdr->packet_length);12871288// Header length must be even1289if (HIDAPI_GIP_GetActualHeaderLength(hdr) % 2) {1290buf[hdr_len - 1] |= 0x80;1291buf[hdr_len++] = 0;1292}12931294if (hdr->options & GIP_OPT_CHUNK) {1295EncodeVariableInt(buf + hdr_len, hdr->chunk_offset);1296}1297}12981299static int HIDAPI_GIP_DecodeHeader(struct gip_header *hdr, const Uint8 *data, int len)1300{1301int hdr_len = 0;13021303hdr->command = data[hdr_len++];1304hdr->options = data[hdr_len++];1305hdr->sequence = data[hdr_len++];1306hdr->packet_length = 0;1307hdr->chunk_offset = 0;13081309hdr_len += DecodeVariableInt(data + hdr_len, len - hdr_len, &hdr->packet_length);13101311if (hdr->options & GIP_OPT_CHUNK) {1312hdr_len += DecodeVariableInt(data + hdr_len, len - hdr_len, &hdr->chunk_offset);1313}1314return hdr_len;1315}13161317static bool HIDAPI_GIP_SendPacket(SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, const void *data)1318{1319Uint8 packet[USB_PACKET_LENGTH];1320int hdr_len, size;13211322hdr_len = HIDAPI_GIP_GetHeaderLength(hdr);1323size = (hdr_len + hdr->packet_length);1324if (size > sizeof(packet)) {1325SDL_SetError("Couldn't send GIP packet, size (%d) too large", size);1326return false;1327}13281329if (!hdr->sequence) {1330hdr->sequence = GetNextPacketSequence(ctx);1331}13321333HIDAPI_GIP_EncodeHeader(hdr, packet);1334if (data) {1335SDL_memcpy(&packet[hdr_len], data, hdr->packet_length);1336}13371338if (!SendProtocolPacket(ctx, packet, size)) {1339SDL_SetError("Couldn't send protocol packet");1340return false;1341}1342return true;1343}13441345static bool HIDAPI_GIP_AcknowledgePacket(SDL_DriverXboxOne_Context *ctx, struct gip_header *ack)1346{1347if (XBOX_ONE_DRIVER_ACTIVE) {1348// The driver is taking care of acks1349return true;1350} else {1351struct gip_header hdr;1352struct gip_pkt_acknowledge pkt;13531354SDL_zero(hdr);1355hdr.command = GIP_CMD_ACKNOWLEDGE;1356hdr.options = GIP_OPT_INTERNAL;1357hdr.sequence = ack->sequence;1358hdr.packet_length = sizeof(pkt);13591360SDL_zero(pkt);1361pkt.command = ack->command;1362pkt.options = GIP_OPT_INTERNAL;1363pkt.length = SDL_Swap16LE((Uint16)(ack->chunk_offset + ack->packet_length));13641365if ((ack->options & GIP_OPT_CHUNK) && ctx->chunk_buffer) {1366pkt.remaining = SDL_Swap16LE((Uint16)(ctx->chunk_length - pkt.length));1367}13681369return HIDAPI_GIP_SendPacket(ctx, &hdr, &pkt);1370}1371}13721373static bool HIDAPI_GIP_DispatchPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data, Uint32 size)1374{1375if ((hdr->options & 0x0F) != 0) {1376// This is a packet for a device plugged into the controller, skip it1377return true;1378}13791380if (hdr->options & GIP_OPT_INTERNAL) {1381switch (hdr->command) {1382case GIP_CMD_ACKNOWLEDGE:1383// Ignore this packet1384break;1385case GIP_CMD_ANNOUNCE:1386// Controller is connected and waiting for initialization1387/* The data bytes are:13880x02 0x20 NN 0x1c, where NN is the packet sequence1389then 6 bytes of wireless MAC address1390then 2 bytes padding1391then 16-bit VID1392then 16-bit PID1393then 16-bit firmware version quartet AA.BB.CC.DD1394e.g. 0x05 0x00 0x05 0x00 0x51 0x0a 0x00 0x001395is firmware version 5.5.2641.0, and product version 0x0505 = 12851396then 8 bytes of unknown data1397*/1398#ifdef DEBUG_JOYSTICK1399SDL_Log("Controller announce after %llu ms", (SDL_GetTicks() - ctx->start_time));1400#endif1401SetInitState(ctx, XBOX_ONE_INIT_STATE_ANNOUNCED);1402break;1403case GIP_CMD_STATUS:1404// Controller status update1405HIDAPI_DriverXboxOne_HandleStatusPacket(ctx, data, size);1406break;1407case GIP_CMD_IDENTIFY:1408#ifdef DEBUG_JOYSTICK1409SDL_Log("Identification request completed after %llu ms", (SDL_GetTicks() - ctx->send_time));1410#endif1411#ifdef DEBUG_XBOX_PROTOCOL1412HIDAPI_DumpPacket("Xbox One identification data: size = %d", data, size);1413#endif1414SetInitState(ctx, XBOX_ONE_INIT_STATE_STARTUP);1415break;1416case GIP_CMD_POWER:1417// Ignore this packet1418break;1419case GIP_CMD_AUTHENTICATE:1420// Ignore this packet1421break;1422case GIP_CMD_VIRTUAL_KEY:1423if (!joystick) {1424break;1425}1426HIDAPI_DriverXboxOne_HandleModePacket(joystick, ctx, data, size);1427break;1428case GIP_CMD_SERIAL_NUMBER:1429/* If the packet starts with this:14300x1E 0x30 0x00 0x10 0x04 0x001431then the next 14 bytes are the controller serial number1432e.g. 0x30 0x39 0x37 0x31 0x32 0x33 0x33 0x32 0x33 0x35 0x34 0x30 0x33 0x361433is serial number "3039373132333332333534303336"14341435The controller sends that in response to this request:14360x1E 0x20 0x00 0x01 0x041437*/1438HIDAPI_DriverXboxOne_HandleSerialIDPacket(ctx, data, size);1439break;1440default:1441#ifdef DEBUG_JOYSTICK1442SDL_Log("Unknown Xbox One packet: 0x%.2x", hdr->command);1443#endif1444break;1445}1446} else {1447switch (hdr->command) {1448case GIP_CMD_INPUT:1449if (ctx->init_state < XBOX_ONE_INIT_STATE_COMPLETE) {1450SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE);14511452// Ignore the first input, it may be spurious1453#ifdef DEBUG_JOYSTICK1454SDL_Log("Controller ignoring spurious input");1455#endif1456break;1457}1458if (!joystick) {1459break;1460}1461HIDAPI_DriverXboxOne_HandleStatePacket(joystick, ctx, data, size);1462break;1463case GIP_CMD_UNMAPPED_STATE:1464if (!joystick) {1465break;1466}1467HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(joystick, ctx, data, size);1468break;1469default:1470#ifdef DEBUG_JOYSTICK1471SDL_Log("Unknown Xbox One packet: 0x%.2x", hdr->command);1472#endif1473break;1474}1475}1476return true;1477}14781479static void HIDAPI_GIP_DestroyChunkBuffer(SDL_DriverXboxOne_Context *ctx)1480{1481if (ctx->chunk_buffer) {1482SDL_free(ctx->chunk_buffer);1483ctx->chunk_buffer = NULL;1484ctx->chunk_length = 0;1485}1486}14871488static bool HIDAPI_GIP_CreateChunkBuffer(SDL_DriverXboxOne_Context *ctx, Uint32 size)1489{1490HIDAPI_GIP_DestroyChunkBuffer(ctx);14911492ctx->chunk_buffer = (Uint8 *)SDL_malloc(size);1493if (ctx->chunk_buffer) {1494ctx->chunk_length = size;1495return true;1496} else {1497return false;1498}1499}15001501static bool HIDAPI_GIP_ProcessPacketChunked(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data)1502{1503bool result;15041505if (!ctx->chunk_buffer) {1506return false;1507}15081509if ((hdr->chunk_offset + hdr->packet_length) > ctx->chunk_length) {1510return false;1511}15121513if (hdr->packet_length) {1514SDL_memcpy(ctx->chunk_buffer + hdr->chunk_offset, data, hdr->packet_length);1515return true;1516}15171518result = HIDAPI_GIP_DispatchPacket(joystick, ctx, hdr, ctx->chunk_buffer, ctx->chunk_length);15191520HIDAPI_GIP_DestroyChunkBuffer(ctx);15211522return result;1523}15241525static bool HIDAPI_GIP_ProcessPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data)1526{1527if (hdr->options & GIP_OPT_CHUNK_START) {1528if (!HIDAPI_GIP_CreateChunkBuffer(ctx, hdr->chunk_offset)) {1529return false;1530}1531ctx->chunk_length = hdr->chunk_offset;15321533hdr->chunk_offset = 0;1534}15351536if (hdr->options & GIP_OPT_ACKNOWLEDGE) {1537if (!HIDAPI_GIP_AcknowledgePacket(ctx, hdr)) {1538return false;1539}1540}15411542if (hdr->options & GIP_OPT_CHUNK) {1543return HIDAPI_GIP_ProcessPacketChunked(joystick, ctx, hdr, data);1544} else {1545return HIDAPI_GIP_DispatchPacket(joystick, ctx, hdr, data, hdr->packet_length);1546}1547}15481549static bool HIDAPI_GIP_ProcessData(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)1550{1551struct gip_header hdr;1552int hdr_len;15531554while (size > GIP_HEADER_MIN_LENGTH) {1555hdr_len = HIDAPI_GIP_DecodeHeader(&hdr, data, size);1556if ((hdr_len + hdr.packet_length) > (Uint32)size) {1557// On macOS we get a shortened version of the real report1558hdr.packet_length = (Uint32)(size - hdr_len);1559}15601561if (!HIDAPI_GIP_ProcessPacket(joystick, ctx, &hdr, data + hdr_len)) {1562return false;1563}15641565data += hdr_len + hdr.packet_length;1566size -= hdr_len + hdr.packet_length;1567}1568return true;1569}15701571static bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device)1572{1573SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;1574SDL_Joystick *joystick = NULL;1575Uint8 data[USB_PACKET_LENGTH];1576int size;15771578if (device->num_joysticks > 0) {1579joystick = SDL_GetJoystickFromID(device->joysticks[0]);1580} else {1581return false;1582}15831584while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {1585#ifdef DEBUG_XBOX_PROTOCOL1586HIDAPI_DumpPacket("Xbox One packet: size = %d", data, size);1587#endif1588if (device->is_bluetooth) {1589switch (data[0]) {1590case 0x01:1591if (!joystick) {1592break;1593}1594if (size >= 16) {1595HIDAPI_DriverXboxOneBluetooth_HandleStatePacket(joystick, ctx, data, size);1596} else {1597#ifdef DEBUG_JOYSTICK1598SDL_Log("Unknown Xbox One Bluetooth packet size: %d", size);1599#endif1600}1601break;1602case 0x02:1603if (!joystick) {1604break;1605}1606HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(joystick, ctx, data, size);1607break;1608case 0x04:1609if (!joystick) {1610break;1611}1612HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(joystick, ctx, data, size);1613break;1614default:1615#ifdef DEBUG_JOYSTICK1616SDL_Log("Unknown Xbox One packet: 0x%.2x", data[0]);1617#endif1618break;1619}1620} else {1621HIDAPI_GIP_ProcessData(joystick, ctx, data, size);1622}1623}16241625HIDAPI_DriverXboxOne_UpdateInitState(ctx);1626HIDAPI_DriverXboxOne_UpdateRumble(ctx);16271628if (size < 0) {1629// Read error, device is disconnected1630HIDAPI_JoystickDisconnected(device, device->joysticks[0]);1631}1632return (size >= 0);1633}16341635static void HIDAPI_DriverXboxOne_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)1636{1637SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;16381639SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED,1640SDL_HomeLEDHintChanged, ctx);1641}16421643static void HIDAPI_DriverXboxOne_FreeDevice(SDL_HIDAPI_Device *device)1644{1645SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;16461647HIDAPI_GIP_DestroyChunkBuffer(ctx);1648}16491650SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne = {1651SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE,1652true,1653HIDAPI_DriverXboxOne_RegisterHints,1654HIDAPI_DriverXboxOne_UnregisterHints,1655HIDAPI_DriverXboxOne_IsEnabled,1656HIDAPI_DriverXboxOne_IsSupportedDevice,1657HIDAPI_DriverXboxOne_InitDevice,1658HIDAPI_DriverXboxOne_GetDevicePlayerIndex,1659HIDAPI_DriverXboxOne_SetDevicePlayerIndex,1660HIDAPI_DriverXboxOne_UpdateDevice,1661HIDAPI_DriverXboxOne_OpenJoystick,1662HIDAPI_DriverXboxOne_RumbleJoystick,1663HIDAPI_DriverXboxOne_RumbleJoystickTriggers,1664HIDAPI_DriverXboxOne_GetJoystickCapabilities,1665HIDAPI_DriverXboxOne_SetJoystickLED,1666HIDAPI_DriverXboxOne_SendJoystickEffect,1667HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled,1668HIDAPI_DriverXboxOne_CloseJoystick,1669HIDAPI_DriverXboxOne_FreeDevice,1670};16711672#endif // SDL_JOYSTICK_HIDAPI_XBOXONE16731674#endif // SDL_JOYSTICK_HIDAPI167516761677