I'm trying write an X11 program to monitor all mouse movements on the desktop. The program should be able to receive a notification whenever the mouse is moved by the human user, or moved programmatically via XWarpPointer()
by a robotic application. I know it should be possible by setting a PointerMotionMask
via XSelectInput()
and monitor MotionNotify
, but I'm having troubles receiving mouse events from all windows, not just one.
Initially, I just tried to receive pointer motion events from the root window, in the following demo.
#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;
}
But it doesn't receive any events, unless the mouse pointer is on an empty desktop background. It's clear that merely receiving events from the root window won't work. Then I tried a workaround: first, set SubstructureNotifyMask
on the root window to monitor all CreateNotify
events to catch all newly created windows, then call XSelectInput()
to enable the PointerMotionMask
on these windows.
#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;
}
This approach is more successful, I started to receive some mouse events from new windows. Unfortunately, it still doesn't work in all portions inside a window - for example, it cannot receive mouse events from the console area in terminal emulators, but can receive events when the mouse is located around the title bar. It appears that a window can create more subwindows, so the mouse events won't be recorded.
Then I tried another workaround - set both SubstructureNotifyMask
and PointerMotionMask
in CreateNotify
, so when a window creates a child window, SubstructureNotifyMask
ensures more CreateNotify
events will be received in a recursive manner, so all child windows will get PointerMotionMask
as well.
#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;
}
It works a bit better than the second example, but it's not reliable:
X is fully asynchronous, is it possible that the child window got created before we had a chance to XSelectInput()
?
Sometimes it just reports a BadWindow
error and crashes.
X event handling becomes messy - if the program already handles a lot of different X events, enabling SubstructureNotifyMask
recursively will make many unrelated events delivered to other handlers, and it's a pain to add extra code to discriminate between wanted and unwanted events.
So, how do I monitor mouse movement events in all windows on X11?
After doing some research, especially reading the source code of Xeyes (I always fell the demo is stupid, but it helps a lot here,): I found:
Calling XSelectInput()
on all the windows and subwindows is a futile attempt, you have to set a mask on every single window and child window ever created, it's not a robust solution, and not recommended.
Instead, it's better to just continuously pulling the mouse pointer from the X server explicitly via XQueryPointer()
, rather than asking the X server to push MotionEvent to us.
One naive solution is simply setting up a timer by XtAppAddTimeOut()
and calling XQueryPointer()
periodically, it works, and indeed, it was what Xeyes did in the past . But it unnecessarily wastes CPU time, Nowadays. the best practice is to take advantage of XInputExtention 2.0: The workflow is:
Initialize XInput v2.0
Enable various masks via XISetMask()
and XIEventMask()
to receive XI_RawMotion
events (or XI_Motion
, see notes below) from XIAllMasterDevices
(or XIAllDevices
).
When a XI_RawMotion
(or XI_Motion
) event has been received, call XQueryPointer()
.
XQueryPointer()
returns:
Perform a XTranslateCoordinates()
if we want relative coordinates with respect to the active window under the mouse cursor.
Here's a demo (save as mouse.c
, compile with gcc mouse.c -o mouse -lX11 -lXi
). However, it cannot detect XWarpPointer()
, see notes below.
#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()
Troubles The demo above doesn't work if the pointer is moved via XWarpPointer()
by a robotic application on newer systems after X.Org 1.10.4. This is intentional, see Bug 30068 on FreeDesktop.
In order to receive mouse events triggered by all mouse movements, including XWarpPointer()
, change XI_RawMotion
to XI_Motion
, and change XIAllMasterDevices
to XIAllDevices
.
This demo lacks error checking and may contain bugs. If in doubts, please check the following authoritative references.
Tracking Cursor Position by Keith Packard , a real X expert, has been heavily involved in the development of X since the late 1980s, and responsible for many X extensions and technical papers.
Xeyes source code from X.Org.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.