将键盘事件置于前台后立即将其注入 NSRunningApplication

Inject keyboard event into NSRunningApplication immediately after foregrounding it

我正在尝试将一个 NSRunningApplication* 实例置于前台,并注入一个键盘事件。

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

...无法注入键盘事件,但是:

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

...成功。

我想 window 在前台渲染需要一定的时间,也许这发生在一个单独的线程上,这解释了注入失败。

然而,这是一个非常丑陋的解决方案。它依赖于任意时间间隔。

以某种方式等待 window 完成前景化会更干净。

有什么办法吗?

PS inject_keystrokes() 使用 CGEventPost(kCGHIDEventTap, someCGEvent)

PPS 参考:
-
- Send NSEvent to background app
- http://advinprog.blogspot.com/2008/06/so-you-want-to-post-keyboard-event-in.html

NSRunningApplication 上为 KVO 属性 isActive 添加一个观察者对我有用。

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

请注意,我手动保留然后释放 NSRunningApplication 以使其引用保持活动状态,因为我没有将其保存在 属性 或 ivar 中。您必须小心,不要在观察者仍然附加的情况下删除引用。