使用 IOHIDManager 获取修饰符键事件

Posted

技术标签:

【中文标题】使用 IOHIDManager 获取修饰符键事件【英文标题】:Using IOHIDManager to Get Modifier Key Events 【发布时间】:2011-11-03 16:29:13 【问题描述】:

我正在尝试使用 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 使用页面,所以我不知道是使用带有键盘使用 ID (06) 的通用桌面页面 (0x01) 还是带有使用 ID 的键盘/键盘页面 (0x07)对于各个键。也许这与它有关?

【问题讨论】:

【参考方案1】:

我想通了。这样做的方法是使用通用桌面页面 (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);

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;

而 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 ...

请注意,每次按下或释放时似乎都会多次调用回调,但使用 ID 超出正常范围,这就是“if (scancode 231)”的用途。

【讨论】:

您在哪里找到有关如何解析回调中的value 以获取扫描码之类的信息的信息?你有一些(可读的)参考,或者你是怎么想出来的?【参考方案2】:

感谢您提供问题的答案。

您可以使用 IOHIDManagerSetInputValueMatching,而不是 myHIDKeyboardCallback 中检查扫描码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 语句更 LOC,但你最终会得到一个更清晰的回调。

【讨论】:

很高兴知道。我想您还可以使用 kIOHIDElementUsagePageKey 将元素使用页面限制为 0x07。不过,这有必要吗?键盘/小键盘设备是否会生成非 0x07 元素?我想如果你有一个带有内置触控板或操纵杆或其他东西的外部键盘,这是可能的。 我从来没有使用过这样的键盘(即带有操纵杆或类似的内置),但我希望第二个设备和键盘能够将它们的事件分开。但我不能确定。 你有关于如何使用 IOHID API 的好的资料吗? (我主要对阅读鼠标/键盘输入感兴趣。)只需按照 Apple 的文档创建 IOHIDManager 并查找匹配的设备等相当简单,但如何解析发送到回调的数据则不太清楚。听起来您对 API 有一定的经验,那么您知道有什么好的参考资料可以解决这些问题吗?

以上是关于使用 IOHIDManager 获取修饰符键事件的主要内容,如果未能解决你的问题,请参考以下文章

在 vim 中重新映射修饰符键

自定义 v-model 之修饰符

C 中的 Vigenere 代码因多字符键而崩溃

如何在 vis.js 中使用带有点击事件的修饰符?

『 再看.NET7』新的访问修饰符——file

IOC 控制反转Android 事件依赖注入 ( 事件三要素 | 修饰注解的注解 | 事件依赖注入步骤 )