Search code examples
macoshidiokitmfigamecontroller

Supporting both GCController and IOHIDDeviceRef


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.


Solution

  • 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:

    1. Whenever a new controller is connected to the Mac, connect callbacks are called for IOKit and GameController with IOHIDDeviceRef and GCController instances respectively.

    2. 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);
    1. 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);
    1. 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);
            }
        }
    }
    1. 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.