【问题标题】:How to tap/hook keyboard events in OSX and record which keyboard fires each event如何在 OSX 中点击/挂钩键盘事件并记录哪个键盘触发每个事件
【发布时间】:2015-08-03 12:20:48
【问题描述】:

我现在发现了如何在 OS X 上以低级别挂钩/点击键盘事件:How to tap (hook) F7 through F12 and Power/Eject on a MacBook keyboard

从该答案中打印出代码:

// compile and run from the commandline with:
//    clang  -framework coreFoundation  -framework IOKit  ./HID.c  -o hid
//    sudo ./hid

// This code works with the IOHID library to get notified of keys.
//   Still haven't figured out how to truly intercept with
//   substitution.

#include <IOKit/hid/IOHIDValue.h>
#include <IOKit/hid/IOHIDManager.h>

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

    printf( "scancode: %d, pressed: %ld\n", scancode, pressed );
}


CFMutableDictionaryRef myCreateDeviceMatchingDictionary( UInt32 usagePage,  UInt32 usage )
{
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
                                                            kCFAllocatorDefault, 0
                                                        , & kCFTypeDictionaryKeyCallBacks
                                                        , & kCFTypeDictionaryValueCallBacks );
    if ( ! dict )
        return NULL;

    CFNumberRef pageNumberRef = CFNumberCreate( kCFAllocatorDefault, kCFNumberIntType, & usagePage );
    if ( ! pageNumberRef ) {
        CFRelease( dict );
        return NULL;
    }

    CFDictionarySetValue( dict, CFSTR(kIOHIDDeviceUsagePageKey), pageNumberRef );
    CFRelease( pageNumberRef );

    CFNumberRef usageNumberRef = CFNumberCreate( kCFAllocatorDefault, kCFNumberIntType, & usage );

    if ( ! usageNumberRef ) {
        CFRelease( dict );
        return NULL;
    }

    CFDictionarySetValue( dict, CFSTR(kIOHIDDeviceUsageKey), usageNumberRef );
    CFRelease( usageNumberRef );

    return dict;
}


int main(void)
{
    IOHIDManagerRef hidManager = IOHIDManagerCreate( kCFAllocatorDefault, kIOHIDOptionsTypeNone );

    CFArrayRef matches;
    {
        CFMutableDictionaryRef keyboard = myCreateDeviceMatchingDictionary( 0x01, 6 );
        CFMutableDictionaryRef keypad   = myCreateDeviceMatchingDictionary( 0x01, 7 );

        CFMutableDictionaryRef matchesList[] = { keyboard, keypad };

        matches = CFArrayCreate( kCFAllocatorDefault, (const void **)matchesList, 2, NULL );
    }

    IOHIDManagerSetDeviceMatchingMultiple( hidManager, matches );

    IOHIDManagerRegisterInputValueCallback( hidManager, myHIDKeyboardCallback, NULL );

    IOHIDManagerScheduleWithRunLoop( hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode );

    IOHIDManagerOpen( hidManager, kIOHIDOptionsTypeNone );

    CFRunLoopRun(); // spins
}

我如何(也许调整该代码)识别哪个键盘负责特定事件?

用例是我计划使用将重新映射的外部键盘,但同时保留我的内置 MacBook 键盘的原始映射。

编辑:
OSX HID Filter for Secondary Keyboard?
https://github.com/candera/khordr/blob/master/src/c/keygrab/hid-scratch.c
http://ianjoker.googlecode.com/svn/trunk/Joker/Joker/hid_test.cpp
http://www.cplusplusdevelop.com/72_17345226/
http://www.cocoabuilder.com/archive/cocoa/229902-which-keyboard-barcode-scanner-did-the-event-come-from.html

【问题讨论】:

    标签: macos hook keyboard-events tap iokit


    【解决方案1】:

    我一直在解决这个问题,终于找到了解决方案。 OP的代码是正确的,如果您想要键盘/pad的产品ID,请在myHIDKeyboardCallback()函数中添加行:

    void myHIDKeyboardCallback(void* context,  IOReturn result,  void* sender,  IOHIDValueRef value){
    
        IOHIDElementRef elem = IOHIDValueGetElement(value);
        if (IOHIDElementGetUsagePage(elem) != 0x07)
            return;
    
        IOHIDDeviceRef device = sender;
        int32_t pid = 1;
        CFNumberGetValue(IOHIDDeviceGetProperty(device, CFSTR("idProduct")), kCFNumberSInt32Type, &pid);
    
        uint32_t scancode = IOHIDElementGetUsage(elem);
    
        if (scancode < 4 || scancode > 231)
            return;
    
        long pressed = IOHIDValueGetIntegerValue(value);
    
        printf("scancode: %d, pressed: %ld, keyboardId=%d\n", scancode, pressed, pid);
    }
    

    正如@pmdj 所说你可以使用IOHIDDeviceRegisterInputValueCallback(),我遇到了麻烦,发现sender 参数提供了键盘产品ID。

    【讨论】:

      【解决方案2】:

      如果您使用IOHIDDeviceRegisterInputValueCallback 在每个感兴趣的设备上单独注册回调,则sender 参数将是一个IOHIDDeviceRef 指示设备。 (而不是使用IOHIDManagerRegisterInputValueCallback,其中发送者将是 HID 管理器引用)

      唯一的缺点是您需要注册并处理匹配设备的热插拔事件通知。 (新设备出现时注册,设备消失时注销)

      您可以使用 IOHIDDeviceCreate() 获取 HID 设备引用 - 这需要 io_service_t 作为参数。这反过来意味着您需要使用标准 IOKit IOService 匹配函数来获取和查看您的设备列表,但您确实获得了单个设备的明确列表,您可以查询名称以显示给用户等。关键函数是IOServiceAddMatchingNotification

      【讨论】:

      • 感谢您的接听。我应该考虑记录sender。刚刚用我的代码示例进行了尝试,它确实给出了不同的值,具体取决于我使用的键盘(1800442080 内置,1800440000 无线)。我可以枚举我的键盘以获取内置键盘的关联 ID 吗?
      • 请注意,您使用的是IOHIDManagerRegisterInputValueCallback,而我建议使用IOHIDDeviceRegisterInputValueCallback - 细微但重要的区别。我将使用有关 HID 设备枚举的更多详细信息来更新答案。
      • 您说,在我当前的 (IOHIDManagerRegisterInputValueCallback) 设置下,sender 将是 HID 管理器参考。但实际上每个键盘的报告不同,这似乎暗示了其他情况,因为只有一个经理。这让我想知道是否真的有必要为特定设备设置回调。
      • 奇怪。这里的文档:developer.apple.com/library/mac/documentation/DeviceDrivers/… 表示在这种情况下发送者应该是 HID 管理器。我猜想投它并尝试用它做一些有用的事情,看看会发生什么......
      • 是的,文档和实际行为之间存在矛盾!我希望文档是错误的,因为接收负责该事件的设备 ID 要有用得多。可以使用 inContext 参数传递 HID 管理器。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-06
      • 2013-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-23
      相关资源
      最近更新 更多