【问题标题】:Virtual keypress goes to wrong application虚拟按键进入错误的应用程序
【发布时间】:2015-03-01 20:15:58
【问题描述】:

我有以下代码可以将虚拟按键发送到给定 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);

正如预期的那样,我希望立即发送按键的过程出现在前面。但问题是,在我的应用程序出现在前面之前,第一个按键将进入前面的应用程序。经测试,[app isActive] 第一次返回 false。在第一个键之后,一切都很好。

为什么会这样?尽管我在将我的流程推向前台之后发布了关键事件。

【问题讨论】:

    标签: objective-c cocoa cgeventtap


    【解决方案1】:

    由于文档没有说 activateWithOptions: 等待,并且当应用程序进入前台时它没有提供回调的完成块,我们可以假设该方法将在已检查开关并已发送激活消息。在这种情况发生与应用程序实际准备好接收用户输入之间不可避免地会有一些延迟。

    虽然我们可能希望 OS X 会缓冲用户输入并在准备好时将其发送到应用程序,但在这种情况下总会出现竞争条件,因此在编写代码时需要等待是谨慎的做法。

    只是等待设定的时间不是一个好主意,但您有工具来确定您应该做什么以及多长时间 - 特别是使用isActive。此外,谨慎检查activateWithOptions: 的响应以确保我们不会最终陷入僵局。

    类似:

    if ([app activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]) {
    
        while (![app isActive]) {
            app = [NSRunningApplication
                       runningApplicationWithProcessIdentifier: pid];
            [NSThread sleepForTimeInterval:0.05];
        }
    }
    
    // send key press events
    

    【讨论】:

    • 好主意。但是有一个问题。 app 永远不会知道它的活动和 while 循环永远不会结束。为了克服这个问题,我尝试在循环内每次都获得app 句柄。但是,不知何故,这大大减慢了我的应用程序进入前台的速度。我不确定为什么对我的代码进行更改会阻止操作系统将我的 appln 带到前台
    • 尝试将时间从 0.05 延长到 0.1,并不是说您的进程占用一点 CPU 时间在现代操作系统上应该是一个大问题
    • 我让它与 0.05 本身一起工作,但正如我之前所说,我需要每次在循环中获取一个新的应用程序句柄。 (不知何故,我在上一条评论中提到的延迟消失了!)我将编辑您的答案以包含这一事实并接受您的答案。
    • 您应该在延迟之后真正获得新的app 实例,尽管延迟为 0.05 不会产生明显的差异。
    • 已明确记录 NSRunningApplication 不会更新其属性,除非您允许运行循环运行。不要睡觉和投票。如果您不打算使用CGEventPostToPSN(),则应保持对NSRunningApplication 实例的强引用,设置active 属性的KVO 观察,然后返回主事件循环。当 KVO 更改通知到达时,检查 active 属性,如果是,请发布您的事件,清除 KVO 观察,并清除您的强引用。
    【解决方案2】:

    不要使用CGEventPost(),而是使用CGEventPostToPSN()。这会将事件传递给特定的进程。您可以使用这样的代码来获取进程序列号:

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

    【讨论】:

    • 我对 Obj-C 完全陌生。但我认为 GetProcessForPID 是一个 Carbon API 并且已被弃用,对吧?你知道可可的任何等价物吗?
    • 它不完全是 Carbon,但它已经过时且已被弃用。但是,弃用并不意味着“从不使用”。它的意思是“只有在它是实现你需要的唯一方法时才使用它,并为它最终消失做好准备”。无论如何,请使用它,因为有未弃用的 API 需要 PSN 并且没有现代方法来获取 PSN。此外,向 Apple 提交错误,请求将事件发布到 PID 或获取 PSN 的现代方式。
    • 我测试了这种方法,但它失败了。
    【解决方案3】:

    我最近在以下位置问了一个类似的问题: Inject keyboard event into NSRunningApplication immediately after foregrounding it

    请检查 TheNextman 的答案。

    它是 Ken Thomases 概述的两种方法之一的实现。

    我刚刚测试过它,它可以工作。

    我测试了 Ken 概述的另一种方法(在他的回答中),它确实没有工作。我想这可能是因为不推荐使用的GetProcessForPID 调用。

    【讨论】:

      猜你喜欢
      • 2023-03-13
      • 2013-06-07
      • 1970-01-01
      • 2019-12-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-10
      • 2012-07-21
      • 1970-01-01
      相关资源
      最近更新 更多