14

I have a very simple chunk of code that is designed to simulate keyboard events. The simple example below should type "Cz" - the shift key goes down, the c key goes down, c comes up and shift comes up. Then the z key goes down and up.

It seems that sometimes the order gets muddled though. When I create a timer to call this routine every second, the output should be CzCzCzCz.... But here's what I get:

CZcZCZCzczCzczCzczCZCZCzCz

I'll run it again:

CzCzCzCzCZCzCZCzCZCzCZCzCZCzCzCz

Different. And equally wrong.

The code:

e1 = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)56, true);
CGEventPost(kCGSessionEventTap, e1);
CFRelease(e1);
e2 = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)8, true);
CGEventPost(kCGSessionEventTap, e2);
CFRelease(e2);
e3 = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)8, false);
CGEventPost(kCGSessionEventTap, e3);
CFRelease(e3);
e4 = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)56, false);
CGEventPost(kCGSessionEventTap, e4);
CFRelease(e4);

e7 = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)6, true);
CGEventPost(kCGSessionEventTap, e7);
CFRelease(e7);
e8 = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)6, false);
CGEventPost(kCGSessionEventTap, e8);
CFRelease(e8);

Is there something I'm missing in how to implement the keydown and keyup for the shift key? I think this might be a bug - where would I report it?

erickson
  • 265,237
  • 58
  • 395
  • 493
Ben Packard
  • 26,102
  • 25
  • 102
  • 183

6 Answers6

25

I have found a reliable way to post modified keyboard events - it does not follow the example in Apple's documentation (which doesn't work) but seems to make sense, and most importantly, WORKS.

Rather than sending 'shift key down' and 'shift key up' messages (as instructed in the docs), you need to set a modifier flag on the keypress. Here's how to output an uppercase Z.

CGEventRef event1, event2;
event1 = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)6, true);//'z' keydown event
CGEventSetFlags(event1, kCGEventFlagMaskShift);//set shift key down for above event
CGEventPost(kCGSessionEventTap, event1);//post event

I'm then releasing the 'z' key for completeness (also setting the shift-flag on, though not sure if this is correct).

event2 = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)6, false);
CGEventSetFlags(event2, kCGEventFlagMaskShift);
CGEventPost(kCGSessionEventTap, event2);

Finally (and bizarrely) you DO need to send the 'key up' event for the shift key:

  e5 = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)56, false);
CGEventPost(kCGSessionEventTap, e5);

Don't forget to release your events once you're done with them.

I hope this is useful to someone - it took me a long time to get this to work.

Ben Packard
  • 26,102
  • 25
  • 102
  • 183
  • I gave a minus 1... this doesn't work. It works to type a capital letter, but follow that capital letter up with a small letter and the capital letter types again. Tried it a million ways and no go. – regulus6633 Jul 31 '10 at 21:30
  • I just can't understand why Apple gives the `CGEventCreateKeyboardEvent(NULL, (CGKeyCode)56, true)` code snippet in their docs, instead of using `CGEventSetFlags(event, kCGEventFlagMaskShift)`... – Kyone Mar 31 '11 at 16:04
  • 1
    Oh and btw I had to use both this and kCGEventSourceStateHIDSystemState in CGEventSourceCreate to it to work reliably. – Andrey Tarantsov Jul 25 '11 at 01:01
  • There is nothing bizarre about having to send the key up for shift since you set it the previous event's flags, you need to combine it with the session's flags instead. – valexa Nov 20 '11 at 15:20
10

The cleanest way for this is bitwise OR'ing the current modifier flags with the flag of your desired modifier(s) , e.g.:

CGEventFlags flags = kCGEventFlagMaskShift;
CGEventRef ev;
CGEventSourceRef source = CGEventSourceCreate (kCGEventSourceStateCombinedSessionState);

//press down            
ev = CGEventCreateKeyboardEvent (source, keyCode, true);    
CGEventSetFlags(ev,flags | CGEventGetFlags(ev)); //combine flags                        
CGEventPost(kCGHIDEventTap,ev);
CFRelease(ev);              

//press up                                  
ev = CGEventCreateKeyboardEvent (source, keyCode, false);                       
CGEventSetFlags(ev,flags | CGEventGetFlags(ev)); //combine flags                        
CGEventPost(kCGHIDEventTap,ev); 
CFRelease(ev);              

