Macos 如何在OSX中点击/挂接键盘事件并记录触发每个事件的键盘

Macos 如何在OSX中点击/挂接键盘事件并记录触发每个事件的键盘,macos,hook,keyboard-events,tap,iokit,Macos,Hook,Keyboard Events,Tap,Iokit,我现在发现了如何在低级别上钩住/点击OS X上的键盘事件: 打印出答案中的代码: // 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. //

我现在发现了如何在低级别上钩住/点击OS X上的键盘事件:

打印出答案中的代码:

// 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
}
//使用以下命令行编译和运行:
//clang-frameworkcorefoundation-frameworkiokit./HID.c-o HID
//sudo./hid
//此代码与IOHID库一起工作,以获得密钥通知。
//还没有弄清楚如何真正用
//替代品。
#包括
#包括
void myHIDKeyboardCallback(void*上下文、IOReturn结果、void*发送方、IOHIDValueRef值)
{
IOHIDElementRef elem=IOHIDValueGetElement(值);
如果(IOHIDElementGetUsagePage(元素)!=0x07)
返回;
uint32\u t扫描代码=IOHIDElementGetUsage(elem);
如果(扫描码<4 | |扫描码>231)
返回;
长按=IOHIDValueGetIntegerValue(值);
printf(“扫描代码:%d,按下:%ld\n”,扫描代码,按下);
}
CFMutableDictionaryRef myCreateDeviceMatchingDictionary(UInt32用法页面,UInt32用法)
{
CFMutableDictionaryRef dict=CFDictionaryCreateMutable(
kCFAllocatorDefault,0
,&kCFTypeDictionaryKeyCallBacks
,&kCFTypeDictionaryValueCallBacks);
如果(!dict)
返回NULL;
CFNumberRef pageNumberRef=CFNumberCreate(kcfolocatordefault、kCFNumberIntType和usagePage);
如果(!pageNumberRef){
CFRelease(dict);
返回NULL;
}
CFDictionarySetValue(dict、CFSTR(kIOHIDDeviceUsagePageKey)、pageNumberRef);
CFRelease(页码ref);
CFNumberRef usageNumberRef=CFNumberCreate(kCFAllocatorDefault、kCFNumberIntType和用法);
如果(!usageNumberRef){
CFRelease(dict);
返回NULL;
}
CFDictionarySetValue(dict、CFSTR(kIOHIDDeviceUsageKey)、USAGunberRef);
CFRelease(USAGunberref);
返回命令;
}
内部主(空)
{
IOHIDManagerRef hidManager=IOHIDManagerCreate(kCFAllocatorDefault,kIOHIDOptionsTypeNone);
CFArrayRef匹配;
{
CFMutableDictionaryRef键盘=myCreateDeviceMatchingDictionary(0x01,6);
CFMutableDictionaryRef小键盘=myCreateDeviceMatchingDictionary(0x01,7);
cfmutableDictionaryRefMatchesList[]={键盘,小键盘};
matches=CFArrayCreate(kcfalocatordefault,(const void**)matchesList,2,NULL);
}
IOHIDManagerSetDeviceMatchingMultiple(hidManager,匹配项);
IOHIDManagerRegisterInputValueCallback(hidManager,myHIDKeyboardCallback,NULL);
IOHIDManagerScheduleWithRunLoop(hidManager,CFRunLoopGetMain(),kCFRunLoopDefaultMode);
IOHIDManagerOpen(hidManager,kIOHIDOptionsTypeNone);
CFRunLoopRun();//旋转
}
我如何(可能修改代码)识别哪个键盘负责某个特定事件

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

编辑:





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

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


您可以使用
IOHIDDeviceCreate()
获取HID设备引用-这将使用
io\u服务\u t
作为参数。这反过来意味着您需要使用标准的IOKit IOService匹配函数来获取和查看设备列表,但您确实可以获得单个设备的显式列表,您可以查询名称以显示给用户等等。这方面的关键功能是。

我一直在研究这个问题,最终找到了解决方案。OP的代码是正确的,如果您想要键盘/键盘的产品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);
}
void myHIDKeyboardCallback(void*上下文、IOReturn结果、void*发送方、IOHIDValueRef值){
IOHIDElementRef elem=IOHIDValueGetElement(值);
如果(IOHIDElementGetUsagePage(元素)!=0x07)
返回;
IOHIDDeviceRef设备=发送方;
int32_t pid=1;
cfNumberTargetValue(IOHIDDeviceGetProperty(设备、CFSTR(“idProduct”))、kCFNumberSInt32Type和pid);
uint32\u t扫描代码=IOHIDElementGetUsage(elem);
如果(扫描码<4 | |扫描码>231)
返回;
长按=IOHIDValueGetIntegerValue(值);
printf(“扫描代码:%d,按下:%ld,键盘ID=%d\n”,扫描代码,按下,pid);
}

正如@pmdj所说,您可以使用
IOHIDDeviceRegisterInputValueCallback()
,我在这方面遇到了问题,发现
sender
参数无论如何都提供了键盘产品id。

感谢您的帮助。我应该想到记录
发送者
。只是用我的代码示例尝试了一下,它确实给出了不同的值,这取决于我使用的键盘(内置1800442080,无线1800440000)。我是否可以以这样的方式枚举键盘,以获得内置键盘的相关ID?请注意,您使用的是
IOHIDManagerRegisterInputValueCallback
,而我的建议是-细微但重要的区别。我将用有关HID设备枚举的更多详细信息更新答案。您说,使用我当前的(
IOHIDManagerRegisterInputValueCallback
)设置,
sender
将作为HID管理器参考。但它实际上报告了不同的情况