我正在尝试编写一个 X11 程序来监视桌面上的所有鼠标移动。每当人类用户移动鼠标或通过 XWarpPointer()
以编程方式移动鼠标时,程序应该能够接收到通知。通过机器人应用程序。我知道通过设置 PointerMotionMask
应该是可能的通过 XSelectInput()
和监控MotionNotify
,但我无法从所有窗口接收鼠标事件,而不仅仅是一个。
最初,在下面的演示中,我只是尝试从根窗口接收指针运动事件。
#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;
}
但它不会接收任何事件,除非鼠标指针位于空的桌面背景上。很明显,仅仅从根窗口接收事件是行不通的。然后我尝试了一个解决方法:首先,设置
SubstructureNotifyMask
在根窗口监控所有CreateNotify
事件捕获所有新创建的窗口,然后调用 XSelectInput()
启用 PointerMotionMask
在这些 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, 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;
}
这种方法比较成功,我开始从新窗口接收一些鼠标事件。不幸的是,它仍然不能在窗口内的所有部分工作 - 例如,它不能从终端模拟器的控制台区域接收鼠标事件,但可以在鼠标位于标题栏周围时接收事件。似乎一个窗口可以创建更多的子窗口,因此不会记录鼠标事件。
然后我尝试了另一种解决方法 - 设置
SubstructureNotifyMask
和 PointerMotionMask
在 CreateNotify
,所以当一个窗口创建一个子窗口时,SubstructureNotifyMask
确保更多 CreateNotify
事件将以递归方式接收,因此所有子窗口都将获得 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;
}
它比第二个例子好一点,但它不可靠:
XSelectInput()
之前创建了子窗口? ? BadWindow
错误和崩溃。 SubstructureNotifyMask
递归将使许多不相关的事件传递给其他处理程序,并且添加额外的代码来区分想要的和不需要的事件是很痛苦的。 那么,如何在 X11 上监控所有窗口中的鼠标移动事件呢?
最佳答案
在做了一些研究之后,尤其是阅读了 Xeyes 的源代码(我总是觉得这个演示很愚蠢,但在这里很有帮助!),我发现:
XSelectInput()
在所有窗口和子窗口上都是徒劳的尝试,您必须在曾经创建的每个窗口和子窗口上设置一个掩码,这不是一个强大的解决方案,也不推荐。XQueryPointer()
显式地从 X 服务器中不断地拉动鼠标指针。 ,而不是要求 X 服务器将 MotionEvent 推送给我们。一个简单的解决方案是通过
XtAppAddTimeOut()
设置一个计时器。并调用 XQueryPointer()
周期性地,它起作用,而且确实,it was what Xeyes did in the past !但它不必要地浪费了 CPU 时间。如今,最佳实践是利用 XInputExtention 2.0。工作流程是:XISetMask()
启用各种掩码和 XIEventMask()
接收XI_RawMotion
来自 XI_Motion
的事件(或 XIAllMasterDevices
,见下面的注释) (或 XIAllDevices
)。XI_RawMotion
(或 XI_Motion
)事件已收到,请调用 XQueryPointer()
.XQueryPointer()
返回: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()
麻烦事如果指针通过
XWarpPointer()
移动,则上面的演示不起作用通过 X.Org 1.10.4 之后的较新系统上的机器人应用程序。这是故意的,见Bug 30068在 FreeDesktop 上。为了接收所有鼠标移动触发的鼠标事件,包括
XWarpPointer()
, 更改 XI_RawMotion
至XI_Motion
, 并更改 XIAllMasterDevices
至XIAllDevices
.引用
此演示缺少错误检查,并且可能包含错误。如有疑问,请查阅以下权威引用资料。
关于c - 如何在 X11 上监视所有 Windows(不仅仅是一个)中的鼠标移动事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62448181/