CFRelease(source);
valexa
  • 4,462
  • 32
  • 48
4

Like another comment referring to other solutions here, after using uppercase with the Shift mask, a successive call would cause any further intended non-shifted character to be turned into an shifted characters. I figured the call to CGEventCreateKeyboardEvent was somehow saving previous masks, so I purposefully clear the mask of the modifier:

CGEventRef event1, event2;

CGEventSourceRef source = CGEventSourceCreate (kCGEventSourceStateCombinedSessionState);
CGEventFlags flags;

event1 = CGEventCreateKeyboardEvent (source, keyCode, true);
if (upper)
    flags = kCGEventFlagMaskShift | CGEventGetFlags(event1);
else
    flags = ~kCGEventFlagMaskShift & CGEventGetFlags(event1);

CGEventSetFlags(event1, flags);
CGEventPost(kCGHIDEventTap,event1);

event2 = CGEventCreateKeyboardEvent (source, keyCode, false);
if (upper)
    flags = kCGEventFlagMaskShift | CGEventGetFlags(event2);
else
    flags = ~kCGEventFlagMaskShift & CGEventGetFlags(event2);
CGEventSetFlags(event2, flags);
CGEventPost(kCGHIDEventTap,event2);
CFRelease(event1);
CFRelease(event2);
CFRelease(source);
Tyler
  • 165
  • 1
  • 7
3

I had a similar problem recently. Instead of passing NULL as the first argument of CGEventCreateKeyboardEvent, create an event source like this:

CGEventSourceRef eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);

and use it while you are creating and posting events. CFRelease it when you are done with it.

Scott K.
  • 1,761
  • 15
  • 26
2

Any relation to this guy's bug?

  • YES! Wish I could get in touch with him. – Ben Packard Jan 05 '10 at 19:46
  • Why get in touch with him? His solution is right there in that message. – Peter Hosey Jan 05 '10 at 20:09
  • Well he has a decent workaround, but I want to know if this is a genuine bug or I (and he) were doing something wrong. – Ben Packard Jan 06 '10 at 17:48
  • No, he doesn't just have a workaround. He explained in his message exactly was wrong in his app. – Peter Hosey Jan 07 '10 at 07:40
  • Ok, that's not how I understand it though. To create a MPQueue, event taps and stick user info on to events to get expected behaviour is, to me, a workaround rather than a problem with his App. In fact Apple have confirmed this is a bug. – Ben Packard Jan 17 '10 at 01:23
  • PS - I think perhaps you only read his first follow up - he swiftly replied, retracting his App problem and restated his problem. Then followed a third time with the workaround I mentioned. Here is the retraction: http://lists.apple.com/archives/quartz-dev/2009/Oct/msg00086.html And here is the restatement: http://lists.apple.com/archives/quartz-dev/2009/Oct/msg00087.html – Ben Packard Jan 17 '10 at 01:32
  • @BenPackard hi Ben, I realize this was about 2 years ago, did you find a way to reliably post keyboard events in order? I need to do the same, I tried to make sense of the MPQueue post but I'm fairly new to Objective C/OSX programming. Could you provide me a code example? EDIT: I'm currently using your own answer above, but if I try to PASTE (by storing a string into the pasteboard then posting a cmd+v event) and then click UP by posting an UP arrow key event, it clicks up first then pastes (I assume due to a delay in storing the string in the pasteboard, as it's asyncronous the UP is faster – tsdexter Apr 11 '12 at 21:01
  • @txdexter - unfortunately I can't really provide anything more than in my answer below (the one marked with the checkmark) - that's all the code I needed to use. It seems like a few people have had different outcomes with other configurations elsewhere on this page, so experiment with them all. If I ever get back under the hood on that project though, I will let you know. – Ben Packard Apr 12 '12 at 00:41
0

reliable workaround:

+(void)runScript:(NSString*)scriptText
{
    NSDictionary *error = nil;
    NSAppleEventDescriptor *appleEventDescriptor;
    NSAppleScript *appleScript; 
    appleScript = [[NSAppleScript alloc] initWithSource:scriptText];
    appleEventDescriptor = [appleScript executeAndReturnError:&error];
}
[self runScript:@"tell application \"System Events\" to key code "c" using shift down"];
[self runScript:@"tell application \"System Events\" to key code "z"];
Anno2001
  • 1,343
  • 12
  • 17