有没有办法在Mac OSX上向一个隐藏的(不在前台显示)窗口发送鼠标点击事件?我正在尝试使用pyobjc
或pyautogui
,对这种情况还很陌生。如果您有任何关键字或想法,请告诉我。谢谢!
有没有办法在Mac OSX上向一个隐藏的(不在前台显示)窗口发送鼠标点击事件?我正在尝试使用pyobjc
或pyautogui
,对这种情况还很陌生。如果您有任何关键字或想法,请告诉我。谢谢!
编辑:根据更多的调查,最后更新在结尾处。但简而言之,在OSX中通常是不可能做到这一点的。
免责声明:这并不是一个真正的答案。我本来要发布大致相同的问题,但是后来我找到了这个问题。为了不重复提问,我想通过一些评论来分享我已经发现/尝试过的东西。虽然我是SO上的新手,但我还没有足够的声誉来留下评论,所以我在这里发表评论作为“答案”。所以它并不是一个真正的答案,但希望能帮助你或其他人更接近答案。
类似答案:
据我所知,在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与Terminal之间发送点击。不确定终端应用程序的特殊性是什么,导致其他应用程序不能正常工作。
您可以从https://developer.apple.com/reference/coregraphics/quartz_window_services?language=objc中记录的CGWindowListCopyWindowInfo
和相关函数中获取window_id
。
如果有人有更好的答案和真正的方案适用于所有应用程序,请发布并告知我们。或者,如果您对为什么点击无法正常工作有解释/线索,请发布。
我收集到的唯一可能有趣的证据是,每次运行上述脚本时,我的系统日志中都会出现以下内容:
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 变量,这就是我能够使它们工作的原因。只要首先聚焦于其他应用程序,其他应用程序也会接收到鼠标事件(不移动全局鼠标指针!)。
您可以通过将睡眠插入上述程序或在终端中,手动将目标应用程序置于前台,并查看鼠标事件是否经过来自己验证。
据我所知,使鼠标事件通常工作的唯一方法是绕过操作系统的 FFM 过滤器,我不知道有什么方法可以做到这一点。如果有人找到了方法,请告诉我!
有关 FFM 和 OSX 的更多信息 在这里。