diff --git a/include/SDL3/SDL_hidapi.h b/include/SDL3/SDL_hidapi.h index d1d3e56fb..ec1febdd5 100644 --- a/include/SDL3/SDL_hidapi.h +++ b/include/SDL3/SDL_hidapi.h @@ -222,6 +222,8 @@ extern DECLSPEC Uint32 SDLCALL SDL_hid_device_change_count(void); * matches. If `vendor_id` and `product_id` are both set to 0, then all HID * devices will be returned. * + * By default SDL will only enumerate controllers, to reduce risk of hanging or crashing on bad drivers, but SDL_HINT_HIDAPI_ENUMERATE_ONLY_CONTROLLERS can be set to "0" to enumerate all HID devices. + * * \param vendor_id The Vendor ID (VID) of the types of device to open, or 0 * to match any vendor. * \param product_id The Product ID (PID) of the types of device to open, or 0 diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index ee9d2f115..62038a3c7 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -580,6 +580,17 @@ extern "C" { */ #define SDL_HINT_GRAB_KEYBOARD "SDL_GRAB_KEYBOARD" +/** + * \brief A variable to control whether SDL_hid_enumerate() enumerates all HID devices or only controllers. + * + * This variable can be set to the following values: + * "0" - SDL_hid_enumerate() will enumerate all HID devices + * "1" - SDL_hid_enumerate() will only enumerate controllers + * + * By default SDL will only enumerate controllers, to reduce risk of hanging or crashing on devices with bad drivers and avoiding macOS keyboard capture permission prompts. + */ +#define SDL_HINT_HIDAPI_ENUMERATE_ONLY_CONTROLLERS "SDL_HIDAPI_ENUMERATE_ONLY_CONTROLLERS" + /** * \brief A variable containing a list of devices to ignore in SDL_hid_enumerate() * diff --git a/src/hidapi/SDL_hidapi.c b/src/hidapi/SDL_hidapi.c index 2dda506b8..b2db1fdfc 100644 --- a/src/hidapi/SDL_hidapi.c +++ b/src/hidapi/SDL_hidapi.c @@ -30,6 +30,8 @@ #include "SDL_internal.h" #include "SDL_hidapi_c.h" +#include "../joystick/usb_ids.h" +#include "../SDL_hints_c.h" /* Initial type declarations */ #define HID_API_NO_EXPORT_DEFINE /* do not export hidapi procedures */ @@ -529,7 +531,8 @@ static void HIDAPI_ShutdownDiscovery(void) /* Platform HIDAPI Implementation */ #define HIDAPI_USING_SDL_RUNTIME -#define HIDAPI_IGNORE_DEVICE(VID, PID) SDL_HIDAPI_ShouldIgnoreDevice(VID, PID) +#define HIDAPI_IGNORE_DEVICE(VID, PID, USAGE_PAGE, USAGE) \ + SDL_HIDAPI_ShouldIgnoreDevice(VID, PID, USAGE_PAGE, USAGE) struct PLATFORM_hid_device_; typedef struct PLATFORM_hid_device_ PLATFORM_hid_device; @@ -1033,8 +1036,14 @@ static void CopyHIDDeviceInfo(struct hid_device_info *pSrc, struct SDL_hid_devic #undef WCOPY_IF_EXISTS static int SDL_hidapi_refcount = 0; +static SDL_bool SDL_hidapi_only_controllers; static char *SDL_hidapi_ignored_devices = NULL; +static void SDLCALL OnlyControllersChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_hidapi_only_controllers = SDL_GetStringBoolean(hint, SDL_TRUE); +} + static void SDLCALL IgnoredDevicesChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { if (SDL_hidapi_ignored_devices) { @@ -1047,9 +1056,22 @@ static void SDLCALL IgnoredDevicesChanged(void *userdata, const char *name, cons } } -SDL_bool SDL_HIDAPI_ShouldIgnoreDevice(Uint16 vendor_id, Uint16 product_id) +SDL_bool SDL_HIDAPI_ShouldIgnoreDevice(Uint16 vendor_id, Uint16 product_id, Uint16 usage_page, Uint16 usage) { /* See if there are any devices we should skip in enumeration */ + if (SDL_hidapi_only_controllers && usage_page) { + if (vendor_id == USB_VENDOR_VALVE) { + /* Ignore the keyboard interface on Steam Controllers */ + if (usage == USB_USAGE_GENERIC_KEYBOARD) { + return SDL_TRUE; + } + } else if (usage_page == USB_USAGEPAGE_GENERIC_DESKTOP && + (usage == USB_USAGE_GENERIC_JOYSTICK || usage == USB_USAGE_GENERIC_GAMEPAD || usage == USB_USAGE_GENERIC_MULTIAXISCONTROLLER)) { + /* This is a controller */ + } else { + return SDL_TRUE; + } + } if (SDL_hidapi_ignored_devices) { char vendor_match[16], product_match[16]; SDL_snprintf(vendor_match, sizeof(vendor_match), "0x%.4x/0x0000", vendor_id); @@ -1071,6 +1093,7 @@ int SDL_hid_init(void) return 0; } + SDL_AddHintCallback(SDL_HINT_HIDAPI_ENUMERATE_ONLY_CONTROLLERS, OnlyControllersChanged, NULL); SDL_AddHintCallback(SDL_HINT_HIDAPI_IGNORE_DEVICES, IgnoredDevicesChanged, NULL); #ifdef SDL_USE_LIBUDEV @@ -1216,6 +1239,7 @@ int SDL_hid_exit(void) } #endif /* HAVE_LIBUSB */ + SDL_DelHintCallback(SDL_HINT_HIDAPI_ENUMERATE_ONLY_CONTROLLERS, OnlyControllersChanged, NULL); SDL_DelHintCallback(SDL_HINT_HIDAPI_IGNORE_DEVICES, IgnoredDevicesChanged, NULL); if (SDL_hidapi_ignored_devices) { diff --git a/src/hidapi/SDL_hidapi_c.h b/src/hidapi/SDL_hidapi_c.h index 8f69cba4c..0b2fe70eb 100644 --- a/src/hidapi/SDL_hidapi_c.h +++ b/src/hidapi/SDL_hidapi_c.h @@ -22,7 +22,7 @@ /* Return true if the HIDAPI should ignore a device during enumeration */ -extern SDL_bool SDL_HIDAPI_ShouldIgnoreDevice(Uint16 vendor_id, Uint16 product_id); +extern SDL_bool SDL_HIDAPI_ShouldIgnoreDevice(Uint16 vendor_id, Uint16 product_id, Uint16 usage_page, Uint16 usage); #ifdef SDL_JOYSTICK_HIDAPI #ifdef HAVE_LIBUSB diff --git a/src/hidapi/android/hid.cpp b/src/hidapi/android/hid.cpp index 6a8dac407..75b0d4fd3 100644 --- a/src/hidapi/android/hid.cpp +++ b/src/hidapi/android/hid.cpp @@ -1074,7 +1074,7 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor const hid_device_info *info = pDevice->GetDeviceInfo(); /* See if there are any devices we should skip in enumeration */ - if (SDL_HIDAPI_ShouldIgnoreDevice(info->vendor_id, info->product_id)) { + if (SDL_HIDAPI_ShouldIgnoreDevice(info->vendor_id, info->product_id, 0, 0)) { continue; } diff --git a/src/hidapi/ios/hid.m b/src/hidapi/ios/hid.m index d67fc1a4f..be0b327ff 100644 --- a/src/hidapi/ios/hid.m +++ b/src/hidapi/ios/hid.m @@ -859,7 +859,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, struct hid_device_info *root = NULL; /* See if there are any devices we should skip in enumeration */ - if (SDL_HIDAPI_ShouldIgnoreDevice(VALVE_USB_VID, D0G_BLE2_PID)) { + if (SDL_HIDAPI_ShouldIgnoreDevice(VALVE_USB_VID, D0G_BLE2_PID, 0, 0)) { return NULL; } diff --git a/src/hidapi/libusb/hid.c b/src/hidapi/libusb/hid.c index 067e9f35e..a7fde33cb 100644 --- a/src/hidapi/libusb/hid.c +++ b/src/hidapi/libusb/hid.c @@ -1080,7 +1080,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, #ifdef HIDAPI_IGNORE_DEVICE /* See if there are any devices we should skip in enumeration */ - if (HIDAPI_IGNORE_DEVICE(dev_vid, dev_pid)) { + if (HIDAPI_IGNORE_DEVICE(dev_vid, dev_pid, 0, 0)) { continue; } #endif diff --git a/src/hidapi/linux/hid.c b/src/hidapi/linux/hid.c index cc0edbaf7..9000caf9d 100644 --- a/src/hidapi/linux/hid.c +++ b/src/hidapi/linux/hid.c @@ -974,7 +974,13 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, if (!parse_hid_vid_pid_from_sysfs(sysfs_path, &bus_type, &dev_vid, &dev_pid)) continue; - if (HIDAPI_IGNORE_DEVICE(dev_vid, dev_pid)) { + struct hidraw_report_descriptor report_desc; + unsigned short page = 0, usage = 0; + unsigned int pos = 0; + if (get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc) >= 0) { + get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage); + } + if (HIDAPI_IGNORE_DEVICE(dev_vid, dev_pid, page, usage)) { continue; } #endif diff --git a/src/hidapi/mac/hid.c b/src/hidapi/mac/hid.c index 13bd9b94a..56d981f19 100644 --- a/src/hidapi/mac/hid.c +++ b/src/hidapi/mac/hid.c @@ -38,11 +38,6 @@ #include "hidapi_darwin.h" -/* Only matching controllers is a more safe option and prevents input monitoring permissions dialogs */ -#ifndef HIDAPI_ONLY_ENUMERATE_CONTROLLERS -#define HIDAPI_ONLY_ENUMERATE_CONTROLLERS 0 -#endif - /* As defined in AppKit.h, but we don't need the entire AppKit for a single constant. */ extern const double NSAppKitVersionNumber; @@ -716,51 +711,6 @@ static struct hid_device_info *create_device_info(IOHIDDeviceRef device) return root; } -static CFDictionaryRef create_usage_match(const UInt32 page, const UInt32 usage, int *okay) -{ - CFDictionaryRef retval = NULL; - CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); - CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); - const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) }; - const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef }; - - if (pageNumRef && usageNumRef) { - retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - } - - if (pageNumRef) { - CFRelease(pageNumRef); - } - if (usageNumRef) { - CFRelease(usageNumRef); - } - - if (!retval) { - *okay = 0; - } - - return retval; -} - -static CFDictionaryRef create_vendor_match(const UInt32 vendor, int *okay) -{ - CFDictionaryRef retval = NULL; - CFNumberRef vidNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vendor); - const void *keys[1] = { (void *) CFSTR(kIOHIDVendorIDKey) }; - const void *vals[1] = { (void *) vidNumRef }; - - if (vidNumRef) { - retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - CFRelease(vidNumRef); - } - - if (!retval) { - *okay = 0; - } - - return retval; -} - struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { struct hid_device_info *root = NULL; /* return object */ @@ -778,8 +728,9 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, process_pending_events(); /* Get a list of the Devices */ + CFMutableDictionaryRef matching = NULL; if (vendor_id != 0 || product_id != 0) { - CFMutableDictionaryRef matching = CFDictionaryCreateMutable(kCFAllocatorDefault, kIOHIDOptionsTypeNone, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + matching = CFDictionaryCreateMutable(kCFAllocatorDefault, kIOHIDOptionsTypeNone, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (matching && vendor_id != 0) { CFNumberRef v = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &vendor_id); @@ -792,35 +743,10 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, CFDictionarySetValue(matching, CFSTR(kIOHIDProductIDKey), p); CFRelease(p); } - - IOHIDManagerSetDeviceMatching(hid_mgr, matching); - if (matching != NULL) { - CFRelease(matching); - } - } else if (HIDAPI_ONLY_ENUMERATE_CONTROLLERS) { - const UInt32 VALVE_USB_VID = 0x28DE; - int okay = 1; - const void *vals[] = { - (void *) create_usage_match(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay), - (void *) create_usage_match(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay), - (void *) create_usage_match(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay), - (void *) create_vendor_match(VALVE_USB_VID, &okay), - }; - CFIndex numElements = sizeof(vals) / sizeof(vals[0]); - CFArrayRef matching = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL; - - for (i = 0; i < numElements; i++) { - if (vals[i]) { - CFRelease((CFTypeRef) vals[i]); - } - } - - IOHIDManagerSetDeviceMatchingMultiple(hid_mgr, matching); - if (matching != NULL) { - CFRelease(matching); - } - } else { - IOHIDManagerSetDeviceMatching(hid_mgr, NULL); + } + IOHIDManagerSetDeviceMatching(hid_mgr, matching); + if (matching != NULL) { + CFRelease(matching); } CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); @@ -848,7 +774,9 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, /* See if there are any devices we should skip in enumeration */ unsigned short dev_vid = get_vendor_id(dev); unsigned short dev_pid = get_product_id(dev); - if (HIDAPI_IGNORE_DEVICE(dev_vid, dev_pid)) { + unsigned short usage_page = get_int_property(dev, CFSTR(kIOHIDPrimaryUsagePageKey)); + unsigned short usage = get_int_property(dev, CFSTR(kIOHIDPrimaryUsageKey)); + if (HIDAPI_IGNORE_DEVICE(dev_vid, dev_pid, usage_page, usage)) { continue; } #endif diff --git a/src/hidapi/windows/hid.c b/src/hidapi/windows/hid.c index b3074c0c9..33d3fd307 100644 --- a/src/hidapi/windows/hid.c +++ b/src/hidapi/windows/hid.c @@ -901,7 +901,13 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor #ifdef HIDAPI_IGNORE_DEVICE /* See if there are any devices we should skip in enumeration */ - if (HIDAPI_IGNORE_DEVICE(attrib.VendorID, attrib.ProductID)) { + PHIDP_PREPARSED_DATA pp_data = NULL; + HIDP_CAPS caps = { 0 }; + if (HidD_GetPreparsedData(device_handle, &pp_data)) { + HidP_GetCaps(pp_data, &caps); + HidD_FreePreparsedData(pp_data); + } + if (HIDAPI_IGNORE_DEVICE(attrib.VendorID, attrib.ProductID, caps.UsagePage, caps.Usage)) { goto cont_close; } #endif