【问题标题】:How to send mouse click event to a window in mac osx如何将鼠标单击事件发送到mac osx中的窗口
【发布时间】:2017-07-28 05:07:28
【问题描述】:

谁能告诉我如何将鼠标单击事件发送到 mac osx 中的隐藏(不显示在前台)窗口?我正在尝试使用 pyobjcpyautogui 并且对这种情况真的很陌生。任何关键字或想法将不胜感激。谢谢!

【问题讨论】:

  • 试试 pypi.python.org/pypi/pynput ,也许可以。
  • 谢谢大佬,但是看完之后,它似乎仍然像控制和监视“真正的”鼠标和键盘,而不是将鼠标单击事件发送到 mac osx 中的指定窗口。 @ShivaGuntuku
  • 对于 GUI 自动化目的getautoma.com/docs,尝试在脚本中组合 pynput 和 automa 尝试一次。不要尝试什么目的,但可能是一些示例想法。
  • 我打算做一个后端机器人,它可以帮助我做一些基本的鼠标点击和键盘输入操作,同时我可以在前台做自己的事情。如果使用GUI自动化,我认为鼠标和键盘会被占用而不能同时做其他事情。不过谢谢!如果您知道与我的场景相关的事情,请告诉我:) @ShivaGuntuku
  • 嘿@KAs,你找到可行的解决方案了吗?

标签: python objective-c macos


【解决方案1】:

编辑:最后有一个更新基于更多调查,但简短的回答似乎是这在 OSX 中通常是不可能的。

免责声明:真的不是一个答案。我正要发布大致相同的问题,但后来我发现了这个问题。与其问同样的问题,我想我会贡献一些关于我已经找到/尝试过的内容。不过,我是对 SO 做出贡献的新手,所以我没有足够的声誉来发表评论,所以我在这里以“答案”的形式发表评论。所以这不是一个真正的答案,但希望它可以帮助你或其他人更接近一点。

答案:

AFAICT,在 OSX 中仅向一个应用程序发送鼠标事件的“正确”方法是使用 Core Graphics CGEventPostToPSN 函数。

不过,获取“PSN”(进程序列号)并不是那么简单,而且人们过去使用的所有方法都已弃用。有一个名为CGEventPostToPid 的替换函数,它使用标准的*nix 进程ID 来指定目标应用程序。

我已使用此功能成功地将键盘事件发布到后台应用程序,但没有发布鼠标事件。

例如,这会将一个字符发送到您通过 PID 指定的任何应用程序:

pid = 1234  # get/input a real pid from somewhere for the target app.
type_a_key_down_event = Quartz.CGEventCreateKeyboardEvent(objc.NULL, 0, True)
type_a_key_up_event = Quartz.CGEventCreateKeyboardEvent(objc.NULL, 0, False)

Quartz.CGEventPostToPid(pid, type_a_key_down_event)
Quartz.CGEventPostToPid(pid, type_a_key_up_event)

