使用IOHIDManager获取修饰键事件

dos*_*nde 8 keyboard macos iokit core-foundation

我正在尝试使用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);
Run Code Online (Sandbox Code Playgroud)

但是,回调永远不会运行.我错过了什么吗?

我不完全了解HID使用页面,因此我不知道是否将通用桌面页面(0x01)与键盘使用ID(06)或键盘/键盘页面(0x07)与个人的使用ID一起使用键.也许这与它有关?

dos*_*nde 10

我想到了.这样做的方法是使用通用桌面页面(0x01)键盘(06)(和键盘(07)的完整性)与IOHIDManagerSetDeviceMatchingMultiple一起使用,然后输入值回调获取键盘/键盘用法页面(0x07)的东西.

例如,要为所有键盘/键盘设置HIDManager,可以执行以下操作:

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);
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

myHIDKeyboardCallback类似于:

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 ...
}
Run Code Online (Sandbox Code Playgroud)

请注意,每次按下或释放时,回调似乎被调用多次,但使用ID超出正常范围,这就是"if(scancode <4 || scancode> 231)"的用途.

  • 你在哪里找到了关于如何在你的回调中解析`value`的信息,以获得像scancode这样的东西?你有一些(可读的)参考,或者你是如何弄清楚的? (2认同)

Yev*_*niy 5

感谢您提供问题的答案。

代替检查scancode <4 或scancode >231 的myHIDKeyboardCallback中的 if 语句,您可以使用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);
Run Code Online (Sandbox Code Playgroud)

它更多的是 LOC 而不是一个简单的 if 语句,但你最终会得到一个更清晰的回调。

  • 你有关于如何使用 IOHID API 的任何好的资源吗?(我主要对读取鼠标/键盘输入感兴趣。)只需按照 Apple 的文档创建 IOHIDManager 并查找匹配设备等相当简单,但如何解析发送到回调的数据则不太清楚。听起来您对 API 有一些经验,所以您知道有什么好的参考资料可以解决这些问题吗? (3认同)