17

I need to simulate keystrokes in OSX. Here's how I do it:

-(void)execute {
    CGEventSourceRef sourceRef =
    CGEventSourceCreate(kCGEventSourceStateHIDSystemState);

    CGEventRef keyPress = CGEventCreateKeyboardEvent (sourceRef, (CGKeyCode)keyCode, true);
    CGEventRef keyUnpress = CGEventCreateKeyboardEvent (sourceRef, (CGKeyCode)keyCode, false);

    CGEventSetFlags(keyPress, modifierFlags);
    CGEventPost(kCGHIDEventTap, keyPress);

    //unpressing the acualkey
    CGEventPost(kCGHIDEventTap, keyUnpress);

    CFRelease(keyPress);
    CFRelease(keyUnpress);
    CFRelease(sourceRef);
}

It works fine for every hotkey or simple keystrokes in any app, but doesn't work for system wide shortcuts, for example option + space to launch Spotlight or cmd + shift + 4 to make a screenshot or ctrl + ` to open iTerm2 window.

I tried to change event's source and the location at which to post event, doesn't help. Any ideas?

Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
Max Al Farakh
  • 4,386
  • 4
  • 29
  • 56

4 Answers4

19

From the documentation for CGEventCreateKeyboardEvent:

All keystrokes needed to generate a character must be entered, including modifier keys. For example, to produce a 'Z', the SHIFT key must be down, the 'z' key must go down, and then the SHIFT and 'z' key must be released:

So, you can't just press and release space with the option modifier to trigger an option-space; you have to press option, press space, release space, release option.

As a side note, opt-space doesn't do anything by default; cmd-space is the Spotlight search hotkey, and cmd-opt-space is the Spotlight window hotkey.

So, this code will pop up the Spotlight search:

- (void)execute {
  CGEventSourceRef src = 
    CGEventSourceCreate(kCGEventSourceStateHIDSystemState);

  CGEventRef cmdd = CGEventCreateKeyboardEvent(src, 0x38, true);
  CGEventRef cmdu = CGEventCreateKeyboardEvent(src, 0x38, false);
  CGEventRef spcd = CGEventCreateKeyboardEvent(src, 0x31, true);
  CGEventRef spcu = CGEventCreateKeyboardEvent(src, 0x31, false);

  CGEventSetFlags(spcd, kCGEventFlagMaskCommand);
  CGEventSetFlags(spcu, kCGEventFlagMaskCommand);

  CGEventTapLocation loc = kCGHIDEventTap; // kCGSessionEventTap also works
  CGEventPost(loc, cmdd);
  CGEventPost(loc, spcd);
  CGEventPost(loc, spcu);
  CGEventPost(loc, cmdu);

  CFRelease(cmdd);
  CFRelease(cmdu);
  CFRelease(spcd);
  CFRelease(spcu);
  CFRelease(src);  
}
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • It works, thanks. I used similar code at first, but it didn't work for some reason. – Max Al Farakh May 25 '12 at 09:42
  • P.S: On non us Macs cmd+space is used to change keyboard layout by default. – Max Al Farakh May 25 '12 at 09:43
  • There are a lot of little oddities about posting keyboard events, and sometimes you have to do a bit of trial and error to get something that works the way you want. In this particular case, the documentation specifically addresses what you were doing wrong, but that's actually pretty rare… – abarnert May 25 '12 at 18:49
  • 1
    Non-US keyboards do not (all) use cmd+space to change keyboard layout (any longer). I'm getting spotlight. – d00dle Jul 09 '13 at 18:34
  • You *are* able to use CGEventSetFlags() to set the modifier flags on a particular event, without having to create actual up/down key events for those modifier keys. Not sure why the docs say "all keystrokes" are needed. – pkamb Nov 21 '15 at 04:23
  • 1
    @pkamb I'm 90% sure that doing that will enter the right character in a text box, but won't do things like trigger cmd-space hot keys, which is why the docs say all keystrokes are needed. – abarnert Dec 09 '15 at 10:07
  • 1
    I don't know why but I need to place usleep(10000); between key down, and key up event else it doesn't do anything! – Michał Ziobro Aug 27 '16 at 12:45
  • Yeah it is the problem that test program is finishing too fast, so the best is to sleep a little at the end of testing this keyboard events. It also worth to note that just sending one key down event, and one key up event suffice, I think that sending key events for modifiers is redundant. In typical usage scenario when you implement virtual keyboard it will be called separately each times user select new keys – Michał Ziobro Aug 27 '16 at 17:32