(用于创建键盘事件的文档:https://developer.apple.com/reference/coregraphics/1456564-cgeventcreatekeyboardevent?language=objc

但是,这不会向目标应用发送点击:

pid = 1234  # get/input a real pid from somewhere for the target app.
point = Quartz.CGPoint()
point.x = 100  # get a target x from somewhere
point.y = 100  # likewise, your target y from somewhere
left_mouse_down_event = Quartz.CGEventCreateMouseEvent(objc.NULL, Quartz.kCGEventLeftMouseDown, point, Quartz.kCGMouseButtonLeft)
left_mouse_up_event = Quartz.CGEventCreateMouseEvent(objc.NULL, Quartz.kCGEventLeftMouseUp, point, Quartz.kCGMouseButtonLeft)

Quartz.CGEventPostToPid(pid, left_mouse_down_event)
Quartz.CGEventPostToPid(pid, left_mouse_up_event)

针对相关问题的一些 SO 答案建议您将 CGEventPost 用于鼠标事件:

Quartz.CGEventPost(Quartz.kCGHIDEventTap, left_mouse_down_event)
Quartz.CGEventPost(Quartz.kCGHIDEventTap, left_mouse_up_event)

我认为 pyautogui 正在这样做,因为它成功地在请求的坐标处单击鼠标,但它是通过移动并单击全局鼠标来实现的。运行该代码会将鼠标移动到屏幕上的点并单击该点的任何内容,然后将鼠标留在那里。这不是我想要的,我不认为这是你想要的。

我想要的是CGEventPostToPSN 或更现代的对应物CGEventPostToPid 中描述的行为:只发布到目标应用程序的输入事件,无论它是否在前台,都不会窃取焦点或更改鼠标的实际位置。

我最接近的方法是改编Simulating mouse-down event on another window from CGWindowListCreate的一些代码

建议使用 NSEvent 而不是 CGEvent。我已经使用 Objective-C 和 pyobjc 进行了尝试,但成功有限。我尝试的objective-C版本对我没有任何帮助,所以我不会发布它。 pyobjc 版本允许我从终端点击 iTerm,反之亦然,但我无法让点击进入其他应用程序。我尝试了 Chrome、Console 和其他一些,但完全没有运气。

这是部分工作的 pyobjc 代码:

#! /usr/bin/env python
"""
CLI for sending mouse clicks to OSX apps.

Author: toejough
License: MIT
"""


# [ Imports ]
import Quartz
import fire
import AppKit


# [ API ]
def click(*, x, y, window_id, pid):
    """Click at x, y in the app with the pid."""
    point = Quartz.CGPoint()
    point.x = x
    point.y = y

    event_types = (
        AppKit.NSEventTypeMouseMoved,
        AppKit.NSEventTypeLeftMouseDown,
        AppKit.NSEventTypeLeftMouseUp,
    )

    for this_event_type in event_types:
        event = _create_ns_mouse_event(this_event_type, point=point, window_id=window_id)
        Quartz.CGEventPostToPid(pid, event)


# [ Internal ]
def _create_ns_mouse_event(event_type, *, point, window_id=None):
    """Create a mouse event."""
    create_ns_mouse_event = AppKit.NSEvent.mouseEventWithType_location_modifierFlags_timestamp_windowNumber_context_eventNumber_clickCount_pressure_
    ns_event = create_ns_mouse_event(
        event_type,  # Event type
        point,  # Window-specific coordinate
        0,  # Flags
        AppKit.NSProcessInfo.processInfo().systemUptime(),  # time of the event since system boot
        window_id,  # window ID the event is targeted to
        None,  # display graphics context.
        0,  # event number
        1,  # the number of mouse clicks associated with the event.
        0  # pressure applied to the input device, from 0.0 to 1.0
    )
    return ns_event.CGEvent()


# [ Script ]
if __name__ == "__main__":
    fire.Fire(click)

我尝试使用标志、事件#、时间等,但没有成功。不管我如何改变这些,我能做到的最好的是将点击从 iTerm 发送到终端或从终端发送到 iTerm。不确定是什么让终端应用程序而不是其他应用程序工作。

您可以从CGWindowListCopyWindowInfo 获取window_id 以及在https://developer.apple.com/reference/coregraphics/quartz_window_services?language=objc 记录的相关函数

如果有人对所有应用有更好的答案,真正的答案,请发布并告诉我们。或者,如果您有关于为什么点击不起作用的解释/线索,请发布。

我收集到的唯一其他可能有趣的证据是,每当我运行上述脚本时,我的系统日志中都会显示以下内容:

4/21/17 4:16:08.284 PM launchservicesd[80]: SecTaskLoadEntitlements failed error=22
4/21/17 4:16:08.288 PM launchservicesd[80]: SecTaskLoadEntitlements failed error=22
4/21/17 4:16:08.295 PM tccd[21292]: SecTaskLoadEntitlements failed error=22

但可能是红鲱鱼 - 我在成功案例(将点击发送到不同的终端应用程序)和失败案例(将点击发送到非终端应用程序)中都收到了这些错误。

希望这可以帮助您或其他读者更接近真正的解决方案 - 如果是这样,请回帖!

更新:认为这些事件适用于终端应用程序,而不是通常作为 osx 内置的反焦点跟随鼠标范例的结果。您可以发送事件,但除非应用处于活动状态(或允许 FFM),否则操作系统会阻止它。

允许 FFM 的应用程序(如 iTerm 和终端)可以接收上面的 ns_event 变体,这就是我可以让它们工作的原因。其他应用程序也将接收鼠标事件(无需移动全局鼠标光标!)只要它们首先获得焦点。

您可以通过在上述程序中或在终端中插入睡眠来亲自看到这一点,然后手动将目标应用程序置于前面,然后查看鼠标事件的经过。

AFAICT,使鼠标事件正常工作的唯一方法是绕过操作系统的 FFM 过滤器,我不知道有什么方法可以做到这一点。如果有人知道怎么做,请告诉我!

更多关于 FFM 和 OSX 的信息here

【讨论】:

  • 从那以后你有什么进展吗?
  • 不,不过我很快会在 High Sierra 上重新讨论这个问题!
  • 用 High Sierra 重试,没有运气。有关更多信息,请参阅上面编辑过的帖子。
  • 嘿,有什么新进展吗?
  • 不,抱歉。我已经有一段时间没有从事任何需要控制鼠标/键盘的工作了。
猜你喜欢
  • 1970-01-01
  • 2014-10-04
  • 1970-01-01
  • 2011-12-23
  • 2014-03-19
  • 1970-01-01
  • 2013-01-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多