如何在OSX中截取/挂钩键盘事件并记录每个事件由哪个键盘触发

7
我现在已经发现如何在OS X上以低层次挂钩/捕获键盘事件:如何在MacBook键盘上挂钩(hook)F7至F12和电源/弹出 从该答案中打印出代码:
// 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

2个回答

2

我在解决这个问题时,终于找到了解决方案。如果你想获取键盘/触控板的产品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()。我曾遇到这个问题并发现提供键盘产品ID的 sender 参数是有用的。


1
如果你使用IOHIDDeviceRegisterInputValueCallback在每个感兴趣的设备上注册回调函数,那么sender参数将是一个IOHIDDeviceRef,表示该设备。(而不是使用IOHIDManagerRegisterInputValueCallback,其中sender将是HID管理器引用)。
唯一的缺点是,你需要注册并处理匹配设备的热插拔事件的通知。(当新设备出现时进行注册,并在设备消失时进行注销)
你可以使用IOHIDDeviceCreate()获取HID设备引用-它需要一个io_service_t作为参数。这又意味着你需要使用标准的IOKit IOService匹配函数来获取和监视设备列表,但你确实会得到一个明确的单个设备列表,你可以查询名称以向用户显示等信息。这个关键函数是IOServiceAddMatchingNotification

感谢接收。我应该想到记录“sender” (发送器)。我刚刚用我的代码示例尝试了一下,它确实会根据我使用的键盘(1800442080 内置,1800440000 无线)给出不同的值。我能否以这样的方式列举我的键盘,以便我可以获取内置键盘的关联 ID? - P i
1
请注意,您正在使用 IOHIDManagerRegisterInputValueCallback,而我建议使用 IOHIDDeviceRegisterInputValueCallback - 这是微妙但重要的区别。我将在答案中更新有关HID设备枚举的更多详细信息。 - pmdj
你说根据我的当前(IOHIDManagerRegisterInputValueCallback)设置,sender将是HID管理器的引用。但实际上,每个键盘的报告都不一样,这似乎暗示了不同的情况,因为只有一个管理器。这让我怀疑是否真的需要为特定设备设置回调函数。 - P i
1
奇怪。这里的文档:https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/HID/new_api_10_5/tn2187.html 表明在这种情况下发送者应该是HID管理器。我猜测将其转换并尝试对其进行一些有用的操作,看看会发生什么... - pmdj
是的,文档和实际行为之间存在矛盾!我希望文档有误,因为接收事件负责的设备ID更加有用。HID管理器可能可以通过使用“inContext”参数传递。 - P i

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