如何在 X11 上监视所有 Windows(不只是一个)的鼠标移动事件
How do I Monitor Mouse Movement Events in All Windows (Not Just One) on X11
我正在尝试编写一个 X11 程序来监视桌面上的所有鼠标移动。每当人类用户移动鼠标,或机器人应用程序通过 XWarpPointer()
以编程方式移动鼠标时,程序应该能够收到通知。我知道应该可以通过 XSelectInput()
设置 PointerMotionMask
并监视 MotionNotify
,但是我在从 all 接收鼠标事件时遇到问题 windows,不止一个。
最初,在下面的演示中,我只是尝试从根 window 接收指针运动事件。
#include <stdio.h>
#include <X11/Xlib.h>
int main(int argc, char **argv)
{
Display *display;
Window root_window;
XEvent event;
display = XOpenDisplay(0);
root_window = XRootWindow(display, 0);
XSelectInput(display, root_window, PointerMotionMask);
while (1) {
XNextEvent(display, &event);
switch(event.type) {
case MotionNotify:
printf("x: %d y: %d\n", event.xmotion.x, event.xmotion.y );
break;
}
}
return 0;
}
但它不会收到任何事件,除非鼠标指针位于空白桌面背景上。很明显,仅从根 window 接收事件是行不通的。然后我尝试了一个解决方法:首先,在根 window 上设置 SubstructureNotifyMask
以监视所有 CreateNotify
事件以捕获所有新创建的 windows,然后调用 XSelectInput()
以在这些 windows 上启用 PointerMotionMask
。
#include <stdio.h>
#include <X11/Xlib.h>
int main(int argc, char **argv)
{
Display *display;
Window root_window;
XEvent event;
display = XOpenDisplay(0);
root_window = XRootWindow(display, 0);
XSelectInput(display, root_window, SubstructureNotifyMask);
while (1) {
XNextEvent(display, &event);
switch(event.type) {
case CreateNotify:
XSelectInput(display, event.xcreatewindow.window, PointerMotionMask);
break;
case MotionNotify:
printf("x: %d y: %d\n", event.xmotion.x, event.xmotion.y);
break;
}
}
return 0;
}
这种方法比较成功,我开始从新windows接收一些鼠标事件。不幸的是,它仍然无法在 window 内的所有部分工作 - 例如,它无法从终端仿真器的控制台区域接收鼠标事件,但可以在鼠标位于标题栏周围时接收事件。看来一个window可以创建更多子windows,所以鼠标事件不会被记录。
然后我尝试了另一种解决方法 - 在 CreateNotify
中同时设置 SubstructureNotifyMask
和 PointerMotionMask
,所以当 window 创建 child window, SubstructureNotifyMask
确保将以递归方式接收更多 CreateNotify
事件,因此所有 child windows 也将获得 PointerMotionMask
。
#include <stdio.h>
#include <X11/Xlib.h>
int main(int argc, char **argv)
{
Display *display;
Window root_window;
XEvent event;
display = XOpenDisplay(0);
root_window = XRootWindow(display, 0);
XSelectInput(display, root_window, SubstructureNotifyMask);
while (1) {
XNextEvent(display, &event);
switch(event.type) {
case CreateNotify:
XSelectInput(display, event.xcreatewindow.window, SubstructureNotifyMask | PointerMotionMask);
break;
case MotionNotify:
printf("x: %d y: %d\n", event.xmotion.x, event.xmotion.y);
break;
}
}
return 0;
}
它比第二个例子好一点,但不可靠:
X 是完全异步的,是否有可能 child window 在我们有机会 XSelectInput()
之前创建?
有时它只是报告 BadWindow
错误并崩溃。
X 事件处理变得混乱 - 如果程序已经处理了很多不同的 X 事件,递归启用 SubstructureNotifyMask
将使许多不相关的事件传递给其他处理程序,这很痛苦添加额外的代码来区分想要的和不需要的事件。
那么,我如何在 X11 上监控所有 windows 中的鼠标移动事件?
经过一些研究,尤其是阅读了 Xeyes 的源代码(我总是觉得这个演示很愚蠢,但在这里帮助很大!),我发现:
在所有 windows 和 subwindows 上调用 XSelectInput()
是徒劳的尝试,您必须在每个 window 上设置掩码和子 window 曾经创建过,这不是一个可靠的解决方案,因此不推荐。
相反,通过 XQueryPointer()
直接从 X 服务器明确地持续拉出鼠标指针,而不是要求 X 服务器将 MotionEvent 推送给我们更好。
一个天真的解决方案是简单地通过 XtAppAddTimeOut()
设置一个计时器并定期调用 XQueryPointer()
,它有效,而且确实 it was what Xeyes did in the past!但它不必要地浪费了 CPU 时间。如今,最佳实践是利用 XInputExtention 2.0。工作流程是:
初始化 XInput v2.0
通过XISetMask()
和XIEventMask()
启用各种掩码以接收来自[=20=的XI_RawMotion
事件(或XI_Motion
,见下面的注释) ](或XIAllDevices
)。
收到XI_RawMotion
(或XI_Motion
)事件后,调用XQueryPointer()
。
XQueryPointer()
returns:
- 鼠标坐标相对于根 window。
- 鼠标光标下的活动window,如果有的话。
如果我们想要相对于鼠标光标下的活动 window 的相对坐标,请执行 XTranslateCoordinates()
。
演示
这是一个演示(另存为mouse.c
,用gcc mouse.c -o mouse -lX11 -lXi
编译)。但是,它无法检测到 XWarpPointer()
,请参阅下面的注释。
#include <stdio.h>
#include <assert.h>
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>
int main(int argc, char **argv)
{
Display *display;
Window root_window;
/* Initialize (FIXME: no error checking). */
display = XOpenDisplay(0);
root_window = XRootWindow(display, 0);
/* check XInput */
int xi_opcode, event, error;
if (!XQueryExtension(display, "XInputExtension", &xi_opcode, &event, &error)) {
fprintf(stderr, "Error: XInput extension is not supported!\n");
return 1;
}
/* Check XInput 2.0 */
int major = 2;
int minor = 0;
int retval = XIQueryVersion(display, &major, &minor);
if (retval != Success) {
fprintf(stderr, "Error: XInput 2.0 is not supported (ancient X11?)\n");
return 1;
}
/*
* Set mask to receive XI_RawMotion events. Because it's raw,
* XWarpPointer() events are not included, you can use XI_Motion
* instead.
*/
unsigned char mask_bytes[(XI_LASTEVENT + 7) / 8] = {0}; /* must be zeroed! */
XISetMask(mask_bytes, XI_RawMotion);
/* Set mask to receive events from all master devices */
XIEventMask evmasks[1];
/* You can use XIAllDevices for XWarpPointer() */
evmasks[0].deviceid = XIAllMasterDevices;
evmasks[0].mask_len = sizeof(mask_bytes);
evmasks[0].mask = mask_bytes;
XISelectEvents(display, root_window, evmasks, 1);
XEvent xevent;
while (1) {
XNextEvent(display, &xevent);
if (xevent.xcookie.type != GenericEvent || xevent.xcookie.extension != xi_opcode) {
/* not an XInput event */
continue;
}
XGetEventData(display, &xevent.xcookie);
if (xevent.xcookie.evtype != XI_RawMotion) {
/*
* Not an XI_RawMotion event (you may want to detect
* XI_Motion as well, see comments above).
*/
XFreeEventData(display, &xevent.xcookie);
continue;
}
XFreeEventData(display, &xevent.xcookie);
Window root_return, child_return;
int root_x_return, root_y_return;
int win_x_return, win_y_return;
unsigned int mask_return;
/*
* We need:
* child_return - the active window under the cursor
* win_{x,y}_return - pointer coordinate with respect to root window
*/
int retval = XQueryPointer(display, root_window, &root_return, &child_return,
&root_x_return, &root_y_return,
&win_x_return, &win_y_return,
&mask_return);
if (!retval) {
/* pointer is not in the same screen, ignore */
continue;
}
/* We used root window as its reference, so both should be the same */
assert(root_x_return == win_x_return);
assert(root_y_return == win_y_return);
printf("root: x %d y %d\n", root_x_return, root_y_return);
if (child_return) {
int local_x, local_y;
XTranslateCoordinates(display, root_window, child_return,
root_x_return, root_y_return,
&local_x, &local_y, &child_return);
printf("local: x %d y %d\n\n", local_x, local_y);
}
}
XCloseDisplay(display);
return 0;
}
示例输出
root: x 631 y 334
local: x 140 y 251
root: x 628 y 338
local: x 137 y 255
root: x 619 y 343
local: x 128 y 260
XWarpPointer()
烦恼
如果 X.Org 1.10.4 之后的较新系统上的机器人应用程序通过 XWarpPointer()
移动指针,则上述演示将不起作用。这是故意的,请参阅 FreeDesktop 上的 Bug 30068。
为了接收所有鼠标移动触发的鼠标事件,包括XWarpPointer()
,将XI_RawMotion
改为XI_Motion
,将XIAllMasterDevices
改为XIAllDevices
.
参考资料
此演示缺少错误检查,可能包含错误。如有疑问,请查看以下权威参考资料。
Tracking Cursor Position by Keith Packard,真正的 X 专家,自 1980 年代后期以来一直大量参与 X 的开发,并负责许多 X 扩展和技术论文。
Xeyes source code 来自 X.Org.
我正在尝试编写一个 X11 程序来监视桌面上的所有鼠标移动。每当人类用户移动鼠标,或机器人应用程序通过 XWarpPointer()
以编程方式移动鼠标时,程序应该能够收到通知。我知道应该可以通过 XSelectInput()
设置 PointerMotionMask
并监视 MotionNotify
,但是我在从 all 接收鼠标事件时遇到问题 windows,不止一个。
最初,在下面的演示中,我只是尝试从根 window 接收指针运动事件。
#include <stdio.h>
#include <X11/Xlib.h>
int main(int argc, char **argv)
{
Display *display;
Window root_window;
XEvent event;
display = XOpenDisplay(0);
root_window = XRootWindow(display, 0);
XSelectInput(display, root_window, PointerMotionMask);
while (1) {
XNextEvent(display, &event);
switch(event.type) {
case MotionNotify:
printf("x: %d y: %d\n", event.xmotion.x, event.xmotion.y );
break;
}
}
return 0;
}
但它不会收到任何事件,除非鼠标指针位于空白桌面背景上。很明显,仅从根 window 接收事件是行不通的。然后我尝试了一个解决方法:首先,在根 window 上设置 SubstructureNotifyMask
以监视所有 CreateNotify
事件以捕获所有新创建的 windows,然后调用 XSelectInput()
以在这些 windows 上启用 PointerMotionMask
。
#include <stdio.h>
#include <X11/Xlib.h>
int main(int argc, char **argv)
{
Display *display;
Window root_window;
XEvent event;
display = XOpenDisplay(0);
root_window = XRootWindow(display, 0);
XSelectInput(display, root_window, SubstructureNotifyMask);
while (1) {
XNextEvent(display, &event);
switch(event.type) {
case CreateNotify:
XSelectInput(display, event.xcreatewindow.window, PointerMotionMask);
break;
case MotionNotify:
printf("x: %d y: %d\n", event.xmotion.x, event.xmotion.y);
break;
}
}
return 0;
}
这种方法比较成功,我开始从新windows接收一些鼠标事件。不幸的是,它仍然无法在 window 内的所有部分工作 - 例如,它无法从终端仿真器的控制台区域接收鼠标事件,但可以在鼠标位于标题栏周围时接收事件。看来一个window可以创建更多子windows,所以鼠标事件不会被记录。
然后我尝试了另一种解决方法 - 在 CreateNotify
中同时设置 SubstructureNotifyMask
和 PointerMotionMask
,所以当 window 创建 child window, SubstructureNotifyMask
确保将以递归方式接收更多 CreateNotify
事件,因此所有 child windows 也将获得 PointerMotionMask
。
#include <stdio.h>
#include <X11/Xlib.h>
int main(int argc, char **argv)
{
Display *display;
Window root_window;
XEvent event;
display = XOpenDisplay(0);
root_window = XRootWindow(display, 0);
XSelectInput(display, root_window, SubstructureNotifyMask);
while (1) {
XNextEvent(display, &event);
switch(event.type) {
case CreateNotify:
XSelectInput(display, event.xcreatewindow.window, SubstructureNotifyMask | PointerMotionMask);
break;
case MotionNotify:
printf("x: %d y: %d\n", event.xmotion.x, event.xmotion.y);
break;
}
}
return 0;
}
它比第二个例子好一点,但不可靠:
X 是完全异步的,是否有可能 child window 在我们有机会
XSelectInput()
之前创建?有时它只是报告
BadWindow
错误并崩溃。X 事件处理变得混乱 - 如果程序已经处理了很多不同的 X 事件,递归启用
SubstructureNotifyMask
将使许多不相关的事件传递给其他处理程序,这很痛苦添加额外的代码来区分想要的和不需要的事件。
那么,我如何在 X11 上监控所有 windows 中的鼠标移动事件?
经过一些研究,尤其是阅读了 Xeyes 的源代码(我总是觉得这个演示很愚蠢,但在这里帮助很大!),我发现:
在所有 windows 和 subwindows 上调用
XSelectInput()
是徒劳的尝试,您必须在每个 window 上设置掩码和子 window 曾经创建过,这不是一个可靠的解决方案,因此不推荐。相反,通过
XQueryPointer()
直接从 X 服务器明确地持续拉出鼠标指针,而不是要求 X 服务器将 MotionEvent 推送给我们更好。
一个天真的解决方案是简单地通过 XtAppAddTimeOut()
设置一个计时器并定期调用 XQueryPointer()
,它有效,而且确实 it was what Xeyes did in the past!但它不必要地浪费了 CPU 时间。如今,最佳实践是利用 XInputExtention 2.0。工作流程是:
初始化 XInput v2.0
通过
XISetMask()
和XIEventMask()
启用各种掩码以接收来自[=20=的XI_RawMotion
事件(或XI_Motion
,见下面的注释) ](或XIAllDevices
)。收到
XI_RawMotion
(或XI_Motion
)事件后,调用XQueryPointer()
。XQueryPointer()
returns:- 鼠标坐标相对于根 window。
- 鼠标光标下的活动window,如果有的话。
如果我们想要相对于鼠标光标下的活动 window 的相对坐标,请执行
XTranslateCoordinates()
。
演示
这是一个演示(另存为mouse.c
,用gcc mouse.c -o mouse -lX11 -lXi
编译)。但是,它无法检测到 XWarpPointer()
,请参阅下面的注释。
#include <stdio.h>
#include <assert.h>
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>
int main(int argc, char **argv)
{
Display *display;
Window root_window;
/* Initialize (FIXME: no error checking). */
display = XOpenDisplay(0);
root_window = XRootWindow(display, 0);
/* check XInput */
int xi_opcode, event, error;
if (!XQueryExtension(display, "XInputExtension", &xi_opcode, &event, &error)) {
fprintf(stderr, "Error: XInput extension is not supported!\n");
return 1;
}
/* Check XInput 2.0 */
int major = 2;
int minor = 0;
int retval = XIQueryVersion(display, &major, &minor);
if (retval != Success) {
fprintf(stderr, "Error: XInput 2.0 is not supported (ancient X11?)\n");
return 1;
}
/*
* Set mask to receive XI_RawMotion events. Because it's raw,
* XWarpPointer() events are not included, you can use XI_Motion
* instead.
*/
unsigned char mask_bytes[(XI_LASTEVENT + 7) / 8] = {0}; /* must be zeroed! */
XISetMask(mask_bytes, XI_RawMotion);
/* Set mask to receive events from all master devices */
XIEventMask evmasks[1];
/* You can use XIAllDevices for XWarpPointer() */
evmasks[0].deviceid = XIAllMasterDevices;
evmasks[0].mask_len = sizeof(mask_bytes);
evmasks[0].mask = mask_bytes;
XISelectEvents(display, root_window, evmasks, 1);
XEvent xevent;
while (1) {
XNextEvent(display, &xevent);
if (xevent.xcookie.type != GenericEvent || xevent.xcookie.extension != xi_opcode) {
/* not an XInput event */
continue;
}
XGetEventData(display, &xevent.xcookie);
if (xevent.xcookie.evtype != XI_RawMotion) {
/*
* Not an XI_RawMotion event (you may want to detect
* XI_Motion as well, see comments above).
*/
XFreeEventData(display, &xevent.xcookie);
continue;
}
XFreeEventData(display, &xevent.xcookie);
Window root_return, child_return;
int root_x_return, root_y_return;
int win_x_return, win_y_return;
unsigned int mask_return;
/*
* We need:
* child_return - the active window under the cursor
* win_{x,y}_return - pointer coordinate with respect to root window
*/
int retval = XQueryPointer(display, root_window, &root_return, &child_return,
&root_x_return, &root_y_return,
&win_x_return, &win_y_return,
&mask_return);
if (!retval) {
/* pointer is not in the same screen, ignore */
continue;
}
/* We used root window as its reference, so both should be the same */
assert(root_x_return == win_x_return);
assert(root_y_return == win_y_return);
printf("root: x %d y %d\n", root_x_return, root_y_return);
if (child_return) {
int local_x, local_y;
XTranslateCoordinates(display, root_window, child_return,
root_x_return, root_y_return,
&local_x, &local_y, &child_return);
printf("local: x %d y %d\n\n", local_x, local_y);
}
}
XCloseDisplay(display);
return 0;
}
示例输出
root: x 631 y 334
local: x 140 y 251
root: x 628 y 338
local: x 137 y 255
root: x 619 y 343
local: x 128 y 260
XWarpPointer()
烦恼
如果 X.Org 1.10.4 之后的较新系统上的机器人应用程序通过 XWarpPointer()
移动指针,则上述演示将不起作用。这是故意的,请参阅 FreeDesktop 上的 Bug 30068。
为了接收所有鼠标移动触发的鼠标事件,包括XWarpPointer()
,将XI_RawMotion
改为XI_Motion
,将XIAllMasterDevices
改为XIAllDevices
.
参考资料
此演示缺少错误检查,可能包含错误。如有疑问,请查看以下权威参考资料。
Tracking Cursor Position by Keith Packard,真正的 X 专家,自 1980 年代后期以来一直大量参与 X 的开发,并负责许多 X 扩展和技术论文。
Xeyes source code 来自 X.Org.