使用IOHIDManager获取修饰键事件

10

我正在尝试使用IOHIDManager获取修改键事件,因为Cocoa的flagsChanged事件不够完善(难以区分按下/释放,左/右键是否同时按下等)。以下是我创建管理器并注册回调的代码。

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);

然而,回调函数从未被运行。我有什么遗漏吗?

我不完全理解HID使用页面,所以我不知道是否应该使用通用桌面页面(0x01)和键盘使用ID(06),还是使用键盘/小键盘页面(0x07)和用于各个键的Usage ID。也许这与此有关?

2个回答

12

我明白了。实现这个的方法是使用通用桌面页面(0x01)的键盘(06)(为了完整性还有数字键盘(07)),并与IOHIDManagerSetDeviceMatchingMultiple一起使用,然后输入值回调会得到键盘/数字键盘使用页(0x07)的信息。

例如,要为所有键盘/数字键盘设置HID管理器,可以这样做:

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);

我的 myCreateDeviceMatchingDictionary 函数大致如下:

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;
}

而我的HIDKeyboardCallback函数大致如下:

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 ...
}
请注意,回调函数似乎会在每次按下或释放时被多次调用,但使用的ID超出了正常范围,这就是为什么要使用 "if (scancode < 4 || scancode > 231)" 的原因。

4
你是从哪里获取有关如何解析回调函数中的value,以获取像扫描码之类的内容的信息?你是否有一些(易读的)参考资料,或者是怎么弄明白这个问题的? - jalf

5

感谢您提供问题的答案。

myHIDKeyboardCallback中,可以使用IOHIDManagerSetInputValueMatching代替检查scancode<4或scancode>231的if语句。

// 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);

相比简单的if语句,使用这种方法需要编写更多代码,但是你会得到一个更加整洁的回调函数。


好知道。我想你也可以使用kIOHIDElementUsageKey将元素使用页面限制为0x07。不过这是必要的吗?键盘/小键盘设备是否会生成非0x07元素?我想如果你有一个带有触摸板或操纵杆的外部键盘,那么这是可能的。 - dostende
我从来没有用过这样的键盘(即带有摇杆或类似内置设备的键盘),但我预期第二个设备和键盘应该会分开处理它们的事件。但我不能确定。 - Yevgeniy
3
你有没有关于如何使用IOHID API的好资料?(我主要感兴趣的是读取鼠标/键盘输入。)只需按照苹果文档创建IOHIDManager并查找匹配的设备等等就相当简单,但如何解析发送到回调函数的数据则不太清楚。听起来你在API方面有一些经验,你知道有什么好的参考资料可以帮助理解这方面的知识吗? - jalf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接