I'm working on OS X app which has support of game controllers. It must support controllers both originated from IOKit HID and GameController.framework. Problem I'm facing is that most of MFi GameController.framework compatible controllers are also hid devices. So, MFi controllers appear twice in controller list, both as GCController and IOHIDDevice. Is there any way to make a connection between them, to ignore HID device?
GCController objects have private property deviceRef
, which points to underlying hid device, making it possible to recognize and ignore device in HID layer. Problem is that deviceRef
is a private property, so I can't use it in App Store app.
Ideal solution would be a way to identify that IOHIDDeviceRef is MFi device, so I can skip it completely in my HID layer.
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);
typedef struct CF_BRIDGED_TYPE(id) __IOHIDServiceClient * IOHIDServiceClientRef;
extern "C" CFTypeRef _Nullable IOHIDServiceClientCopyProperty(IOHIDServiceClientRef service, CFStringRef key);
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);
}
}
}
You can see the full code described above in the Ouzel engine.