Skip to content

Commit 4c93262

Browse files
authored
hidapi/libusb: enable support for Xbox 360 and Xbox One controllers (#572)
Xbox controllers are not technically HID devices, but you can talk to them directly using libusb, and [SDL](https://github.com/libsdl-org/SDL) uses this to talk to controllers which may not have OS-level support.
1 parent d0856c0 commit 4c93262

1 file changed

Lines changed: 180 additions & 6 deletions

File tree

libusb/hid.c

Lines changed: 180 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,107 @@ static uint16_t get_report_descriptor_size_from_interface_descriptors(const stru
675675
return result;
676676
}
677677

678+
static int is_xbox360(unsigned short vendor_id, const struct libusb_interface_descriptor *intf_desc)
679+
{
680+
static const int xb360_iface_subclass = 93;
681+
static const int xb360_iface_protocol = 1; /* Wired */
682+
static const int xb360w_iface_protocol = 129; /* Wireless */
683+
static const int supported_vendors[] = {
684+
0x0079, /* GPD Win 2 */
685+
0x044f, /* Thrustmaster */
686+
0x045e, /* Microsoft */
687+
0x046d, /* Logitech */
688+
0x056e, /* Elecom */
689+
0x06a3, /* Saitek */
690+
0x0738, /* Mad Catz */
691+
0x07ff, /* Mad Catz */
692+
0x0e6f, /* PDP */
693+
0x0f0d, /* Hori */
694+
0x1038, /* SteelSeries */
695+
0x11c9, /* Nacon */
696+
0x12ab, /* Unknown */
697+
0x1430, /* RedOctane */
698+
0x146b, /* BigBen */
699+
0x1532, /* Razer Sabertooth */
700+
0x15e4, /* Numark */
701+
0x162e, /* Joytech */
702+
0x1689, /* Razer Onza */
703+
0x1949, /* Lab126, Inc. */
704+
0x1bad, /* Harmonix */
705+
0x20d6, /* PowerA */
706+
0x24c6, /* PowerA */
707+
0x2c22, /* Qanba */
708+
0x2dc8, /* 8BitDo */
709+
0x9886, /* ASTRO Gaming */
710+
};
711+
712+
if (intf_desc->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC &&
713+
intf_desc->bInterfaceSubClass == xb360_iface_subclass &&
714+
(intf_desc->bInterfaceProtocol == xb360_iface_protocol ||
715+
intf_desc->bInterfaceProtocol == xb360w_iface_protocol)) {
716+
size_t i;
717+
for (i = 0; i < sizeof(supported_vendors)/sizeof(supported_vendors[0]); ++i) {
718+
if (vendor_id == supported_vendors[i]) {
719+
return 1;
720+
}
721+
}
722+
}
723+
return 0;
724+
}
725+
726+
static int is_xboxone(unsigned short vendor_id, const struct libusb_interface_descriptor *intf_desc)
727+
{
728+
static const int xb1_iface_subclass = 71;
729+
static const int xb1_iface_protocol = 208;
730+
static const int supported_vendors[] = {
731+
0x044f, /* Thrustmaster */
732+
0x045e, /* Microsoft */
733+
0x0738, /* Mad Catz */
734+
0x0e6f, /* PDP */
735+
0x0f0d, /* Hori */
736+
0x10f5, /* Turtle Beach */
737+
0x1532, /* Razer Wildcat */
738+
0x20d6, /* PowerA */
739+
0x24c6, /* PowerA */
740+
0x2dc8, /* 8BitDo */
741+
0x2e24, /* Hyperkin */
742+
0x3537, /* GameSir */
743+
};
744+
745+
if (intf_desc->bInterfaceNumber == 0 &&
746+
intf_desc->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC &&
747+
intf_desc->bInterfaceSubClass == xb1_iface_subclass &&
748+
intf_desc->bInterfaceProtocol == xb1_iface_protocol) {
749+
size_t i;
750+
for (i = 0; i < sizeof(supported_vendors)/sizeof(supported_vendors[0]); ++i) {
751+
if (vendor_id == supported_vendors[i]) {
752+
return 1;
753+
}
754+
}
755+
}
756+
return 0;
757+
}
758+
759+
static int should_enumerate_interface(unsigned short vendor_id, const struct libusb_interface_descriptor *intf_desc)
760+
{
761+
#if 0
762+
printf("Checking interface 0x%x %d/%d/%d/%d\n", vendor_id, intf_desc->bInterfaceNumber, intf_desc->bInterfaceClass, intf_desc->bInterfaceSubClass, intf_desc->bInterfaceProtocol);
763+
#endif
764+
765+
if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID)
766+
return 1;
767+
768+
/* Also enumerate Xbox 360 controllers */
769+
if (is_xbox360(vendor_id, intf_desc))
770+
return 1;
771+
772+
/* Also enumerate Xbox One controllers */
773+
if (is_xboxone(vendor_id, intf_desc))
774+
return 1;
775+
776+
return 0;
777+
}
778+
678779
struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
679780
{
680781
libusb_device **devs;
@@ -718,7 +819,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
718819
for (k = 0; k < intf->num_altsetting; k++) {
719820
const struct libusb_interface_descriptor *intf_desc;
720821
intf_desc = &intf->altsetting[k];
721-
if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) {
822+
if (should_enumerate_interface(dev_vid, intf_desc)) {
722823
struct hid_device_info *tmp;
723824

724825
res = libusb_open(dev, &handle);
@@ -982,8 +1083,71 @@ static void *read_thread(void *param)
9821083
return NULL;
9831084
}
9841085

1086+
static void init_xbox360(libusb_device_handle *device_handle, unsigned short idVendor, unsigned short idProduct, const struct libusb_config_descriptor *conf_desc)
1087+
{
1088+
(void)conf_desc;
1089+
1090+
if ((idVendor == 0x05ac && idProduct == 0x055b) /* Gamesir-G3w */ ||
1091+
idVendor == 0x0f0d /* Hori Xbox controllers */) {
1092+
unsigned char data[20];
1093+
1094+
/* The HORIPAD FPS for Nintendo Switch requires this to enable input reports.
1095+
This VID/PID is also shared with other HORI controllers, but they all seem
1096+
to be fine with this as well.
1097+
*/
1098+
memset(data, 0, sizeof(data));
1099+
libusb_control_transfer(device_handle, 0xC1, 0x01, 0x100, 0x0, data, sizeof(data), 100);
1100+
}
1101+
}
1102+
1103+
static void init_xboxone(libusb_device_handle *device_handle, unsigned short idVendor, unsigned short idProduct, const struct libusb_config_descriptor *conf_desc)
1104+
{
1105+
static const int vendor_microsoft = 0x045e;
1106+
static const int xb1_iface_subclass = 71;
1107+
static const int xb1_iface_protocol = 208;
1108+
int j, k, res;
1109+
1110+
(void)idProduct;
1111+
1112+
for (j = 0; j < conf_desc->bNumInterfaces; j++) {
1113+
const struct libusb_interface *intf = &conf_desc->interface[j];
1114+
for (k = 0; k < intf->num_altsetting; k++) {
1115+
const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k];
1116+
if (intf_desc->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC &&
1117+
intf_desc->bInterfaceSubClass == xb1_iface_subclass &&
1118+
intf_desc->bInterfaceProtocol == xb1_iface_protocol) {
1119+
int bSetAlternateSetting = 0;
1120+
1121+
/* Newer Microsoft Xbox One controllers have a high speed alternate setting */
1122+
if (idVendor == vendor_microsoft &&
1123+
intf_desc->bInterfaceNumber == 0 && intf_desc->bAlternateSetting == 1) {
1124+
bSetAlternateSetting = 1;
1125+
} else if (intf_desc->bInterfaceNumber != 0 && intf_desc->bAlternateSetting == 0) {
1126+
bSetAlternateSetting = 1;
1127+
}
1128+
1129+
if (bSetAlternateSetting) {
1130+
res = libusb_claim_interface(device_handle, intf_desc->bInterfaceNumber);
1131+
if (res < 0) {
1132+
LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res);
1133+
continue;
1134+
}
1135+
1136+
LOG("Setting alternate setting for VID/PID 0x%x/0x%x interface %d to %d\n", idVendor, idProduct, intf_desc->bInterfaceNumber, intf_desc->bAlternateSetting);
9851137

986-
static int hidapi_initialize_device(hid_device *dev, int config_number, const struct libusb_interface_descriptor *intf_desc)
1138+
res = libusb_set_interface_alt_setting(device_handle, intf_desc->bInterfaceNumber, intf_desc->bAlternateSetting);
1139+
if (res < 0) {
1140+
LOG("xbox init: can't set alt setting %d: %d\n", intf_desc->bInterfaceNumber, res);
1141+
}
1142+
1143+
libusb_release_interface(device_handle, intf_desc->bInterfaceNumber);
1144+
}
1145+
}
1146+
}
1147+
}
1148+
}
1149+
1150+
static int hidapi_initialize_device(hid_device *dev, const struct libusb_interface_descriptor *intf_desc, const struct libusb_config_descriptor *conf_desc)
9871151
{
9881152
int i =0;
9891153
int res = 0;
@@ -1020,13 +1184,23 @@ static int hidapi_initialize_device(hid_device *dev, int config_number, const st
10201184
return 0;
10211185
}
10221186

1187+
/* Initialize XBox 360 controllers */
1188+
if (is_xbox360(desc.idVendor, intf_desc)) {
1189+
init_xbox360(dev->device_handle, desc.idVendor, desc.idProduct, conf_desc);
1190+
}
1191+
1192+
/* Initialize XBox One controllers */
1193+
if (is_xboxone(desc.idVendor, intf_desc)) {
1194+
init_xboxone(dev->device_handle, desc.idVendor, desc.idProduct, conf_desc);
1195+
}
1196+
10231197
/* Store off the string descriptor indexes */
10241198
dev->manufacturer_index = desc.iManufacturer;
10251199
dev->product_index = desc.iProduct;
10261200
dev->serial_index = desc.iSerialNumber;
10271201

10281202
/* Store off the USB information */
1029-
dev->config_number = config_number;
1203+
dev->config_number = conf_desc->bConfigurationValue;
10301204
dev->interface = intf_desc->bInterfaceNumber;
10311205

10321206
dev->report_descriptor_size = get_report_descriptor_size_from_interface_descriptors(intf_desc);
@@ -1110,7 +1284,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path)
11101284
const struct libusb_interface *intf = &conf_desc->interface[j];
11111285
for (k = 0; k < intf->num_altsetting && !good_open; k++) {
11121286
const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k];
1113-
if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) {
1287+
if (should_enumerate_interface(desc.idVendor, intf_desc)) {
11141288
char dev_path[64];
11151289
get_path(&dev_path, usb_dev, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber);
11161290
if (!strcmp(dev_path, path)) {
@@ -1122,7 +1296,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path)
11221296
LOG("can't open device\n");
11231297
break;
11241298
}
1125-
good_open = hidapi_initialize_device(dev, conf_desc->bConfigurationValue, intf_desc);
1299+
good_open = hidapi_initialize_device(dev, intf_desc, conf_desc);
11261300
if (!good_open)
11271301
libusb_close(dev->device_handle);
11281302
}
@@ -1200,7 +1374,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys
12001374
goto err;
12011375
}
12021376

1203-
if (!hidapi_initialize_device(dev, conf_desc->bConfigurationValue, selected_intf_desc))
1377+
if (!hidapi_initialize_device(dev, selected_intf_desc, conf_desc))
12041378
goto err;
12051379

12061380
return dev;

0 commit comments

Comments
 (0)