XTestFakeButtonEvent 和 XSendEvent 之间的区别
Difference between XTestFakeButtonEvent & XSendEvent
我正在尝试通过 x11 为 ubuntu 编写简单的鼠标点击器。
首先我写了点击程序的第一个变体(基于 XSendEvent):
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
void mouseClick(int button)
{
Display *display = XOpenDisplay(NULL);
XEvent event;
if(display == NULL)
{
std::cout << "clicking error 0" << std::endl;
exit(EXIT_FAILURE);
}
memset(&event, 0x00, sizeof(event));
event.type = ButtonPress;
event.xbutton.button = button;
event.xbutton.same_screen = True;
XQueryPointer(display, RootWindow(display, DefaultScreen(display)), &event.xbutton.root, &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
event.xbutton.subwindow = event.xbutton.window;
while(event.xbutton.subwindow)
{
event.xbutton.window = event.xbutton.subwindow;
XQueryPointer(display, event.xbutton.window, &event.xbutton.root, &event.xbutton.subwindow, &event.xbutton.x_root, &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
}
if(XSendEvent(display, PointerWindow, True, 0xfff, &event) == 0)
std::cout << "clicking error 1" << std::endl;
XFlush(display);
event.type = ButtonRelease;
event.xbutton.state = 0x100;
if(XSendEvent(display, PointerWindow, True, 0xfff, &event) == 0)
std::cout << "clicking error 2" << std::endl;
XFlush(display);
XCloseDisplay(display);
}
此代码在除 chrome 之外的每个应用程序上都可以正常工作(mozilla 也可以正常工作)。
所以我写了第二个变体(基于 XTestFakeButtonEvent):
#include <X11/extensions/XTest.h>
void SendClick(int button, Bool down)
{
Display *display = XOpenDisplay(NULL);
XTestFakeButtonEvent(display, button, down, CurrentTime);
XFlush(display);
XCloseDisplay(display);
}
而且这段代码在每个地方都可以正常工作,包括 chrome。
这些函数的调用非常简单
// XSendEvent variant
mouseClick(1);
// XTestFakeButtonEvent variant
SendClick(1, true); // press lmb
SendClick(1, false); // release lmb
1:帮助我理解我在第一个变体中做错了什么(或者 chrome 中可能做错了什么)。
1.1:当我使用 XOpenDisplay(NULL); 打开显示时,我认为我正在尝试发送不需要的事件 window。 chrome 与 x11 服务器有不同的连接系统吗?
2: 在应用程序中使用第二种变体是个好主意吗?它很短,适用于我拥有的每个应用程序)
P.S。要编译此代码,您需要添加 -lX11 -lXtst libs
XSendEvent
生成标记为已发送的事件。服务器发送的事件没有被标记。
typedef struct {
int type;
unsigned long serial;
Bool send_event; // <----- here
Display *display;
Window window;
} XAnyEvent;
出于安全原因,某些应用程序会忽略设置了此标志的事件。想想以某种方式访问您的 X11 服务器的恶意软件 — 它可以通过发送这些事件来欺骗任何应用程序做它想做的事。
在您自己的机器上使用第二个变体是完全可以的,但它依赖于一个可以禁用的扩展(同样,出于安全原因),因此不一定适用于其他人的 X11 服务器。
在 XCB 上,您可以使用以下函数来验证事件是否通过 XSendEvent() / xcb_send_event() API:
static bool fromSendEvent(const void *event)
{
// From X11 protocol: Every event contains an 8-bit type code. The most
// significant bit in this code is set if the event was generated from
// a SendEvent request.
const xcb_generic_event_t *e = reinterpret_cast<const xcb_generic_event_t *>(event);
return (e->response_type & 0x80) != 0;
}
事实上,无法判断事件是否通过 XTest 扩展发送。
您应该使用 XTest,因为它会更好地工作,XSendEvents 对内部 X 服务器状态一无所知。 Fom XSendEvent 手册:
"The contents of the event are otherwise unaltered and unchecked by the X server except to force send_event to True".
因此,在某些情况下,使用 XSendEvent 可能会出现意想不到的问题。
虽然不是直接使用 Xlib,而是通过 python Xlib 包装器库作为 Xlib 的代理,第一种方法目前对我目前在桌面上打开的所有 windows 都有效,除了 IntelliJ。
在第一种方法中,您将事件直接发送到目标 window,并且 ,您的事件也被标记(污染)为一个将其标记为模拟事件的属性值.接收方 window 可能会像许多应用程序 windows 一样对它采取同样的行动。
然而,使用第二种方法,您是在模拟实际发生的事情——根据我的理解,这与用户请求的事件几乎没有区别:该事件经历了更完整的 X11 用户输入事件处理流程(而不是盲目地直接发送到目标 window),这意味着它会像真实用户请求事件的自然事件流一样,滴流到指针下的 window(或 Gnome 桌面小部件)。
因此,第二种方法似乎比第一种方法更广泛地适用——对于选择不对通过第一种方法发送给他们的事件采取行动的windows,它也会产生预期的效果,以及例如不普通的 Gnome 桌面元素 windows per-se(例如语言和电源小部件)。您提供的坐标没有任何提及 window,然后点击完成。
如果我必须对路由的这种双重性提出某种解释,我可能会认为 XSendEvent
更像是一个通用的事件发送工具,而 XTEST 提供了专门模拟 用户输入 事件。
我正在尝试通过 x11 为 ubuntu 编写简单的鼠标点击器。
首先我写了点击程序的第一个变体(基于 XSendEvent):
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
void mouseClick(int button)
{
Display *display = XOpenDisplay(NULL);
XEvent event;
if(display == NULL)
{
std::cout << "clicking error 0" << std::endl;
exit(EXIT_FAILURE);
}
memset(&event, 0x00, sizeof(event));
event.type = ButtonPress;
event.xbutton.button = button;
event.xbutton.same_screen = True;
XQueryPointer(display, RootWindow(display, DefaultScreen(display)), &event.xbutton.root, &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
event.xbutton.subwindow = event.xbutton.window;
while(event.xbutton.subwindow)
{
event.xbutton.window = event.xbutton.subwindow;
XQueryPointer(display, event.xbutton.window, &event.xbutton.root, &event.xbutton.subwindow, &event.xbutton.x_root, &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
}
if(XSendEvent(display, PointerWindow, True, 0xfff, &event) == 0)
std::cout << "clicking error 1" << std::endl;
XFlush(display);
event.type = ButtonRelease;
event.xbutton.state = 0x100;
if(XSendEvent(display, PointerWindow, True, 0xfff, &event) == 0)
std::cout << "clicking error 2" << std::endl;
XFlush(display);
XCloseDisplay(display);
}
此代码在除 chrome 之外的每个应用程序上都可以正常工作(mozilla 也可以正常工作)。
所以我写了第二个变体(基于 XTestFakeButtonEvent):
#include <X11/extensions/XTest.h>
void SendClick(int button, Bool down)
{
Display *display = XOpenDisplay(NULL);
XTestFakeButtonEvent(display, button, down, CurrentTime);
XFlush(display);
XCloseDisplay(display);
}
而且这段代码在每个地方都可以正常工作,包括 chrome。
这些函数的调用非常简单
// XSendEvent variant
mouseClick(1);
// XTestFakeButtonEvent variant
SendClick(1, true); // press lmb
SendClick(1, false); // release lmb
1:帮助我理解我在第一个变体中做错了什么(或者 chrome 中可能做错了什么)。
1.1:当我使用 XOpenDisplay(NULL); 打开显示时,我认为我正在尝试发送不需要的事件 window。 chrome 与 x11 服务器有不同的连接系统吗?
2: 在应用程序中使用第二种变体是个好主意吗?它很短,适用于我拥有的每个应用程序)
P.S。要编译此代码,您需要添加 -lX11 -lXtst libs
XSendEvent
生成标记为已发送的事件。服务器发送的事件没有被标记。
typedef struct {
int type;
unsigned long serial;
Bool send_event; // <----- here
Display *display;
Window window;
} XAnyEvent;
出于安全原因,某些应用程序会忽略设置了此标志的事件。想想以某种方式访问您的 X11 服务器的恶意软件 — 它可以通过发送这些事件来欺骗任何应用程序做它想做的事。
在您自己的机器上使用第二个变体是完全可以的,但它依赖于一个可以禁用的扩展(同样,出于安全原因),因此不一定适用于其他人的 X11 服务器。
在 XCB 上,您可以使用以下函数来验证事件是否通过 XSendEvent() / xcb_send_event() API:
static bool fromSendEvent(const void *event)
{
// From X11 protocol: Every event contains an 8-bit type code. The most
// significant bit in this code is set if the event was generated from
// a SendEvent request.
const xcb_generic_event_t *e = reinterpret_cast<const xcb_generic_event_t *>(event);
return (e->response_type & 0x80) != 0;
}
事实上,无法判断事件是否通过 XTest 扩展发送。
您应该使用 XTest,因为它会更好地工作,XSendEvents 对内部 X 服务器状态一无所知。 Fom XSendEvent 手册:
"The contents of the event are otherwise unaltered and unchecked by the X server except to force send_event to True".
因此,在某些情况下,使用 XSendEvent 可能会出现意想不到的问题。
虽然不是直接使用 Xlib,而是通过 python Xlib 包装器库作为 Xlib 的代理,第一种方法目前对我目前在桌面上打开的所有 windows 都有效,除了 IntelliJ。
在第一种方法中,您将事件直接发送到目标 window,并且
然而,使用第二种方法,您是在模拟实际发生的事情——根据我的理解,这与用户请求的事件几乎没有区别:该事件经历了更完整的 X11 用户输入事件处理流程(而不是盲目地直接发送到目标 window),这意味着它会像真实用户请求事件的自然事件流一样,滴流到指针下的 window(或 Gnome 桌面小部件)。
因此,第二种方法似乎比第一种方法更广泛地适用——对于选择不对通过第一种方法发送给他们的事件采取行动的windows,它也会产生预期的效果,以及例如不普通的 Gnome 桌面元素 windows per-se(例如语言和电源小部件)。您提供的坐标没有任何提及 window,然后点击完成。
如果我必须对路由的这种双重性提出某种解释,我可能会认为 XSendEvent
更像是一个通用的事件发送工具,而 XTEST 提供了专门模拟 用户输入 事件。