Added SDL_HINT_HIDAPI_ENUMERATE_ONLY_CONTROLLERS to control whether SDL_hid_enumerate() enumerates all HID devices or only 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.
This commit is contained in:
Sam Lantinga 2023-05-27 09:37:38 -07:00
parent 77e59d4fa9
commit 0ffeca8a1c
10 changed files with 66 additions and 89 deletions

View File

@ -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

View File

@ -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()
*

View File

@ -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) {

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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