编辑:最后有一个更新基于更多调查,但简短的回答似乎是这在 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。