11

For anyone else wanting a CGKeyCode list here is a function from the RUI project with a partial table.

Although this might be a more complete example. Anyone know of a more complete map?

int keyCodeForKeyString(char * keyString);  // get the Mac keycode for the RUI representation

int keyCodeForKeyString(char * keyString)
{
    if (strcmp(keyString, "a") == 0) return 0;
    if (strcmp(keyString, "s") == 0) return 1;
    if (strcmp(keyString, "d") == 0) return 2;
    if (strcmp(keyString, "f") == 0) return 3;
    if (strcmp(keyString, "h") == 0) return 4;
    if (strcmp(keyString, "g") == 0) return 5;
    if (strcmp(keyString, "z") == 0) return 6;
    if (strcmp(keyString, "x") == 0) return 7;
    if (strcmp(keyString, "c") == 0) return 8;
    if (strcmp(keyString, "v") == 0) return 9;
    // what is 10?
    if (strcmp(keyString, "b") == 0) return 11;
    if (strcmp(keyString, "q") == 0) return 12;
    if (strcmp(keyString, "w") == 0) return 13;
    if (strcmp(keyString, "e") == 0) return 14;
    if (strcmp(keyString, "r") == 0) return 15;
    if (strcmp(keyString, "y") == 0) return 16;
    if (strcmp(keyString, "t") == 0) return 17;
    if (strcmp(keyString, "1") == 0) return 18;
    if (strcmp(keyString, "2") == 0) return 19;
    if (strcmp(keyString, "3") == 0) return 20;
    if (strcmp(keyString, "4") == 0) return 21;
    if (strcmp(keyString, "6") == 0) return 22;
    if (strcmp(keyString, "5") == 0) return 23;
    if (strcmp(keyString, "=") == 0) return 24;
    if (strcmp(keyString, "9") == 0) return 25;
    if (strcmp(keyString, "7") == 0) return 26;
    if (strcmp(keyString, "-") == 0) return 27;
    if (strcmp(keyString, "8") == 0) return 28;
    if (strcmp(keyString, "0") == 0) return 29;
    if (strcmp(keyString, "]") == 0) return 30;
    if (strcmp(keyString, "o") == 0) return 31;
    if (strcmp(keyString, "u") == 0) return 32;
    if (strcmp(keyString, "[") == 0) return 33;
    if (strcmp(keyString, "i") == 0) return 34;
    if (strcmp(keyString, "p") == 0) return 35;
    if (strcmp(keyString, "RETURN") == 0) return 36;
    if (strcmp(keyString, "l") == 0) return 37;
    if (strcmp(keyString, "j") == 0) return 38;
    if (strcmp(keyString, "'") == 0) return 39;
    if (strcmp(keyString, "k") == 0) return 40;
    if (strcmp(keyString, ";") == 0) return 41;
    if (strcmp(keyString, "\\") == 0) return 42;
    if (strcmp(keyString, ",") == 0) return 43;
    if (strcmp(keyString, "/") == 0) return 44;
    if (strcmp(keyString, "n") == 0) return 45;
    if (strcmp(keyString, "m") == 0) return 46;
    if (strcmp(keyString, ".") == 0) return 47;
    if (strcmp(keyString, "TAB") == 0) return 48;
    if (strcmp(keyString, "SPACE") == 0) return 49;
    if (strcmp(keyString, "`") == 0) return 50;
    if (strcmp(keyString, "DELETE") == 0) return 51;
    if (strcmp(keyString, "ENTER") == 0) return 52;
    if (strcmp(keyString, "ESCAPE") == 0) return 53;

    // some more missing codes abound, reserved I presume, but it would
    // have been helpful for Apple to have a document with them all listed

    if (strcmp(keyString, ".") == 0) return 65;

    if (strcmp(keyString, "*") == 0) return 67;

    if (strcmp(keyString, "+") == 0) return 69;

    if (strcmp(keyString, "CLEAR") == 0) return 71;

    if (strcmp(keyString, "/") == 0) return 75;
    if (strcmp(keyString, "ENTER") == 0) return 76;  // numberpad on full kbd

    if (strcmp(keyString, "=") == 0) return 78;

    if (strcmp(keyString, "=") == 0) return 81;
    if (strcmp(keyString, "0") == 0) return 82;
    if (strcmp(keyString, "1") == 0) return 83;
    if (strcmp(keyString, "2") == 0) return 84;
    if (strcmp(keyString, "3") == 0) return 85;
    if (strcmp(keyString, "4") == 0) return 86;
    if (strcmp(keyString, "5") == 0) return 87;
    if (strcmp(keyString, "6") == 0) return 88;
    if (strcmp(keyString, "7") == 0) return 89;

    if (strcmp(keyString, "8") == 0) return 91;
    if (strcmp(keyString, "9") == 0) return 92;

    if (strcmp(keyString, "F5") == 0) return 96;
    if (strcmp(keyString, "F6") == 0) return 97;
    if (strcmp(keyString, "F7") == 0) return 98;
    if (strcmp(keyString, "F3") == 0) return 99;
    if (strcmp(keyString, "F8") == 0) return 100;
    if (strcmp(keyString, "F9") == 0) return 101;

    if (strcmp(keyString, "F11") == 0) return 103;

    if (strcmp(keyString, "F13") == 0) return 105;

    if (strcmp(keyString, "F14") == 0) return 107;

    if (strcmp(keyString, "F10") == 0) return 109;

    if (strcmp(keyString, "F12") == 0) return 111;

    if (strcmp(keyString, "F15") == 0) return 113;
    if (strcmp(keyString, "HELP") == 0) return 114;
    if (strcmp(keyString, "HOME") == 0) return 115;
    if (strcmp(keyString, "PGUP") == 0) return 116;
    if (strcmp(keyString, "DELETE") == 0) return 117;
    if (strcmp(keyString, "F4") == 0) return 118;
    if (strcmp(keyString, "END") == 0) return 119;
    if (strcmp(keyString, "F2") == 0) return 120;
    if (strcmp(keyString, "PGDN") == 0) return 121;
    if (strcmp(keyString, "F1") == 0) return 122;
    if (strcmp(keyString, "LEFT") == 0) return 123;
    if (strcmp(keyString, "RIGHT") == 0) return 124;
    if (strcmp(keyString, "DOWN") == 0) return 125;
    if (strcmp(keyString, "UP") == 0) return 126;

    fprintf(stderr, "keyString %s Not Found. Aborting...\n", keyString);
    exit(EXIT_FAILURE);
}
Gourneau
  • 12,660
  • 8
  • 42
  • 42
  • 1
    Isn't it keyboard layout specific? Like just US Keyboard and If you use this on for instance German keyboard layout set in OS X than it will be not correct? – Michał Ziobro Aug 27 '16 at 12:44
  • There are few keys missing from this table, like "CAPSLOCK", found the missing ones here: https://gist.github.com/swillits/df648e87016772c7f7e5dbed2b345066 – Starwave Mar 13 '20 at 12:52
  • 1
    This is keyboard layout specific, which can be a lot tricky when you try to call shortcuts.... If you want, you can hack your way around with changing `NSTextInputContext` to desired layout and then set it back, it will work, but it's ugly hack... – Dominik Bucher Apr 22 '20 at 16:23
