3

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.

4
  • 1
    I don't have a GCController capable device handy, but have you checked the properties on the IOHIDDevice in the I/O registry to see if there are any hints of the Game Controller framework in there? (use IORegistryExplorer or 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.
    – pmdj
    Nov 4, 2015 at 11:54
  • Just encountered a need for this myself. Did you ever find a solution?
    – Toji
    Aug 24, 2016 at 15:53
  • No, I didn't. I talked to Apple guys on Apple TV Tech Talks, they told me there would not be a solution. My suggestion is to make couple heuristics to check if devices are same.
    – Shchvova
    Aug 31, 2016 at 18:21
  • I know this is not a universal approach, but I plan to create a vendor/product id database (github.com/elnormous/ouzel/blob/master/ouzel/input/macos/…) and check it to see if the device supports GCController. Jul 19, 2017 at 7:40

2 Answers 2

5

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.

2

GCController on macOS 11 onwards has +[GCController supportsHIDDevice:] API that takes a IOHIDDeviceRef. To support earlier systems I'd advise what Elviss suggests. You can also refer to WebKit's source code which handles both cases.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.