Modify NSEvent to send a different key than the one that was pressed

I’m trying to create an OS X keyboard hook for assistive technology purposes (i.e. don’t worry, not a keylogger).

When a user presses a key, I want to prevent the real keypress and send a fake keypress (character of my choosing) instead.

  • How to find Realm file location of a Mac App
  • CATransition push without fade
  • Conditional categories in Mountain Lion
  • Can I have a cross-platform framework for OS X and tvOS?
  • How to get notifications when the headphones are plugged in/out? Mac
  • How to create a CFDictionary in an OS X target?
  • I have the following code:

    - (void) hookTheKeyboard {
        CGEventMask keyboardMask = CGEventMaskBit(kCGEventKeyDown);
        id eventHandler = [NSEvent addGlobalMonitorForEventsMatchingMask:keyboardMask handler:^(NSEvent *keyboardEvent) {
            NSLog(@"keyDown: %c", [[keyboardEvent characters] characterAtIndex:0]);
            //Want to: Stop the keyboard input
            //Want to: Send another key input instead
        }];
    }
    

    Any help accomplishing either of those goals? Basically modifying the NSEvent “keyboardEvent” to send a different character. Thanks.

    2 Solutions Collect From Internet About “Modify NSEvent to send a different key than the one that was pressed”

    You can’t do this with the NSEvent API, but you can do this with a CGEventTap. You can create an active event tap and register a callback that receives a CGEventRef and can modify it (if necessary) and return it to modify the actual event stream.


    EDIT

    Here’s a simple program that, while running, replaces every “b” keystroke with a “v”:

    #import <Cocoa/Cocoa.h>
    
    CGEventRef myCGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
      //0x0b is the virtual keycode for "b"
      //0x09 is the virtual keycode for "v"
      if (CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode) == 0x0B) {
        CGEventSetIntegerValueField(event, kCGKeyboardEventKeycode, 0x09);
      }
    
      return event;
    }
    
    int main(int argc, char *argv[]) {
      NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
      CFRunLoopSourceRef runLoopSource;
    
      CFMachPortRef eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, kCGEventMaskForAllEvents, myCGEventCallback, NULL);
    
      if (!eventTap) {
        NSLog(@"Couldn't create event tap!");
        exit(1);
      }
    
      runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
    
      CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
    
      CGEventTapEnable(eventTap, true);
    
      CFRunLoopRun();
    
      CFRelease(eventTap);
      CFRelease(runLoopSource);
      [pool release];
    
      exit(0);
    }
    

    (Funny story: as I was editing this post, I kept on trying to write “replaces every ‘b’ keystroke”, but it kept on coming out as “replaces every ‘v’ keystroke”. I was confused. Then I remembered that I hadn’t stopped the app yet.)

    I happened across this answer, needing to do the same but only for events within my own application not global . There is a much simpler solution, for this much simpler problem, which I am noting here incase it’s useful for anyone else:

    • I intercepted the event at the window, by creating an override for sendEvent:. I then check for key events (KeyUp or KeyDown) and then simply create a new event using nearly all the data from the prevous event, then call NSWindow superclass with this event instead.

    This seems to work perfectly for me and I didn’t have to even modify the keyCode part – but maybe this could be an issue…

    Example in Swift:

    class KeyInterceptorWindow : NSWindow {
    
    
        override func sendEvent(theEvent: NSEvent) {
    
            if theEvent.type == .KeyDown || theEvent.type == .KeyUp {
                println(theEvent.description)
                let newEvent = NSEvent.keyEventWithType(theEvent.type, 
                    location: theEvent.locationInWindow, 
                    modifierFlags: theEvent.modifierFlags, 
                    timestamp: theEvent.timestamp, 
                    windowNumber: theEvent.windowNumber, 
                    context: theEvent.context, 
                    characters: "H", 
                    charactersIgnoringModifiers: theEvent.charactersIgnoringModifiers!, 
                    isARepeat: theEvent.ARepeat, 
                    keyCode: theEvent.keyCode)
            super.sendEvent(newEvent!)
        } else {
            super.sendEvent(theEvent)
        }
    }
    

    }