6

For the record, while @abarnert's answer is great and that's the way you should do it according to documentation, my original code also works. I found out that I had a different problem not relevant to this question.

So, if you need to apply modifier keys to the keypress, you can simply add them like this CGEventSetFlags(keyPress, modifierFlags); without pressing an depressing every modifier key separately. That approach works, and I haven't found any drawbacks yet and readability of the code is way better.

Max Al Farakh
  • 4,386
  • 4
  • 29
  • 56
0

XCode 7.3 and Swift 2.2:

let event1 = CGEventCreateKeyboardEvent(nil, 0x09, true); // cmd-v down
CGEventSetFlags(event1, CGEventFlags.MaskCommand);
CGEventPost(CGEventTapLocation.CGHIDEventTap, event1);

let event2 = CGEventCreateKeyboardEvent(nil, 0x09, false); // cmd-v up
CGEventSetFlags(event2, CGEventFlags.MaskCommand);
CGEventPost(CGEventTapLocation.CGHIDEventTap, event2);

Code above simulates CMD-V pressed then released(AKA: paste).

Tyler Liu
  • 19,552
  • 11
  • 100
  • 84
  • Note that there is a Swift3 example [http://stackoverflow.com/questions/27484330/simulate-keypress-using-swift](http://stackoverflow.com/questions/27484330/simulate-keypress-using-swift) – Fred Appelman Jan 22 '17 at 09:53