I was experimenting with the GCController and finally found a hacky solution. Here is probably the only way to distinguish controllers that use GameController framework from the ones that use IOKit:
Whenever a new controller is connected to the Mac, connect callbacks are called for IOKit and GameController with IOHIDDeviceRef and GCController instances respectively.
Get the vendor ID and product ID of the IOHIDDeviceRef:
CFNumberRef vendor = static_cast<CFNumberRef>(IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
if (vendor) CFNumberGetValue(vendor, kCFNumberSInt32Type, &vendorId);
CFNumberRef product = static_cast<CFNumberRef>(IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)));
if (product) CFNumberGetValue(product, kCFNumberSInt32Type, &productId);
- Getting vendor ID and product ID of the GCController instance is a bit tricky (and hacky), but I tested it with many devices on a few versions of macOS and it works. First, you have to declare the IOHIDServiceClientCopyProperty function which is defined in IOKit/hidsystem/IOHIDServiceClient.h. IOHIDServiceClient.h is included only in the MacOSX sdk 10.12, so you will have to define the function for use with earlier versions of the SDK:
typedef struct CF_BRIDGED_TYPE(id) __IOHIDServiceClient * IOHIDServiceClientRef;
extern "C" CFTypeRef _Nullable IOHIDServiceClientCopyProperty(IOHIDServiceClientRef service, CFStringRef key);
- To get the actual ID, you first have to call the protected method "hidServices" of the GCController, that will return an array of pointers to GCCControllerHIDServiceInfo instances. GCCControllerHIDServiceInfo is an internal class which has two methods: "inputData" and "service" (which we are interested in). The array usually has only one element in it so we call the service method of it to get a IOHIDServiceClientRef instance of the device. You can get the vendor ID and product ID of it by calling the IOHIDServiceClientCopyProperty, everything else is similar to IOKit:
if (class_respondsToSelector(object_getClass(controller), sel_getUid("hidServices")))
{
NSArray* hidServices = reinterpret_cast<NSArray* (*)(id, SEL)>(objc_msgSend)(controller, sel_getUid("hidServices"));
if (hidServices && [hidServices count] > 0)
{
IOHIDServiceClientRef service = reinterpret_cast<IOHIDServiceClientRef (*)(id, SEL)>(objc_msgSend)([hidServices firstObject], sel_getUid("service"));
CFNumberRef vendor = static_cast<CFNumberRef>(IOHIDServiceClientCopyProperty(service, CFSTR(kIOHIDVendorIDKey)));
if (vendor)
{
CFNumberGetValue(vendor, kCFNumberSInt32Type, &vendorId);
CFRelease(vendor);
}
CFNumberRef product = static_cast<CFNumberRef>(IOHIDServiceClientCopyProperty(service, CFSTR(kIOHIDProductIDKey)));
if (product)
{
CFNumberGetValue(product, kCFNumberSInt32Type, &productId);
CFRelease(product);
}
}
}
- The last thing you have to do is actually compare the vendor ID and product ID to a list of devices that support one or another framework. For now, I know only two devices that support GameController framework (If you know any other GameController framework compatible devices, please let me know):
- SteelSeries Nimbus: Vendor ID = 0x1038, Product ID = 0x1420
- HoriPad Ultimate: Vendor ID = 0x0F0D, Product ID = 0x0090
You can see the full code described above in the Ouzel engine.
ioreg
or IOJones) If not, making deviceRef private seems like a bug, so I'd file a radar requesting a reliable public way of matching up HID device with GCController.