From 5774c9638c22863edb6f02c2380ec68c56373155 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 15 Sep 2023 05:14:26 -0700
Subject: [PATCH] Prefer hidraw over libusb when libusb whitelisting is not
enabled
Since opening devices via libusb will unbind them from other drivers, we will either whitelist specific devices that we want to support via libusb or we will prefer other drivers over libusb.
---
src/hidapi/SDL_hidapi.c | 249 +++++++++++++++++++++-------------------
1 file changed, 131 insertions(+), 118 deletions(-)
diff --git a/src/hidapi/SDL_hidapi.c b/src/hidapi/SDL_hidapi.c
index 77199292a8e3..929ed9e6a8f9 100644
--- a/src/hidapi/SDL_hidapi.c
+++ b/src/hidapi/SDL_hidapi.c
@@ -888,8 +888,7 @@ static const struct {
{ 0x057e, 0x0337 } /* Nintendo WUP-028, Wii U/Switch GameCube Adapter */
};
-static SDL_bool
-IsInWhitelist(Uint16 vendor, Uint16 product)
+static SDL_bool IsInWhitelist(Uint16 vendor, Uint16 product)
{
int i;
for (i = 0; i < SDL_arraysize(SDL_libusb_whitelist); i += 1) {
@@ -901,6 +900,10 @@ IsInWhitelist(Uint16 vendor, Uint16 product)
return SDL_FALSE;
}
+#endif /* HAVE_LIBUSB */
+
+#endif /* !SDL_HIDAPI_DISABLED */
+
#if HAVE_PLATFORM_BACKEND || HAVE_DRIVER_BACKEND
/* We have another way to get HID devices, so use the whitelist to get devices where libusb is preferred */
#define SDL_HIDAPI_LIBUSB_WHITELIST_DEFAULT SDL_TRUE
@@ -911,10 +914,6 @@ IsInWhitelist(Uint16 vendor, Uint16 product)
static SDL_bool use_libusb_whitelist = SDL_HIDAPI_LIBUSB_WHITELIST_DEFAULT;
-#endif /* HAVE_LIBUSB */
-
-#endif /* !SDL_HIDAPI_DISABLED */
-
/* Shared HIDAPI Implementation */
struct hidapi_backend
@@ -1155,9 +1154,9 @@ int SDL_hid_init(void)
}
#endif
-#ifdef HAVE_LIBUSB
use_libusb_whitelist = SDL_GetHintBoolean("SDL_HIDAPI_LIBUSB_WHITELIST",
SDL_HIDAPI_LIBUSB_WHITELIST_DEFAULT);
+#ifdef HAVE_LIBUSB
if (SDL_getenv("SDL_HIDAPI_DISABLE_LIBUSB") != NULL) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"libusb disabled by SDL_HIDAPI_DISABLE_LIBUSB");
@@ -1320,154 +1319,168 @@ Uint32 SDL_hid_device_change_count(void)
return counter;
}
-struct SDL_hid_device_info *SDL_hid_enumerate(unsigned short vendor_id, unsigned short product_id)
+static void AddDeviceToEnumeration(const char *driver_name, struct hid_device_info *dev, struct SDL_hid_device_info **devs, struct SDL_hid_device_info **last)
{
-#if defined(HAVE_PLATFORM_BACKEND) || defined(HAVE_DRIVER_BACKEND) || defined(HAVE_LIBUSB)
-#ifdef HAVE_LIBUSB
- struct hid_device_info *usb_devs = NULL;
- struct hid_device_info *usb_dev;
-#endif
-#ifdef HAVE_DRIVER_BACKEND
- struct hid_device_info *driver_devs = NULL;
- struct hid_device_info *driver_dev;
-#endif
-#ifdef HAVE_PLATFORM_BACKEND
- struct hid_device_info *raw_devs = NULL;
- struct hid_device_info *raw_dev;
+ struct SDL_hid_device_info *new_dev;
+
+#ifdef DEBUG_HIDAPI
+ SDL_Log("Adding %s device to enumeration: %ls %ls 0x%.4hx/0x%.4hx/%d",
+ driver_name, dev->manufacturer_string, dev->product_string, dev->vendor_id, dev->product_id, dev->interface_number);
+#else
+ (void)driver_name;
#endif
- struct SDL_hid_device_info *devs = NULL, *last = NULL, *new_dev;
- if (SDL_hidapi_refcount == 0 && SDL_hid_init() != 0) {
- return NULL;
+ new_dev = (struct SDL_hid_device_info *)SDL_malloc(sizeof(struct SDL_hid_device_info));
+ if (new_dev == NULL) {
+ /* Don't bother returning an error, get as many devices as possible */
+ return;
}
+ CopyHIDDeviceInfo(dev, new_dev);
-#ifdef HAVE_LIBUSB
- if (libusb_ctx.libhandle) {
- usb_devs = LIBUSB_hid_enumerate(vendor_id, product_id);
+ if ((*last) != NULL) {
+ (*last)->next = new_dev;
+ } else {
+ *devs = new_dev;
+ }
+ *last = new_dev;
+}
+
+static void RemoveDeviceFromEnumeration(const char *driver_name, struct hid_device_info *dev, struct hid_device_info **devs, void (*free_device_info)(struct hid_device_info *))
+{
+ struct hid_device_info *last = NULL, *curr, *next;
+
+ for (curr = *devs; curr; curr = next) {
+ next = curr->next;
+
+ if (dev->vendor_id == curr->vendor_id &&
+ dev->product_id == curr->product_id &&
+ (dev->interface_number < 0 || curr->interface_number < 0 || dev->interface_number == curr->interface_number)) {
#ifdef DEBUG_HIDAPI
- SDL_Log("libusb devices found:");
+ SDL_Log("Skipping %s device: %ls %ls 0x%.4hx/0x%.4hx/%d",
+ driver_name, curr->manufacturer_string, curr->product_string, curr->vendor_id, curr->product_id, curr->interface_number);
+#else
+ (void)driver_name;
#endif
- for (usb_dev = usb_devs; usb_dev; usb_dev = usb_dev->next) {
- if (use_libusb_whitelist) {
- if (!IsInWhitelist(usb_dev->vendor_id, usb_dev->product_id)) {
-#ifdef DEBUG_HIDAPI
- SDL_Log("Device was not in libusb whitelist: %ls %ls 0x%.4hx 0x%.4hx",
- usb_dev->manufacturer_string, usb_dev->product_string,
- usb_dev->vendor_id, usb_dev->product_id);
-#endif /* DEBUG_HIDAPI */
- continue;
- }
- }
- new_dev = (struct SDL_hid_device_info *)SDL_malloc(sizeof(struct SDL_hid_device_info));
- if (new_dev == NULL) {
- LIBUSB_hid_free_enumeration(usb_devs);
- SDL_hid_free_enumeration(devs);
- SDL_OutOfMemory();
- return NULL;
+ if (last) {
+ last->next = next;
+ } else {
+ *devs = next;
}
- CopyHIDDeviceInfo(usb_dev, new_dev);
+
+ curr->next = NULL;
+ free_device_info(curr);
+ continue;
+ }
+ last = curr;
+ }
+}
+
+#ifdef HAVE_LIBUSB
+static void RemoveNonWhitelistedDevicesFromEnumeration(struct hid_device_info **devs, void (*free_device_info)(struct hid_device_info *))
+{
+ struct hid_device_info *last = NULL, *curr, *next;
+
+ for (curr = *devs; curr; curr = next) {
+ next = curr->next;
+
+ if (!IsInWhitelist(curr->vendor_id, curr->product_id)) {
#ifdef DEBUG_HIDAPI
- SDL_Log(" - %ls %ls 0x%.4hx 0x%.4hx",
- usb_dev->manufacturer_string, usb_dev->product_string,
- usb_dev->vendor_id, usb_dev->product_id);
+ SDL_Log("Device was not in libusb whitelist, skipping: %ls %ls 0x%.4hx/0x%.4hx/%d",
+ curr->manufacturer_string, curr->product_string, curr->vendor_id, curr->product_id, curr->interface_number);
#endif
-
- if (last != NULL) {
- last->next = new_dev;
+ if (last) {
+ last->next = next;
} else {
- devs = new_dev;
+ *devs = next;
}
- last = new_dev;
+
+ curr->next = NULL;
+ free_device_info(curr);
+ continue;
}
+ last = curr;
}
+}
#endif /* HAVE_LIBUSB */
+struct SDL_hid_device_info *SDL_hid_enumerate(unsigned short vendor_id, unsigned short product_id)
+{
+ struct hid_device_info *driver_devs = NULL;
+ struct hid_device_info *usb_devs = NULL;
+ struct hid_device_info *raw_devs = NULL;
+ struct hid_device_info *dev;
+ struct SDL_hid_device_info *devs = NULL, *last = NULL;
+
+ if (SDL_hidapi_refcount == 0 && SDL_hid_init() != 0) {
+ return NULL;
+ }
+
+ /* Collect the available devices */
#ifdef HAVE_DRIVER_BACKEND
driver_devs = DRIVER_hid_enumerate(vendor_id, product_id);
- for (driver_dev = driver_devs; driver_dev; driver_dev = driver_dev->next) {
- new_dev = (struct SDL_hid_device_info *)SDL_malloc(sizeof(struct SDL_hid_device_info));
- CopyHIDDeviceInfo(driver_dev, new_dev);
+#endif
- if (last != NULL) {
- last->next = new_dev;
- } else {
- devs = new_dev;
+#ifdef HAVE_LIBUSB
+ if (libusb_ctx.libhandle) {
+ usb_devs = LIBUSB_hid_enumerate(vendor_id, product_id);
+
+ if (use_libusb_whitelist) {
+ RemoveNonWhitelistedDevicesFromEnumeration(&usb_devs, LIBUSB_hid_free_enumeration);
}
- last = new_dev;
}
-#endif /* HAVE_DRIVER_BACKEND */
+#endif /* HAVE_LIBUSB */
#ifdef HAVE_PLATFORM_BACKEND
if (udev_ctx) {
raw_devs = PLATFORM_hid_enumerate(vendor_id, product_id);
-#ifdef DEBUG_HIDAPI
- SDL_Log("hidraw devices found:");
-#endif
- for (raw_dev = raw_devs; raw_dev; raw_dev = raw_dev->next) {
- SDL_bool bFound = SDL_FALSE;
-#ifdef DEBUG_HIDAPI
- SDL_Log(" - %ls %ls 0x%.4hx 0x%.4hx",
- raw_dev->manufacturer_string, raw_dev->product_string,
- raw_dev->vendor_id, raw_dev->product_id);
+ }
#endif
+
+ /* Highest priority are custom driver devices */
+ for (dev = driver_devs; dev; dev = dev->next) {
+ AddDeviceToEnumeration("driver", dev, &devs, &last);
#ifdef HAVE_LIBUSB
- for (usb_dev = usb_devs; usb_dev; usb_dev = usb_dev->next) {
- if (raw_dev->vendor_id == usb_dev->vendor_id &&
- raw_dev->product_id == usb_dev->product_id &&
- (raw_dev->interface_number < 0 || raw_dev->interface_number == usb_dev->interface_number)) {
- bFound = SDL_TRUE;
- break;
- }
- }
+ RemoveDeviceFromEnumeration("libusb", dev, &usb_devs, LIBUSB_hid_free_enumeration);
#endif
-#ifdef HAVE_DRIVER_BACKEND
- for (driver_dev = driver_devs; driver_dev; driver_dev = driver_dev->next) {
- if (raw_dev->vendor_id == driver_dev->vendor_id &&
- raw_dev->product_id == driver_dev->product_id &&
- (raw_dev->interface_number < 0 || raw_dev->interface_number == driver_dev->interface_number)) {
- bFound = SDL_TRUE;
- break;
- }
- }
+#ifdef HAVE_PLATFORM_BACKEND
+ RemoveDeviceFromEnumeration("raw", dev, &raw_devs, PLATFORM_hid_free_enumeration);
#endif
- if (!bFound) {
- new_dev = (struct SDL_hid_device_info *)SDL_malloc(sizeof(struct SDL_hid_device_info));
- if (new_dev == NULL) {
+ }
+
+ /* If whitelist is in effect, libusb has priority, otherwise raw devices do */
+ if (use_libusb_whitelist) {
+ for (dev = usb_devs; dev; dev = dev->next) {
+ AddDeviceToEnumeration("libusb", dev, &devs, &last);
+#ifdef HAVE_PLATFORM_BACKEND
+ RemoveDeviceFromEnumeration("raw", dev, &raw_devs, PLATFORM_hid_free_enumeration);
+#endif
+ }
+ for (dev = raw_devs; dev; dev = dev->next) {
+ AddDeviceToEnumeration("platform", dev, &devs, &last);
+ }
+ } else {
+ for (dev = raw_devs; dev; dev = dev->next) {
+ AddDeviceToEnumeration("raw", dev, &devs, &last);
#ifdef HAVE_LIBUSB
- if (libusb_ctx.libhandle) {
- LIBUSB_hid_free_enumeration(usb_devs);
- }
+ RemoveDeviceFromEnumeration("libusb", dev, &usb_devs, LIBUSB_hid_free_enumeration);
#endif
- PLATFORM_hid_free_enumeration(raw_devs);
- SDL_hid_free_enumeration(devs);
- SDL_OutOfMemory();
- return NULL;
- }
- CopyHIDDeviceInfo(raw_dev, new_dev);
- new_dev->next = NULL;
-
- if (last != NULL) {
- last->next = new_dev;
- } else {
- devs = new_dev;
- }
- last = new_dev;
- }
}
- PLATFORM_hid_free_enumeration(raw_devs);
+ for (dev = usb_devs; dev; dev = dev->next) {
+ AddDeviceToEnumeration("libusb", dev, &devs, &last);
+ }
}
-#endif /* HAVE_PLATFORM_BACKEND */
+#ifdef HAVE_DRIVER_BACKEND
+ DRIVER_hid_free_enumeration(driver_devs);
+#endif
#ifdef HAVE_LIBUSB
- if (libusb_ctx.libhandle) {
- LIBUSB_hid_free_enumeration(usb_devs);
- }
+ LIBUSB_hid_free_enumeration(usb_devs);
+#endif
+#ifdef HAVE_PLATFORM_BACKEND
+ PLATFORM_hid_free_enumeration(raw_devs);
#endif
- return devs;
-#else
- return NULL;
-#endif /* HAVE_PLATFORM_BACKEND || HAVE_DRIVER_BACKEND || HAVE_LIBUSB */
+ return devs;
}
void SDL_hid_free_enumeration(struct SDL_hid_device_info *devs)