Cómo tocar (enganchar) F7 a F12 y Power / Eject en un teclado MacBook

Esta pregunta se sigue de¿Cómo conectar / reasignar un evento de teclado arbitrario en OSX?

Hasta ahora puedo tocar las teclas modificadoras y la mayoría de las otras teclas usando:

    _eventTap = CGEventTapCreate( kCGHIDEventTap, 
                                  kCGHeadInsertEventTap,
                                  kCGEventTapOptionDefault,
                                       CGEventMaskBit( kCGEventKeyDown )
                                     | CGEventMaskBit( kCGEventFlagsChanged )
                                     ,
                                  (CGEventTapCallBack)_tapCallback,
                                  (__bridge void *)(self));

Notablemente,F3 informa correctamente un código clave (160)antes de tomando acción. es decir, puedo desactivar la acción haciendo que mi controlador de eventos devuelva NULL (y, por lo tanto, no pueda propagar el evento).

Sin embargo, F7 a F12 y Eject / Power no activan la devolución de llamada.

Si agrego:

                                     | CGEventMaskBit( NSSystemDefined )

... Ahora las teclas Fx restantes SÍ activan la devolución de llamada (aunque Power / Eject todavía no lo hace), pero no puedo acceder al método keyCode del evento.

Produce un error:

2015-05-21 12: 30: 02.044 tap_k [16532: 698660] NSSystemDefined: 0 2015-05-21 12: 30: 02.044 tap_k [16532: 698660]* Error de aserción en - [NSEvent keyCode], /SourceCache/AppKit/AppKit-1347.57/AppKit.subproj/NSEvent.m:2471 2015-05-21 12: 30: 02.045 tap_k [16532: 698660] * Aplicación finalizada debido a la excepción no detectada 'NSInternalInconsistencyException', razón: 'Mensaje no válido enviado al evento "NSEvent: type = SysDefined loc = (882,687) time = 118943.3 flags = 0 win = 0x0 winNum = 0 ctxt = 0x0 subtipo = 8 data1 = 2560 data2 = -1 "'

Entonces:
(1) Necesito alguna otra forma de extraer algún identificador único de NSEvent, o
(2) Necesito tocar / enganchar a un nivel inferior.

Al trabajar con (1), noto que NSEvent tiene una propiedad data1. Registrar esto es en hexadecimal da:

2015-05-21 12:40:05.428 tap_k[16576:704298] NSSystemDefined: 140b00
2015-05-21 12:40:06.914 tap_k[16576:704298] NSSystemDefined: 100a00
2015-05-21 12:40:06.992 tap_k[16576:704298] NSSystemDefined: 100b00
2015-05-21 12:40:07.600 tap_k[16576:704298] NSSystemDefined: 130a00
2015-05-21 12:40:07.690 tap_k[16576:704298] NSSystemDefined: 130b00
2015-05-21 12:40:08.219 tap_k[16576:704298] NSSystemDefined: 70a00
2015-05-21 12:40:08.277 tap_k[16576:704298] NSSystemDefined: 70b00
2015-05-21 12:40:09.062 tap_k[16576:704298] NSSystemDefined: 10a00
2015-05-21 12:40:09.186 tap_k[16576:704298] NSSystemDefined: 10b00
2015-05-21 12:40:09.637 tap_k[16576:704298] NSSystemDefined: a00
2015-05-21 12:40:09.726 tap_k[16576:704298] NSSystemDefined: b00

.. Cuando presiono la tecla / tecla F6 F7 F8 F9 F10 F11 F12.

(también, el último valor cambia a 1 para repeticiones).

Así que supongo que podría comer eventos con estos valores y pasar otros eventos definidos por NSSystem.

Y todavía no resuelve el problema de atrapar Eject / Power.

¿Pero hay una forma más limpia / mejor?

Si alguien está interesado en jugar, aquí está el código completo:

// compile and run from the commandline with:
//    clang -fobjc-arc -framework Cocoa  ./foo.m  -o foo
//    sudo ./foo

#import <Foundation/Foundation.h>
#import <AppKit/NSEvent.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]
            _runLoopSource = CFMachPortCreateRunLoopSource( kCFAllocatorDefault,
                                                            _eventTap, 0);
            // Add to the current run loop.
            CFRunLoopAddSource( CFRunLoopGetCurrent(), _runLoopSource,
                                kCFRunLoopCommonModes);

            NSLog(@"Registering event tap as run loop source.");
            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];

    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: %x", 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;
}

Respuestas a la pregunta(2)

Su respuesta a la pregunta