Inject keyboard event into NSRunningApplication im

2019-08-24 13:36发布

问题:

I am trying to bring to foreground a NSRunningApplication* instance, and inject a keyboard event.

NSRunningApplication* app = ...;
[app activateWithOptions: 0];
inject_keystrokes();

... fails to inject keyboard events, but:

NSRunningApplication* app = ...;
[app activateWithOptions: 0];
dispatch_time_t _100ms = dispatch_time( DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC) );
dispatch_after(
               _100ms,
               dispatch_get_main_queue(),
               ^{ inject_keystrokes(); }
               );

... succeeds.

I imagine it takes a certain amount of time for the window to render in the foreground, and maybe this happens on a separate thread, and this explains the injection failure.

However this is a very ugly solution. It relies on an arbitrary time interval.

It would be much cleaner to somehow wait for the window to complete foregrounding.

Is there any way of doing this?

PS inject_keystrokes() uses CGEventPost(kCGHIDEventTap, someCGEvent)

PPS Refs:
- Virtual keypress goes to wrong application
- Send NSEvent to background app
- http://advinprog.blogspot.com/2008/06/so-you-want-to-post-keyboard-event-in.html

回答1:

Adding an observer for the KVO property isActive on NSRunningApplication works for me.

for (NSRunningApplication* ra in [[NSWorkspace sharedWorkspace] runningApplications])
{
    if ([ra.bundleIdentifier isEqualToString:@"com.apple.TextEdit"])
    {
        [ra addObserver:self forKeyPath:@"isActive" options:0 context:ra];
        [ra retain];
        [ra activateWithOptions:0];
    }
}

// ...

- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
{
    if ([keyPath isEqualToString:@"isActive"])
    {
        NSRunningApplication* ra = (NSRunningApplication*) context;
        [ra removeObserver:self forKeyPath:@"isActive"];
        [ra release];
        inject_keystrokes();
    }
}

Note that I manually retain and then release the NSRunningApplication to keep its reference alive, since I'm not keeping it in a property or ivar. You have to be careful that the reference doesn't get dropped with the observer still attached.