如何将鼠标单击事件发送到mac osx中的窗口

时间:2017-03-07 20:26:03

标签: python objective-c macos

有人能告诉我如何将鼠标点击事件发送到mac osx中的隐藏(不显示在前台)窗口吗?我正在尝试使用pyobjcpyautogui,这对于这种情况来说真的很新。任何关键字或想法将不胜感激。谢谢!

1 个答案:

答案 0 :(得分:6)

编辑:根据更多调查结果,最后会有更新,但简短的回答似乎是在OSX中通常无法做到这一点。

免责声明:这不是真的答案。我即将发布大致相同的问题,但后来我发现了这个问题。我没有问同样的问题,而是认为我会对我已经找到/试过的内容做出一些评论。不过,我很擅长为SO做贡献,所以我没有足够的声誉来发表评论,所以我在这里以“回答”的方式发表评论。所以这不是一个真正的答案,但希望它可以帮助你或其他人更近一点。

排序答案:

AFAICT,在OSX中向一个应用程序发送鼠标事件的“正确”方法是使用核心图形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。不确定终端应用程序是什么让它们起作用而不是其他应用程序。

您可以从window_id获取CGWindowListCopyWindowInfo 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 以及https://developer.apple.com/reference/coregraphics/quartz_window_services?language=objc

中记录的相关功能

如果某人对所有应用都有更好的答案,真实答案,请发布并告知我们。或者,如果您对点击不起作用的原因有解释/线索,请发布。

我收集的唯一其他可能有趣的证据是,无论何时运行上述脚本,我都会在我的系统日志中获得以下内容:

{{1}}

虽然可能是红鲱鱼 - 我在成功的情况下(向不同的终端应用发送点击)和失败案例(向非终端应用发送点击)都会收到这些错误。

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

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

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

您可以通过在上述程序或终端中插入睡眠来自行查看,并手动将目标应用程序置于前面,并查看鼠标事件是否通过。

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

有关FFM和OSX的更多信息here