Unpredictable Behaviour with XI2 and XI_Enter

Peter Hutterer peter.hutterer at who-t.net
Sun Jun 21 22:32:10 PDT 2009


On Tue, Jun 16, 2009 at 09:31:07PM +0000, Florian Echtler wrote:
> I tried to get to the bottom of my lost XI_Enter events today. As I
> mentioned, I'm not getting those events which should occur when a cursor
> is inside the window when it is first created. I've experimented a bit
> with the relevant part from xinput, and I've stripped it down to a
> minimal example which shows the problem (source code attached).
> 
> It seems to be some kind of race condition: if there is any significant
> delay between creation of the window and the first call to XNextEvent,
> then the XI_Event is very unlikely to arrive, as opposed to the standard
> EnterNotify. After removing the sleep(1) in the code, it works almost
> always. The standard events are never lost, neither are any subsequent
> XI_Enter or XI_Leave events. It's only the very first one, and only if
> the cursor is already inside the window upon creation.

I just tested this on the xinput example and it's not really a race
condition but merely an intrinsic detail of the async property of the X
protocol.
enter/leave events are generated when the window is mapped, i.e. when the
actual map request is processed (if the pointer is in the window at this
time). For a client to receive the event, it must have registered the event
mask when the server generates the events.

in xinput and the example code below the order of things is the following:

1. create window
2. select for core events
3. map window
==== server generates events here
4. select for XI2 events.

with the code in this order, you will never receive XI2 events, but you will
always receive core events.
the reason why you do has to do with the window manager. If a window manager
is running, the order of of things is:

1. create window                        |
2. select for core events               |
3. map window                           |
==== server forwards map request to WM  |
4. select for XI2 events                |  a. map request received
                                        |  b. create container window
                                        |  c. actually map window
==== server generates events here

after the call to XMapWindow() your client and the WM work in parallel, so
it's a race to who is faster - your client in selecting the event mask or
the window manager in mapping the window.

The correct way to avoid that problem is to simply move the event mask
selection up that at the time of mapping, the events are already selected.

1. create window
2. select for core events
3. select for XI2 events.
4. map window
==== server generates events here

Cheers,
  Peter

> #include <X11/Xlib.h>
> #include <X11/extensions/XInput2.h>
> 
> #include <string.h>
> 
> #define BitIsOn(ptr, bit) (((unsigned char *) (ptr))[(bit)>>3] & (1 << ((bit) & 7)))
> #define SetBit(ptr, bit)  (((unsigned char *) (ptr))[(bit)>>3] |= (1 << ((bit) & 7)))
> 
> static Window create_win(Display *dpy)
> {
> 	Window win = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, 200,
> 					200, 0, CopyFromParent, InputOutput, CopyFromParent, 0,0 );
> 	//Window win = XCreateSimpleWindow(dpy, win, 0, 0, 400, 400, 0, 0, WhitePixel(dpy, 0));
> 
> 
> 	XSelectInput(dpy, win, StructureNotifyMask | SubstructureNotifyMask |
> 		ExposureMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask |
> 		KeyReleaseMask | VisibilityChangeMask | EnterWindowMask |
> 		LeaveWindowMask | PointerMotionMask | ButtonMotionMask );
> 	XMapWindow(dpy, win);
        ^^ this map request screws things up

> 	XFlush(dpy);
> 	return win;
> }
> 
> 
> int main( int argc, char	*argv[] ) {
> 
> 	Display	*display = XOpenDisplay(NULL);
> 	
> 	XIEventMask mask;
> 	Window win;
> 
> 	win = create_win(display);
> 
> 	XSync(display,False);
> 	/* Select for motion events */
> 	mask.deviceid = XIAllMasterDevices;
> 	mask.mask_len = 2;
> 	mask.mask = calloc(mask.mask_len, sizeof(char));
> 	SetBit(mask.mask, XI_ButtonPress);
> 	SetBit(mask.mask, XI_ButtonRelease);
> 	SetBit(mask.mask, XI_Motion);
> 	SetBit(mask.mask, XI_Enter);
> 	SetBit(mask.mask, XI_Leave);
> 	XISelectEvents(display, win, &mask, 1);

the XMapWindow() should be here.
> 
> 	free(mask.mask);
> 	XSync(display,False);
> 
> 	// removing this sleep increases the probability of
> 	// actually receiving the first XI_Enter
> 	sleep(1);
> 
> 	while(1) {
> 
> 		XEvent xev;
> 		XNextEvent(display, &xev);
> 		//fghPrintEvent(&xev); printf("\n");
> 		XIEvent ev = *((XIEvent*)&xev);
> 
> 		if (ev.type == EnterNotify)
> 			printf("got standard EnterNotify\n");
> 
> 		if (ev.type == LeaveNotify)
> 			printf("got standard LeaveNotify\n");
> 
> 		if (ev.type == GenericEvent) {
> 
> 				XIDeviceEvent *event = (XIDeviceEvent*)&ev;
> 				switch (event->evtype) {
> 						case XI_Enter:
> 								printf("XI_Enter\n");
> 								break;
> 						case XI_Leave:
> 								printf("XI_Leave\n");
> 								break;
> 						default:
> 								break;
> 				}
> 		}
> 
> 		XIFreeEventData(&ev);
> 
> 	}
> 
> 	XDestroyWindow(display, win);
> 
> 	return 0;
> }
> 
> // build with gcc -lX11 -lXi -Wall -o xi2test xi2test.c




More information about the xorg mailing list