Virtual keypress goes to wrong application

2019-08-07 11:43发布

问题:

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.

回答1:

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 of activateWithOptions: to ensure we don't end up deadlocked.

Something like:

if ([app activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]) {

    while (![app isActive]) {
        app = [NSRunningApplication
                   runningApplicationWithProcessIdentifier: pid];
        [NSThread sleepForTimeInterval:0.05];
    }
}

// send key press events


回答2:

Instead of using CGEventPost(), use CGEventPostToPSN(). That delivers the events to the specific process. You can use code this like to get the process serial number:

ProcessSerialNumber psn;
GetProcessForPID(app.processIdentifier, &psn);


回答3:

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.