I tried to determine (from within the event handler) which keyboard raised the event. I used these two posts:
In the second article, the author successfully separates his keyboards using Carbon technology, but an attempt to perform the same trick using Cocoa fails.
Both of them refused on my own system, perhaps because my wireless keyboard is also made of apple, so it may be reporting the same identifier as the built-in keyboard.
In the first article, someone offers a solution for observing keyboard events at a lower level (where it is possible to distinguish the keyboard) and storing this data in a queue, as well as receiving an event handler.
It looks a little hairy, so I'm just checking here to see if anyone has found the best.
Here is my code that demonstrates the inability to differentiate the keyboard in the event handler:
// 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; }