SDL: hid: Only enumerate IOHIDDevices that are likely to be joysticks

From db60b27188d275f1bb711bac5e7a9f0295c64b12 Mon Sep 17 00:00:00 2001
From: Cameron Gutman <[EMAIL REDACTED]>
Date: Sat, 20 Nov 2021 13:13:17 -0600
Subject: [PATCH] hid: Only enumerate IOHIDDevices that are likely to be
 joysticks

Touching HID devices with keyboard usages will trigger a keyboard capture
permission prompt on macOS 11+. See #4887

Like the IOKit joystick backend, we accept HID devices that have joystick,
gamepad, or multi-axis controller usages. We also allow the Valve VID for
the Steam Controller, just like the Windows HIDAPI implementation does.
---
 src/hidapi/mac/hid.c | 74 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 71 insertions(+), 3 deletions(-)

diff --git a/src/hidapi/mac/hid.c b/src/hidapi/mac/hid.c
index 1ad042d4900..3d0ad30eee0 100644
--- a/src/hidapi/mac/hid.c
+++ b/src/hidapi/mac/hid.c
@@ -33,6 +33,8 @@
 
 #include "../hidapi/hidapi.h"
 
+#define VALVE_USB_VID		0x28DE
+
 /* Barrier implementation because Mac OSX doesn't have pthread_barrier.
  It also doesn't have clock_gettime(). So much for POSIX and SUSv2.
  This implementation came from Brent Priddy and was posted on
@@ -399,20 +401,86 @@ static void hid_device_removal_callback(void *context, IOReturn result,
 	}
 }
 
+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;
+}
+
 /* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */
 static int init_hid_manager(void)
 {
+    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),
+    };
+    const size_t numElements = SDL_arraysize(vals);
+    CFArrayRef matchingArray = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
+	size_t i;
+
+    for (i = 0; i < numElements; i++) {
+        if (vals[i]) {
+            CFRelease((CFTypeRef) vals[i]);
+        }
+    }
 
 	/* Initialize all the HID Manager Objects */
 	hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
 	if (hid_mgr) {
-		IOHIDManagerSetDeviceMatching(hid_mgr, NULL);
+		IOHIDManagerSetDeviceMatchingMultiple(hid_mgr, matchingArray);
 		IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
 		IOHIDManagerRegisterDeviceRemovalCallback(hid_mgr, hid_device_removal_callback, NULL);
-		return 0;
 	}
 	
-	return -1;
+	if (matchingArray != NULL) {
+		CFRelease(matchingArray);
+	}
+
+	return hid_mgr ? 0 : -1;
 }
 
 /* Initialize the IOHIDManager if necessary. This is the public function, and