I have the following code to send virtual keypresses to a process given its pid
NSRunningApplication* app = [NSRunningApplication
runningApplicationWithProcessIdentifier: pid];
[app activateWithOptions: (NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
event1 = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)cg_key_code, true);
event2 = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)cg_key_code, false);
CGEventPost(kCGHIDEventTap, event1);
CGEventPost(kCGHIDEventTap, event2);
The process that I wish to send keypresses to promptly comes to the front, as expected. But the problem is, the first keypress is going to the application which was at front before my application came front. When tested, [app isActive]
returns false for the first time. After the first key, everything goes fine.
Why is this happening? Even though I am posting the key event after getting my process to the front.
I have recently asked a similar question at: Inject keyboard event into NSRunningApplication immediately after foregrounding it
Please examine the answer by TheNextman.
It is an implementation of one of two approaches outlined by Ken Thomases.
I have just tested it and it works.
I tested the other approach outlined by Ken (in his answer), and it did not work. I imagine this may be because of the deprecated
GetProcessForPID
call.As the documentation doesn't say that
activateWithOptions:
waits, and it doesn't provide a completion block to callback when the application has come to the foreground we can assume that the method will return as soon as the validity of the switch have been checked and the activation message sent. There will inevitably be some latency between this happening and the application actually being ready to receive user input.While we could hope that OS X would buffer the user input and send it to the application when it's ready there will always be a race condition in this case so it's prudent to code with the expectation that you need to wait.
Just waiting for a set time isn't a great idea, but you have the tools to determine what you should do and for how long - specifically using
isActive
. Also, it's prudent to check the response ofactivateWithOptions:
to ensure we don't end up deadlocked.Something like:
Instead of using
CGEventPost()
, useCGEventPostToPSN()
. That delivers the events to the specific process. You can use code this like to get the process serial number: