Commit c6817a2c195da52a600a47d5ba796f33dc05dfad

Sam Lantinga 2020-01-16T15:32:41

Added support for the paddles on the Xbox One Elite Series 2 controller

diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c
index af09f6e..56ff2d6 100644
--- a/src/joystick/hidapi/SDL_hidapi_xboxone.c
+++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c
@@ -34,6 +34,9 @@
 
 #ifdef SDL_JOYSTICK_HIDAPI_XBOXONE
 
+/* Define this if you want to log all packets from the controller */
+/*#define DEBUG_XBOX_PROTOCOL*/
+
 #define USB_PACKET_LENGTH   64
 
 /* The amount of time to wait after hotplug to send controller init sequence */
@@ -100,8 +103,9 @@ static const SDL_DriverXboxOne_InitPacket xboxone_init_packets[] = {
     { 0x0000, 0x0000, 0x0000, 0x0000, xboxone_init2, sizeof(xboxone_init2), { 0x00, 0x00 } },
     { 0x0000, 0x0000, 0x0000, 0x0000, xboxone_init3, sizeof(xboxone_init3), { 0x00, 0x00 } },
     { 0x0000, 0x0000, 0x0000, 0x0000, xboxone_init4, sizeof(xboxone_init4), { 0x00, 0x00 } },
+
     /* These next packets are required for third party controllers (PowerA, PDP, HORI),
-       but are the wrong protocol for Microsoft Xbox controllers.
+       but aren't the correct protocol for Microsoft Xbox controllers.
      */
     { 0x0000, 0x0000, 0x045e, 0x0000, xboxone_init5, sizeof(xboxone_init5), { 0x00, 0x00 } },
     { 0x0000, 0x0000, 0x045e, 0x0000, xboxone_init6, sizeof(xboxone_init6), { 0x00, 0x00 } },
@@ -116,9 +120,29 @@ typedef struct {
     Uint8 last_state[USB_PACKET_LENGTH];
     SDL_bool rumble_synchronized;
     Uint32 rumble_expiration;
+    SDL_bool has_paddles;
 } SDL_DriverXboxOne_Context;
 
 
+#ifdef DEBUG_XBOX_PROTOCOL
+static void
+DumpPacket(const char *prefix, Uint8 *data, int size)
+{
+    int i;
+    char buffer[5*USB_PACKET_LENGTH];
+
+    SDL_snprintf(buffer, sizeof(buffer), prefix, size);
+    for (i = 0; i < size; ++i) {
+        if ((i % 8) == 0) {
+            SDL_snprintf(&buffer[SDL_strlen(buffer)], sizeof(buffer) - SDL_strlen(buffer), "\n%.2d:      ", i);
+        }
+        SDL_snprintf(&buffer[SDL_strlen(buffer)], sizeof(buffer) - SDL_strlen(buffer), " 0x%.2x", data[i]);
+    }
+    SDL_strlcat(buffer, "\n", sizeof(buffer));
+    SDL_Log("%s", buffer);
+}
+#endif /* DEBUG_XBOX_PROTOCOL */
+
 static SDL_bool
 IsBluetoothXboxOneController(Uint16 vendor_id, Uint16 product_id)
 {
@@ -139,6 +163,21 @@ IsBluetoothXboxOneController(Uint16 vendor_id, Uint16 product_id)
 }
 
 static SDL_bool
+ControllerHasPaddles(Uint16 vendor_id, Uint16 product_id)
+{
+    const Uint16 USB_VENDOR_MICROSOFT = 0x045e;
+    const Uint16 USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2 = 0x0b00;
+
+    if (vendor_id == USB_VENDOR_MICROSOFT) {
+        if (product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) {
+            return SDL_TRUE;
+        }
+        /* The original Elite controller probably works here, but I don't have one to test... */
+    }
+    return SDL_FALSE;
+}
+
+static SDL_bool
 ControllerNeedsRumbleSequenceSynchronized(Uint16 vendor_id, Uint16 product_id)
 {
     const Uint16 USB_VENDOR_MICROSOFT = 0x045e;
@@ -251,14 +290,7 @@ SendControllerInit(hid_device *dev, SDL_DriverXboxOne_Context *ctx)
 
                     while ((size = hid_read_timeout(dev, data, sizeof(data), 0)) > 0) {
 #ifdef DEBUG_XBOX_PROTOCOL
-                        SDL_Log("Xbox One INIT 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]);
+                        DumpPacket("Xbox One INIT packet: size = %d", data, size);
 #endif
                         if (size >= 2 && data[0] == packet->response[0] && data[1] == packet->response[1]) {
                             got_response = SDL_TRUE;
@@ -339,9 +371,10 @@ HIDAPI_DriverXboxOne_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joyst
     ctx->product_id = device->product_id;
     ctx->start_time = SDL_GetTicks();
     ctx->sequence = 1;
+    ctx->has_paddles = ControllerHasPaddles(ctx->vendor_id, ctx->product_id);
 
     /* Initialize the joystick capabilities */
-    joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX;
+    joystick->nbuttons = ctx->has_paddles ? SDL_CONTROLLER_BUTTON_MAX : (SDL_CONTROLLER_BUTTON_MAX + 4);
     joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
     joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
 
@@ -401,6 +434,20 @@ HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, 
         SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data[5] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
     }
 
+    if (ctx->has_paddles && size >= 20) {
+        /* data[19] is the paddle remapping mode, 0 = no remapping */
+        if (data[19] != 0) {
+            /* Respect that the paddles are being used for other controls and don't pass them on to the app */
+            data[18] = 0;
+        }
+        if (ctx->last_state[18] != data[18]) {
+            SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_MAX+0, (data[18] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+            SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_MAX+1, (data[18] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
+            SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_MAX+2, (data[18] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
+            SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_MAX+3, (data[18] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
+        }
+    }
+
     axis = ((int)*(Sint16*)(&data[6]) * 64) - 32768;
     if (axis == 32704) {
         axis = 32767;
@@ -464,14 +511,7 @@ HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device)
 
     while ((size = hid_read_timeout(device->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]);
+        DumpPacket("Xbox One packet: size = %d", data, size);
 #endif
         switch (data[0]) {
         case 0x02: