From 9ca5373024f3579e8b50c7c3c9ad74705033650f Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sat, 16 Nov 2019 19:55:53 -0800 Subject: [PATCH] Added support for the Xbox One S controller in Bluetooth mode on Linux --- src/joystick/hidapi/SDL_hidapi_xboxone.c | 147 ++++++++++++++++++++--- 1 file changed, 129 insertions(+), 18 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c index 5645ee5e8..42b809614 100644 --- a/src/joystick/hidapi/SDL_hidapi_xboxone.c +++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c @@ -148,6 +148,7 @@ HIDAPI_DriverXboxOne_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor SDL_DriverXboxOne_Context *ctx; int i; Uint8 init_packet[USB_PACKET_LENGTH]; + SDL_bool is_bluetooth = SDL_FALSE; ctx = (SDL_DriverXboxOne_Context *)SDL_calloc(1, sizeof(*ctx)); if (!ctx) { @@ -156,16 +157,21 @@ HIDAPI_DriverXboxOne_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor } *context = ctx; - /* Send the controller init data */ - for (i = 0; i < SDL_arraysize(xboxone_init_packets); ++i) { - const SDL_DriverXboxOne_InitPacket *packet = &xboxone_init_packets[i]; - if (!packet->vendor_id || (vendor_id == packet->vendor_id && product_id == packet->product_id)) { - SDL_memcpy(init_packet, packet->data, packet->size); - init_packet[2] = ctx->sequence++; - if (hid_write(dev, init_packet, packet->size) != packet->size) { - SDL_SetError("Couldn't write Xbox One initialization packet"); - SDL_free(ctx); - return SDL_FALSE; + if (vendor_id == 0x045e && product_id == 0x02fd) { + is_bluetooth = SDL_TRUE; + } + if (!is_bluetooth) { + /* Send the controller init data */ + for (i = 0; i < SDL_arraysize(xboxone_init_packets); ++i) { + const SDL_DriverXboxOne_InitPacket *packet = &xboxone_init_packets[i]; + if (!packet->vendor_id || (vendor_id == packet->vendor_id && product_id == packet->product_id)) { + SDL_memcpy(init_packet, packet->data, packet->size); + init_packet[2] = ctx->sequence++; + if (hid_write(dev, init_packet, packet->size) != packet->size) { + SDL_SetError("Couldn't write Xbox One initialization packet"); + SDL_free(ctx); + return SDL_FALSE; + } } } } @@ -184,15 +190,10 @@ HIDAPI_DriverXboxOne_Rumble(SDL_Joystick *joystick, hid_device *dev, void *conte SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)context; Uint8 rumble_packet[] = { 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF }; - /* The Rock Candy Xbox One Controller limits the range of - low frequency rumble strength in the range of [0 - 0x99] - high frequency rumble strength in the range of [0 - 0x82] - - I think the valid range of rumble at the firmware level is [0 - 0x7F] - */ + /* Magnitude is 1..100 so scale the 16-bit input here */ rumble_packet[2] = ctx->sequence++; - rumble_packet[8] = (low_frequency_rumble >> 9); - rumble_packet[9] = (high_frequency_rumble >> 9); + rumble_packet[8] = low_frequency_rumble / 655; + rumble_packet[9] = high_frequency_rumble / 655; if (hid_write(dev, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { return SDL_SetError("Couldn't send rumble packet"); @@ -266,6 +267,100 @@ HIDAPI_DriverXboxOne_HandleModePacket(SDL_Joystick *joystick, hid_device *dev, S SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data[4] & 0x01) ? SDL_PRESSED : SDL_RELEASED); } +static void +HIDAPI_DriverXboxOne_HandleBluetoothStatePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + + if (ctx->last_state[14] != data[14]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data[14] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data[14] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data[14] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data[14] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data[14] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data[14] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + } + if (ctx->last_state[15] != data[15]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data[15] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data[15] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data[15] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + } + if (ctx->last_state[16] != data[16]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data[16] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + } + + if (ctx->last_state[13] != data[13]) { + SDL_bool dpad_up = SDL_FALSE; + SDL_bool dpad_down = SDL_FALSE; + SDL_bool dpad_left = SDL_FALSE; + SDL_bool dpad_right = SDL_FALSE; + + switch (data[13]) { + case 1: + dpad_up = SDL_TRUE; + break; + case 2: + dpad_up = SDL_TRUE; + dpad_right = SDL_TRUE; + break; + case 3: + dpad_right = SDL_TRUE; + break; + case 4: + dpad_right = SDL_TRUE; + dpad_down = SDL_TRUE; + break; + case 5: + dpad_down = SDL_TRUE; + break; + case 6: + dpad_left = SDL_TRUE; + dpad_down = SDL_TRUE; + break; + case 7: + dpad_left = SDL_TRUE; + break; + case 8: + dpad_up = SDL_TRUE; + dpad_left = SDL_TRUE; + break; + default: + break; + } + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, dpad_down); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, dpad_up); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, dpad_right); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, dpad_left); + } + + axis = ((int)*(Sint16*)(&data[9]) * 64) - 32768; + if (axis == 32704) { + axis = 32767; + } + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis); + axis = ((int)*(Sint16*)(&data[11]) * 64) - 32768; + if (axis == 32704) { + axis = 32767; + } + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); + axis = *(Uint16*)(&data[1]) - 32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis); + axis = *(Uint16*)(&data[3]) - 32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis); + axis = *(Uint16*)(&data[5]) - 32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis); + axis = *(Uint16*)(&data[7]) - 32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis); + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void +HIDAPI_DriverXboxOne_HandleBluetoothModePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +{ + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED); +} + static SDL_bool HIDAPI_DriverXboxOne_Update(SDL_Joystick *joystick, hid_device *dev, void *context) { @@ -274,7 +369,23 @@ HIDAPI_DriverXboxOne_Update(SDL_Joystick *joystick, hid_device *dev, void *conte int size; while ((size = hid_read_timeout(dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_XBOX_PROTOCOL + SDL_Log("Xbox One packet: size = %d\n" + " 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x\n" + " 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x\n" + " 0x%.2x 0x%.2x 0x%.2x 0x%.2x\n", + size, + data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15], + data[16], data[17], data[18], data[19]); +#endif switch (data[0]) { + case 0x01: + HIDAPI_DriverXboxOne_HandleBluetoothStatePacket(joystick, dev, ctx, data, size); + break; + case 0x02: + HIDAPI_DriverXboxOne_HandleBluetoothModePacket(joystick, dev, ctx, data, size); + break; case 0x20: HIDAPI_DriverXboxOne_HandleStatePacket(joystick, dev, ctx, data, size); break;