Using IOHIDManager to Retrieve Key Modifier Events

I'm trying to use the IOHIDManager to get modifier key events, because Cocoa flagsChanged events aren’t enough (it's hard to distinguish between press / release, left / right if both are omitted, etc.) Here is the code where I create the manager and register the reverse call.

IOHIDManagerRef hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); if (CFGetTypeID(hidManager) != IOHIDManagerGetTypeID()) return 1; CFMutableDictionaryRef capsLock = myCreateDeviceMatchingDictionary(0x07, 0x39); CFMutableDictionaryRef lctrl = myCreateDeviceMatchingDictionary(0x07, 0xE0); CFMutableDictionaryRef lshift = myCreateDeviceMatchingDictionary(0x07, 0xE1); CFMutableDictionaryRef lalt = myCreateDeviceMatchingDictionary(0x07, 0xE2); CFMutableDictionaryRef lsuper = myCreateDeviceMatchingDictionary(0x07, 0xE3); CFMutableDictionaryRef rctrl = myCreateDeviceMatchingDictionary(0x07, 0xE4); CFMutableDictionaryRef rshift = myCreateDeviceMatchingDictionary(0x07, 0xE5); CFMutableDictionaryRef ralt = myCreateDeviceMatchingDictionary(0x07, 0xE6); CFMutableDictionaryRef rsuper = myCreateDeviceMatchingDictionary(0x07, 0xE7); CFMutableDictionaryRef matchesList[] = { capsLock, lctrl, lshift, lalt, lsuper, rctrl, rshift, ralt, rsuper }; CFArrayRef matches = CFArrayCreate(kCFAllocatorDefault, (const void **)matchesList, 9, NULL); IOHIDManagerSetDeviceMatchingMultiple(hidManager, matches); IOHIDManagerRegisterInputValueCallback(hidManager, myHandleModifiersCallback, NULL); IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone); 

However, the callback never starts. Did I miss something?

I do not fully understand the HID usage pages, so I did not know whether to use a shared desktop page (0x01) with a keyboard usage identifier (06) or a keyboard / keyboard page (0x07) with usage identifiers for individual keys. Maybe something is connected with this?

+7
source share
2 answers

I get it. The way to do this is to use the full-featured keyboard (0x01) (06) (and the keyboard (07)) to use with the IOHIDManagerSetDeviceMatchingMultiple, and then the Keyboard / Keyboard Usage (0x07) material gets called back.

For example, to configure HIDManager for all keyboards / keyboards, you can do something like:

 IOHIDManagerRef hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); CFMutableDictionaryRef keyboard = myCreateDeviceMatchingDictionary(0x01, 6); CFMutableDictionaryRef keypad = myCreateDeviceMatchingDictionary(0x01, 7); CFMutableDictionaryRef matchesList[] = { keyboard, keypad, }; CFArrayRef matches = CFArrayCreate(kCFAllocatorDefault, (const void **)matchesList, 2, NULL); IOHIDManagerSetDeviceMatchingMultiple(hidManager, matches); IOHIDManagerRegisterInputValueCallback(hidManager, myHIDKeyboardCallback, NULL); IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone); 

Where myCreateDeviceMatchingDictionary is something like:

 CFMutableDictionaryRef myCreateDeviceMatchingDictionary(UInt32 usagePage, UInt32 usage) { CFMutableDictionaryRef ret = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!ret) return NULL; CFNumberRef pageNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage ); if (!pageNumberRef) { CFRelease(ret); return NULL; } CFDictionarySetValue(ret, CFSTR(kIOHIDDeviceUsagePageKey), pageNumberRef); CFRelease(pageNumberRef); CFNumberRef usageNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); if (!usageNumberRef) { CFRelease(ret); return NULL; } CFDictionarySetValue(ret, CFSTR(kIOHIDDeviceUsageKey), usageNumberRef); CFRelease(usageNumberRef); return ret; } 

And myHIDKeyboardCallback looks something like this:

 void myHIDKeyboardCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value) { IOHIDElementRef elem = IOHIDValueGetElement(value); if (IOHIDElementGetUsagePage(elem) != 0x07) return; uint32_t scancode = IOHIDElementGetUsage(elem); if (scancode < 4 || scancode > 231) return; long pressed = IOHIDValueGetIntegerValue(value); // ... Do something ... } 

Note that the callback appears to be called several times for each click or release, but with usage identifiers outside the normal range, which means "if (scancode <4 || scancode> 231)".

+9
source

Thanks for answering your question.

instead of the if-statement in myHIDKeyboardCallback, which checks scancode <4 or scancode> 231, you can use IOHIDManagerSetInputValueMatching .

 // before IOHIDManagerOpen int usageMin = 4; CFNumberRef minNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usageMin); CFDictionarySetValue(inputValueFilter, CFSTR(kIOHIDElementUsageMinKey), minNumberRef); CFRelease(minNumberRef); int usageMax = 231; CFNumberRef maxNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usageMax); CFDictionarySetValue(inputValueFilter, CFSTR(kIOHIDElementUsageMaxKey), maxNumberRef); CFRelease(maxNumberRef); IOHIDManagerSetInputValueMatching(hidManager, inputValueFilter); 

this is more LOC than a simple if statement, but you get a cleaner callback.

+4
source

All Articles