Commit 4e1cc124d2bd68134cfb48e36f34b32077c4a3a7

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

Improved Xbox One controller initialization

diff --git a/src/hidapi/SDL_hidapi.c b/src/hidapi/SDL_hidapi.c
index f320c76..2d30f28 100644
--- a/src/hidapi/SDL_hidapi.c
+++ b/src/hidapi/SDL_hidapi.c
@@ -165,6 +165,7 @@ static struct
     int (*release_interface)(libusb_device_handle *dev_handle, int interface_number);
     int (*kernel_driver_active)(libusb_device_handle *dev_handle, int interface_number);
     int (*detach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number);
+    int (*set_interface_alt_setting)(libusb_device_handle *dev, int interface_number, int alternate_setting);
     struct libusb_transfer * (*alloc_transfer)(int iso_packets);
     int (*submit_transfer)(struct libusb_transfer *transfer);
     int (*cancel_transfer)(struct libusb_transfer *transfer);
@@ -207,6 +208,7 @@ static struct
 #define libusb_release_interface               libusb_ctx.release_interface
 #define libusb_kernel_driver_active            libusb_ctx.kernel_driver_active
 #define libusb_detach_kernel_driver            libusb_ctx.detach_kernel_driver
+#define libusb_set_interface_alt_setting       libusb_ctx.set_interface_alt_setting
 #define libusb_alloc_transfer                  libusb_ctx.alloc_transfer
 #define libusb_submit_transfer                 libusb_ctx.submit_transfer
 #define libusb_cancel_transfer                 libusb_ctx.cancel_transfer
@@ -472,6 +474,7 @@ int HID_API_EXPORT HID_API_CALL hid_init(void)
         LOAD_LIBUSB_SYMBOL(release_interface)
         LOAD_LIBUSB_SYMBOL(kernel_driver_active)
         LOAD_LIBUSB_SYMBOL(detach_kernel_driver)
+        LOAD_LIBUSB_SYMBOL(set_interface_alt_setting)
         LOAD_LIBUSB_SYMBOL(alloc_transfer)
         LOAD_LIBUSB_SYMBOL(submit_transfer)
         LOAD_LIBUSB_SYMBOL(cancel_transfer)
diff --git a/src/hidapi/libusb/hid.c b/src/hidapi/libusb/hid.c
index ce847bd..d6dfaf3 100644
--- a/src/hidapi/libusb/hid.c
+++ b/src/hidapi/libusb/hid.c
@@ -913,6 +913,39 @@ static int read_thread(void *param)
 	return 0;
 }
 
+static void init_xboxone(libusb_device_handle *device_handle, struct libusb_config_descriptor *conf_desc)
+{
+        static const int XB1_IFACE_SUBCLASS = 71;
+        static const int XB1_IFACE_PROTOCOL = 208;
+	int j, k, res;
+
+	for (j = 0; j < conf_desc->bNumInterfaces; j++) {
+		const struct libusb_interface *intf = &conf_desc->interface[j];
+		for (k = 0; k < intf->num_altsetting; k++) {
+			const struct libusb_interface_descriptor *intf_desc;
+			intf_desc = &intf->altsetting[k];
+
+			if (intf_desc->bInterfaceNumber != 0 &&
+			    intf_desc->bAlternateSetting == 0 &&
+			    intf_desc->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC &&
+			    intf_desc->bInterfaceSubClass == XB1_IFACE_SUBCLASS &&
+			    intf_desc->bInterfaceProtocol == XB1_IFACE_PROTOCOL) {
+				res = libusb_claim_interface(device_handle, intf_desc->bInterfaceNumber);
+				if (res < 0) {
+					LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res);
+					continue;
+				}
+
+				res = libusb_set_interface_alt_setting(device_handle, intf_desc->bInterfaceNumber, intf_desc->bAlternateSetting);
+				if (res < 0) {
+					LOG("xbox init: can't set alt setting %d: %d\n", intf_desc->bInterfaceNumber, res);
+				}
+
+				libusb_release_interface(device_handle, intf_desc->bInterfaceNumber);
+			}
+		}
+	}
+}
 
 hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive)
 {
@@ -934,6 +967,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive)
 		struct libusb_device_descriptor desc;
 		struct libusb_config_descriptor *conf_desc = NULL;
 		int i,j,k;
+
 		libusb_get_device_descriptor(usb_dev, &desc);
 
 		res = libusb_get_active_config_descriptor(usb_dev, &conf_desc);
@@ -959,6 +993,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive)
 							break;
 						}
 						good_open = 1;
+
 #ifdef DETACH_KERNEL_DRIVER
 						/* Detach the kernel driver, but only if the
 						   device is managed by the kernel */
@@ -973,6 +1008,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive)
 							}
 						}
 #endif
+
 						res = libusb_claim_interface(dev->device_handle, intf_desc->bInterfaceNumber);
 						if (res < 0) {
 							LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res);
@@ -982,6 +1018,11 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive)
 							break;
 						}
 
+						/* Initialize XBox One controllers */
+						if (is_xboxone(desc.idVendor, intf_desc)) {
+							init_xboxone(dev->device_handle, conf_desc);
+						}
+
 						/* Store off the string descriptor indexes */
 						dev->manufacturer_index = desc.iManufacturer;
 						dev->product_index      = desc.iProduct;
diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c
index 3f4c7f4..af09f6e 100644
--- a/src/joystick/hidapi/SDL_hidapi_xboxone.c
+++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c
@@ -86,6 +86,8 @@ static const Uint8 xboxone_rumble_reset[] = {
 typedef struct {
     Uint16 vendor_id;
     Uint16 product_id;
+    Uint16 exclude_vendor_id;
+    Uint16 exclude_product_id;
     const Uint8 *data;
     int size;
     const Uint8 response[2];
@@ -93,13 +95,16 @@ typedef struct {
 
 
 static const SDL_DriverXboxOne_InitPacket xboxone_init_packets[] = {
-    { 0x0000, 0x0000, xboxone_init0, sizeof(xboxone_init0), { 0x04, 0xf0 } },
-    { 0x0000, 0x0000, xboxone_init1, sizeof(xboxone_init1), { 0x04, 0xb0 } },
-    { 0x0000, 0x0000, xboxone_init2, sizeof(xboxone_init2), { 0x00, 0x00 } },
-    { 0x0000, 0x0000, xboxone_init3, sizeof(xboxone_init3), { 0x00, 0x00 } },
-    { 0x0000, 0x0000, xboxone_init4, sizeof(xboxone_init4), { 0x00, 0x00 } },
-    { 0x0000, 0x0000, xboxone_init5, sizeof(xboxone_init5), { 0x00, 0x00 } },
-    { 0x0000, 0x0000, xboxone_init6, sizeof(xboxone_init6), { 0x00, 0x00 } },
+    { 0x0000, 0x0000, 0x0000, 0x0000, xboxone_init0, sizeof(xboxone_init0), { 0x04, 0xf0 } },
+    { 0x0000, 0x0000, 0x0000, 0x0000, xboxone_init1, sizeof(xboxone_init1), { 0x04, 0xb0 } },
+    { 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.
+     */
+    { 0x0000, 0x0000, 0x045e, 0x0000, xboxone_init5, sizeof(xboxone_init5), { 0x00, 0x00 } },
+    { 0x0000, 0x0000, 0x045e, 0x0000, xboxone_init6, sizeof(xboxone_init6), { 0x00, 0x00 } },
 };
 
 typedef struct {
@@ -137,14 +142,10 @@ static SDL_bool
 ControllerNeedsRumbleSequenceSynchronized(Uint16 vendor_id, Uint16 product_id)
 {
     const Uint16 USB_VENDOR_MICROSOFT = 0x045e;
-    const Uint16 USB_PRODUCT_XBOX_ONE_MODEL_1708 = 0x02ea;      /* Needed with the latest firmware */
-    const Uint16 USB_PRODUCT_XBOX_ONE_ELITE_SERIES2 = 0x0b00;
 
     if (vendor_id == USB_VENDOR_MICROSOFT) {
-        if (product_id == USB_PRODUCT_XBOX_ONE_MODEL_1708 ||
-            product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES2) {
-            return SDL_TRUE;
-        }
+        /* All Xbox One controllers, from model 1537 through Elite Series 2, appear to need this */
+        return SDL_TRUE;
     }
     return SDL_FALSE;
 }
@@ -177,12 +178,12 @@ SynchronizeRumbleSequence(hid_device *dev, SDL_DriverXboxOne_Context *ctx)
     return SDL_TRUE;
 }
 
-
 /* Return true if this controller sends the 0x02 "waiting for init" packet */
 static SDL_bool
 ControllerSendsWaitingForInit(Uint16 vendor_id, Uint16 product_id)
 {
     const Uint16 USB_VENDOR_HYPERKIN = 0x2e24;
+    const Uint16 USB_VENDOR_PDP = 0x0e6f;
 
     if (vendor_id == USB_VENDOR_HYPERKIN) {
         /* The Hyperkin controllers always send 0x02 when waiting for init,
@@ -190,11 +191,15 @@ ControllerSendsWaitingForInit(Uint16 vendor_id, Uint16 product_id)
            to make sure we don't send the init sequence if it isn't needed.
         */
         return SDL_TRUE;
-    } else {
-        /* Other controllers may or may not send 0x02, but it doesn't hurt to reinit */
-        /* The PDP and PowerA controllers don't always send 0x02 when plugged in on Linux */
+    }
+    if (vendor_id == USB_VENDOR_PDP) {
+        /* The PDP Rock Candy (PID 0x0246) doesn't send 0x02 on Linux for some reason */
         return SDL_FALSE;
     }
+
+    /* It doesn't hurt to reinit, especially if a driver has misconfigured the controller */
+    /*return SDL_TRUE;*/
+    return SDL_FALSE;
 }
 
 static SDL_bool
@@ -218,6 +223,14 @@ SendControllerInit(hid_device *dev, SDL_DriverXboxOne_Context *ctx)
                 continue;
             }
 
+            if (packet->exclude_vendor_id && (vendor_id == packet->exclude_vendor_id)) {
+                continue;
+            }
+
+            if (packet->exclude_product_id && (product_id == packet->exclude_product_id)) {
+                continue;
+            }
+
             SDL_memcpy(init_packet, packet->data, packet->size);
             if (init_packet[0] != 0x01) {
                 init_packet[2] = ctx->sequence++;