В OSX, как определить, какая клавиатура сгенерировала NSEvent?

Я пытался определить (из обработчика событий), какая клавиатура вызвала событие. Я использовал эти два поста:

Во второй статье автор успешно отделил свои клавиатуры с помощью техники Carbon, но попытка того же трюка с использованием Cocoa не удалась.

В моей собственной системе оба терпят неудачу, возможно, потому, что моя беспроводная клавиатура также произведена Apple, поэтому, возможно, сообщает тот же идентификатор, что и встроенная клавиатура.

В первой статье кто-то предлагает решение для наблюдения за событиями клавиатуры на более низком уровне (где можно различать клавиатуры) и сохранения этих данных в очереди, а обработчик событий извлекает информацию.

Это выглядит немного запутанно, поэтому я просто проверяю здесь, не нашел ли кто-нибудь что-нибудь получше.

Вот мой код, демонстрирующий невозможность различения клавиатур в обработчике событий:

// 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;
}

person P i    schedule 22.05.2015    source источник
comment
Вы когда-нибудь находили решение своего вопроса? Я рассматриваю возможность написания небольшого демона для автоматического переключения источника ввода на основе последней активной клавиатуры (см. -keyboards" title="osx автоматически меняет источник ввода для разных клавиатур">superuser.com/questions/1143990/), но, похоже, определить, какая клавиатура активирована последней, не так просто. Это должно быть возможно, так как раскладка клавиатуры в программе просмотра клавиатуры динамически меняется при наборе текста на той или иной клавиатуре...   -  person Jean    schedule 15.11.2016
comment
На это нет ответов?   -  person RuLoViC    schedule 29.01.2019
comment
@RuLoViC, возможно, вы захотите проверить только что полученный ответ.   -  person P i    schedule 29.05.2021


Ответы (1)


Вот схема того, что вы можете сделать:

Вы настраиваете IOHIDManager и устанавливаете словари соответствия ввода для соответствия клавиатуре.

Затем IOHIDManager предоставит вам ссылки на все подключенные клавиатуры как IOHIDDevices.

Затем, наконец, вы можете настроить обратные вызовы ввода для IOHIDDevices. Теперь у вас может быть отдельный обратный вызов ввода для каждого устройства!

Это немного громоздко в настройке и работе, и это не позволяет вам фильтровать/изменять события, как это сделал бы CGEventTap. Но это единственный известный мне метод мониторинга ввода, когда вы знаете, какое устройство вызвало какой ввод.


Вот некоторые отправные точки:

Документы IOHIDManager

IOHIDUsageTables.h и IOHIDDeviceKeys.h

Для клавиатур вы захотите объявить соответствие dict следующим образом

NSDictionary *matchDict = @{
    @(kIOHIDDeviceUsagePageKey): @(kHIDPage_GenericDesktop),
    @(kIOHIDDeviceUsageKey): @(kHIDUsage_GD_Keyboard),
};

(А затем преобразовать его в CFDictionaryRef с помощью бесплатного моста) (Не уверен, что это правильно - ничего из этого не тестировалось)

person Noah Nuebling    schedule 29.05.2021