Simulate keypress for system wide hotkeys

11,889

Solution 1

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);  
}

Solution 2

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);
}

Solution 3

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.

Share:
11,889

Related videos on Youtube

Max Al Farakh
Author by

Max Al Farakh

Co-founder of Jitbit, we build the best helpdesk ticketing system on the market and other apps that make people lifes easier.

Updated on September 19, 2022

Comments

  • Max Al Farakh
    Max Al Farakh about 1 year

    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?

  • Max Al Farakh
    Max Al Farakh over 11 years
    It works, thanks. I used similar code at first, but it didn't work for some reason.
  • Max Al Farakh
    Max Al Farakh over 11 years
    P.S: On non us Macs cmd+space is used to change keyboard layout by default.
  • abarnert
    abarnert over 11 years
    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…
  • d00dle
    d00dle over 10 years
    Non-US keyboards do not (all) use cmd+space to change keyboard layout (any longer). I'm getting spotlight.
  • pkamb
    pkamb almost 8 years
    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.
  • abarnert
    abarnert almost 8 years
    @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.
  • Michał Ziobro
    Michał Ziobro about 7 years
    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
    Michał Ziobro about 7 years
    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
    Michał Ziobro about 7 years
    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
  • Fred Appelman
    Fred Appelman almost 7 years
  • Starwave
    Starwave over 3 years
    There are few keys missing from this table, like "CAPSLOCK", found the missing ones here: gist.github.com/swillits/df648e87016772c7f7e5dbed2b345066
  • Dominik Bucher
    Dominik Bucher over 3 years
    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...