This question follows from How to connect / remap an arbitrary keyboard event in OSX?
So far, I have been able to use modifier keys and most other keys using:
_eventTap = CGEventTapCreate( kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, CGEventMaskBit( kCGEventKeyDown ) | CGEventMaskBit( kCGEventFlagsChanged ) , (CGEventTapCallBack)_tapCallback, (__bridge void *)(self));
It is noteworthy that F3 correctly reports the key code (160) before taking action. that is, I can disable the action by returning a NULL event handler (and thus could not propagate the event).
However, F7-F12 and Eject / Power do not activate the callback.
If I add:
| CGEventMaskBit( NSSystemDefined )
... Now the remaining Fx DO keys trigger the callback (although Power / Eject still does not work), but I cannot access the keyCode method of the event.
An error message is displayed:
2015-05-21 12: 30: 02.044 tap_k [16532: 698660] NSSystemDefined: 0 2015-05-21 12: 30: 02.044 tap_k [16532: 698660] * Approval error in - [NCEvent keyCode], / SourceCache / AppKit / AppKit-1347.57 / AppKit.subproj / NSEvent.m: 2471 2015-05-21 12: 30: 02.045 tap_k [16532: 698660] * Application terminated due to an uncaught exception "NSInternalInconsistencyException", reason: 'Invalid message sent to the event "NSEvent: type = SysDefined loc = (882,687) time = 118943.3 flags = 0 win = 0x0 winNum = 0 ctxt = 0x0 subtype = 8 data1 = 2560 data2 = -1" '
So either:
(1) I need another way to retrieve a unique identifier from NSEvent, or
(2) I need to push / hook at a lower level.
Working with (1), I notice that NSEvent has the data1 property. Record in hexadecimal format gives:
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
.. When I started / launched F6 F7 F8 F9 F10 F11 F12.
(also the last value changes to 1 for repetitions).
So, I think that I could just eat events with these values and pass other NSSystemDefined events through.
And this still does not solve the problem of catching Eject / Power.
But is there a cleaner / better way?
If anyone is interested in playing, here is the full code:
// 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; }