macos - 在 OSX 中,如何确定哪个键盘生成了 NSEvent?

标签 macos keyboard-events iokit nsevent

我一直在尝试确定(从事件处理程序中)哪个键盘触发了事件。我一直在使用这两个帖子:

  • http://www.cocoabuilder.com/archive/cocoa/229902-which-keyboard-barcode-scanner-did-the-event-come-from.html
  • http://www.cocoabuilder.com/archive/cocoa/274586-getting-keyboard-type-carbon-vs-cg.html#274586

  • 在第二篇文章中,作者使用 Carbon 技术成功地分离了他的键盘,但是使用 Cocoa 尝试相同的技巧失败了。

    在我自己的系统上都失败了,可能是因为我的无线键盘也是​​苹果制造的,所以可能报告与内置键盘相同的标识符。

    在第一篇文章中,有人提出了在较低级别(可以区分键盘)观察键盘事件并将此数据存储在队列中,并让事件处理程序检索信息的解决方案。

    这看起来有点毛茸茸的,所以我只是在这里查看是否有人发现了更好的东西。

    这是我的代码,它演示了在事件处理程序中区分键盘的失败:
    // compile and run from the commandline with:
    //    clang -fobjc-arc -framework Cocoa -framework Carbon  ./tap_k.m  -o tap_k
    //    sudo ./tap_k
    
    #import <Foundation/Foundation.h>
    #import <AppKit/NSEvent.h>
    //#import <CarbonEventsCore.h>
    #include <Carbon/Carbon.h>
    
    
    typedef CFMachPortRef EventTap;
    
    // - - - - - - - - - - - - - - - - - - - - -
    
    @interface KeyChanger : NSObject
    {
    @private
        EventTap            _eventTap;
        CFRunLoopSourceRef  _runLoopSource;
        CGEventRef          _lastEvent;
    }
    @end
    
    // - - - - - - - - - - - - - - - - - - - - -
    
    CGEventRef _tapCallback(
                            CGEventTapProxy proxy,
                            CGEventType     type,
                            CGEventRef      event,
                            KeyChanger*     listener
                            );
    
    // - - - - - - - - - - - - - - - - - - - - -
    
    @implementation KeyChanger
    
    - (BOOL)tapEvents
    {
        if (!_eventTap) {
            NSLog(@"Initializing an event tap.");
    
            // kCGHeadInsertEventTap -- new event tap should be inserted before any pre-existing event taps at the same location,
            _eventTap = CGEventTapCreate( kCGHIDEventTap, // kCGSessionEventTap,
                                          kCGHeadInsertEventTap,
                                          kCGEventTapOptionDefault,
                                               CGEventMaskBit( kCGEventKeyDown )
                                             | CGEventMaskBit( kCGEventFlagsChanged )
                                             | CGEventMaskBit( NSSystemDefined )
                                             ,
                                          (CGEventTapCallBack)_tapCallback,
                                          (__bridge void *)(self));
            if (!_eventTap) {
                NSLog(@"unable to create event tap. must run as root or "
                        "add privlidges for assistive devices to this app.");
                return NO;
            }
        }
        CGEventTapEnable(_eventTap, TRUE);
    
        return [self isTapActive];
    }
    
    - (BOOL)isTapActive
    {
        return CGEventTapIsEnabled(_eventTap);
    }
    
    - (void)listen
    {
        if( ! _runLoopSource ) {
            if( _eventTap ) { // dont use [self tapActive]
                NSLog(@"Registering event tap as run loop source.");
                _runLoopSource = CFMachPortCreateRunLoopSource( kCFAllocatorDefault, _eventTap, 0 );
    
                // Add to the current run loop.
                CFRunLoopAddSource( CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes );
    
                CFRunLoopRun();
            }else{
                NSLog(@"No Event tap in place! You will need to call listen after tapEvents to get events.");
            }
        }
    }
    
    - (CGEventRef)processEvent:(CGEventRef)cgEvent
    {
        NSEvent* event = [NSEvent eventWithCGEvent:cgEvent];
    
    
        //NSEventType type = [event type];
    
        EventRef ce = (EventRef)[event eventRef];
    
        if(ce)
        {
            unsigned kbt;
            GetEventParameter(
                                ce,
                                kEventParamKeyboardType,
                                typeUInt32, NULL,
                                sizeof kbt, NULL,
                                & kbt
                                );
    
            NSLog(@"CARBON Keyboard type: %d",kbt);
        }
    
        CGEventSourceRef evSrc = CGEventCreateSourceFromEvent( cgEvent );
    
        if(evSrc)
        {
            unsigned kbt = (NSUInteger) CGEventSourceGetKeyboardType( evSrc );
            CFRelease(evSrc);
            NSLog(@"COCOA: %d",kbt);
        }
    
        //[super sendEvent:anEvent];
        //}
    
    
        NSUInteger modifiers = [event modifierFlags] &
            (NSCommandKeyMask | NSAlternateKeyMask | NSShiftKeyMask | NSControlKeyMask);
    
        enum {
           kVK_ANSI_3 = 0x14,
        };
    
    
        switch( event.type ) {
            case NSFlagsChanged:
                NSLog(@"NSFlagsChanged: %d", event.keyCode);
                break;
    
            case NSSystemDefined:
                NSLog(@"NSSystemDefined: %lx", event.data1);
                return NULL;
    
            case kCGEventKeyDown:
                NSLog(@"KeyDown: %d", event.keyCode);
                break;
    
            default:
                NSLog(@"WTF");
        }
    
    
        // TODO: add other cases and do proper handling of case
        if (
            //[event.characters caseInsensitiveCompare:@"3"] == NSOrderedSame
            event.keyCode == kVK_ANSI_3
            && modifiers == NSShiftKeyMask
            ) 
        {
            NSLog(@"Got SHIFT+3");
    
            event = [NSEvent keyEventWithType: event.type
                                     location: NSZeroPoint
                                modifierFlags: event.modifierFlags & ! NSShiftKeyMask
                                    timestamp: event.timestamp
                                 windowNumber: event.windowNumber
                                      context: event.context
                                   characters: @"#"
                  charactersIgnoringModifiers: @"#"
                                    isARepeat: event.isARepeat
                                      keyCode: event.keyCode];
        }
        _lastEvent = [event CGEvent];
        CFRetain(_lastEvent); // must retain the event. will be released by the system
        return _lastEvent;
    }
    
    - (void)dealloc
    {
        if( _runLoopSource ) {
            CFRunLoopRemoveSource( CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes );
            CFRelease( _runLoopSource );
        }
        if( _eventTap ) {
            //kill the event tap
            CGEventTapEnable( _eventTap, FALSE );
            CFRelease( _eventTap );
        }
    }
    
    @end
    
    // - - - - - - - - - - - - - - - - - - - - -
    
    CGEventRef _tapCallback(
                            CGEventTapProxy proxy,
                            CGEventType     type,
                            CGEventRef      event,
                            KeyChanger*     listener
                            )
    {
        //Do not make the NSEvent here.
        //NSEvent will throw an exception if we try to make an event from the tap timout type
        @autoreleasepool {
            if( type == kCGEventTapDisabledByTimeout ) {
                NSLog(@"event tap has timed out, re-enabling tap");
                [listener tapEvents];
                return nil;
            }
            if( type != kCGEventTapDisabledByUserInput ) {
                return [listener processEvent:event];
            }
        }
        return event;
    }
    
    // - - - - - - - - - - - - - - - - - - - - -
    
    int main(int argc, const char * argv[])
    {
        @autoreleasepool {
            KeyChanger* keyChanger = [KeyChanger new];
            [keyChanger tapEvents];
            [keyChanger listen];//blocking call.
        }
        return 0;
    }
    

    最佳答案

    以下是您可以执行的操作的概述:
    您设置 IOHIDManager 并设置输入匹配字典以匹配键盘。
    然后 IOHIDManager 将为您提供对所有连接键盘的引用作为 IOHIDDevices。
    最后,您可以为 IOHIDDevices 设置输入回调。
    现在您可以为每个设备设置单独的输入回调!
    设置和使用这有点麻烦,它不允许您像 CGEventTap 那样过滤/更改事件。但这是我所知道的唯一一种监控输入的方法,这样您就可以知道哪个设备导致了哪个输入。

    以下是一些起点:
    IOHIDManager 文档

  • https://developer.apple.com/documentation/iokit/iohidmanager_h

  • IOHIDUsageTables.h 和 IOHIDDeviceKeys.h
  • https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-421.6/IOHIDFamily/IOHIDUsageTables.h.auto.html
  • https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-1446.40.16/IOHIDFamily/IOHIDDeviceKeys.h.auto.html
  • 您将需要这些用于设备匹配的字典。

  • 对于键盘,您需要像这样声明匹配字典
    NSDictionary *matchDict = @{
        @(kIOHIDDeviceUsagePageKey): @(kHIDPage_GenericDesktop),
        @(kIOHIDDeviceUsageKey): @(kHIDUsage_GD_Keyboard),
    };
    
    (然后通过免费桥接将其转换为 CFDictionaryRef)
    (不确定是否正确 - 这些都没有经过测试)

    关于macos - 在 OSX 中,如何确定哪个键盘生成了 NSEvent?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30394429/

    相关文章:

    swift - 在 Cocoa OS X AVPlayer 中播放 HLS (m3u8) - Swift

    Xcode Server bot 无法使用默认命令读取属性列表

    javascript - 如何检测退出键的 KeyboardEvent?

    javascript - 使用键盘箭头选择和选择 div 元素并输入键?

    macos - 如何在 OS X 终端中逐字移动光标

    macos - 如何在 osx 上模拟 cp 和 mv --parent

    javascript - 如何用JS模拟真人键盘上的空格键

    usb - 如何设置 `com.apple.developer.driverkit.transport.usb`权限?

    c - IOHID : add trackpad to matching dictionary

    macos - 在 darwin 上查找 USB 设备的驱动程序