
Tracking Cursor Position
I spent yesterday morning in the Accessibility BOF here at Guadec and
was reminded that one persistent problem with tools like
screen magnifiers and screen readers is that they need to know the
current cursor position all the time, independent of which window the
cursor is in and independent of grabs.
The current method that these applications are using to track the
cursor is to poll the X server using XQueryPointer. This is obviously
terrible for at least a couple of reasons:
- Keeps the system active at regular intervals, preventing power
savings.
- Increased latency in mouse tracking the interval between polling
calls limits the time resolution of the position information.
These two problems also conflict with one another. Reducing input
latency comes at the cost of further reducing the opportunities for
power saving, and vice versa.
XInput2 to the rescue

XInput2 has the ability to deliver raw device events right to
applications, bypassing the whole event selection mechanism within the
X server. This was designed to let games and other applications see
relative mouse motion events and drawing applications see the whole
tablet surface.
These raw events are really raw though; they do not include the
cursor position, and so cannot be directly used for tracking.
However, we do know that the cursor only moves in response to input
device events, so we can easily use the arrival of a raw event to
trigger a query for the mouse position.
A better plan?
Perhaps what we should do is to actually create a new event type to
report the cursor position and the containing window so that
applications can simply track that. Yeah, it s a bit of a special
case, but it s a common requirement for accessibility tools.
CursorEvent
EVENTHEADER
detail: CARD32
sourceid: DEVICEID
flags: DEVICEEVENTFLAGS
root: WINDOW
window: WINDOW
root-x, root-y: INT16
window-x, window-y: INT16
A CursorEvent is sent whenever a sprite moves on the screen. sourceid
is the master pointer which is moving. root is the root window
containing the cursor, window is the window that the pointer is
in. root-x and root-y indicate the position within the root window,
window-x and window-y indicate the position within window .
Demo Application
Here s a short application, hacked from Peter Hutterer s part1.c
/* cc -o track_cursor track_cursor.c pkg-config --cflags --libs xi x11 */
#include <stdio.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>
/* Return 1 if XI2 is available, 0 otherwise */
static int has_xi2(Display *dpy)
int major, minor;
int rc;
/* We support XI 2.2 */
major = 2;
minor = 2;
rc = XIQueryVersion(dpy, &major, &minor);
if (rc == BadRequest)
printf("No XI2 support. Server supports version %d.%d only.\n", major, minor);
return 0;
else if (rc != Success)
fprintf(stderr, "Internal Error! This is a bug in Xlib.\n");
printf("XI2 supported. Server provides version %d.%d.\n", major, minor);
return 1;
static void select_events(Display *dpy, Window win)
XIEventMask evmasks[1];
unsigned char mask1[(XI_LASTEVENT + 7)/8];
memset(mask1, 0, sizeof(mask1));
/* select for button and key events from all master devices */
XISetMask(mask1, XI_RawMotion);
evmasks[0].deviceid = XIAllMasterDevices;
evmasks[0].mask_len = sizeof(mask1);
evmasks[0].mask = mask1;
XISelectEvents(dpy, win, evmasks, 1);
XFlush(dpy);
int main (int argc, char **argv)
Display *dpy;
int xi_opcode, event, error;
XEvent ev;
dpy = XOpenDisplay(NULL);
if (!dpy)
fprintf(stderr, "Failed to open display.\n");
return -1;
if (!XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error))
printf("X Input extension not available.\n");
return -1;
if (!has_xi2(dpy))
return -1;
/* select for XI2 events */
select_events(dpy, DefaultRootWindow(dpy));
while(1)
XGenericEventCookie *cookie = &ev.xcookie;
XIRawEvent *re;
Window root_ret, child_ret;
int root_x, root_y;
int win_x, win_y;
unsigned int mask;
XNextEvent(dpy, &ev);
if (cookie->type != GenericEvent
cookie->extension != xi_opcode
!XGetEventData(dpy, cookie))
continue;
switch (cookie->evtype)
case XI_RawMotion:
re = (XIRawEvent *) cookie->data;
XQueryPointer(dpy, DefaultRootWindow(dpy),
&root_ret, &child_ret, &root_x, &root_y, &win_x, &win_y, &mask);
printf ("raw %g,%g root %d,%d\n",
re->raw_values[0], re->raw_values[1],
root_x, root_y);
break;
XFreeEventData(dpy, cookie);
return 0;
Hacks in xeyes
Of course, one common mouse tracking application is xeyes, so I ve
hacked up that code (on top of my present changes) here:
git clone git://people.freedesktop.org/~keithp/xeyes.git