Sunday, July 12, 2009

Xlib Cookie Events

Xlib in git has two new functions:

  • XGetEventData(3)

  • XFreeEventData(3)

Both are designed to deal with events delivered by servers supporting the X Generic Event extension (XGE). These events are longer than 32 bytes on the wire and can become rather large when they are unpacked into Xlib's event structures. Since Xlib has an internal maximum size for XEvents we cannot easily deal with XGE events without introducing memory leaks.

XGE event cookies

A new datatype has been introduced - the XGenericEventCookie. This datatype is essentially a wrapper to fit into Xlib but provide access to the actual event data.

It's simple enough and overlaps with XGenericEvents and of course XEvents:

typedef struct
int type; /* of event. Always GenericEvent */
unsigned long serial; /* # of last request processed */
Bool send_event; /* true if from SendEvent request */
Display *display; /* Display the event was read from */
int extension; /* major opcode of extension that caused the event */
int evtype; /* actual event type. */
unsigned int cookie; /* unique event cookie */
void *data; /* actual event data */
} XGenericEventCookie;

The two interesting fields are the cookie and the data pointer. The cookie is simply a unique number assigned to each event as it is received. It serves to identify the event when data needs to be retrieved from the Xlib internal event storage. The data pointer is a pointer to the actual event data - its type is of whatever type the extension has specified for this event type (e.g. XIDeviceEvent for XI2 motion events).

Fetching cookie data

Retrieving an event through XNextEvent or similar retrieves a cookie event instead - with a data pointer NULL. The extra data can then be received by passing the cookie event into XGetEventData. XGetEventData returns True if the cookie has been fetched successfully or False for invalid cookies (including already claimed cookies) or events that aren't cookie events.

Here is an example code snippet:

XEvent ev;
XNextEvent(dpy, &ev);
if (XGetEventData(dpy, &ev))
XGenericEventCookie *cookie = &ev.xcookie;
if (cookie->extension == <my extension> &&
cookie->evtype == <my event type>)
} else
printf("Not a cookie event. process as usual.\n");

XFreeEventData(dpy, &ev);

Once data has been obtained by the client, it becomes the client's responsibility to free this data with XFreeEventData. Failure to do so will leak memory. Unclaimed cookies are freed automatically by the library, so if you never call XGetEventData, memory doesn't leak.

XGetEventData and XFreeEventData are safe to be called with non-cookie events.

One cookie - one claim

The important thing about the cookies:

  • Each cookie can only ever be claimed once for each event.

  • XGetEventData and XFreeEventData are symmetrical

Each cookie returned by the library can be claimed exactly once, even if it represents the same actual event. In the following snippet, both XGetEventData calls return the cookie data, even though they are the same event.

XPending(dpy, &ev);
XGetEventData(dpy, &ev);
/* process */
XFreeEventData(dpy, &ev);

XNextEvent(dpy, &ev);
XGetEventData(dpy, &ev);
/* process */
XFreeEventData(dpy, &ev);

The symmetry between XGetEventData and XFreeEventData must be maintained even if the cookie is put back into the event queue:

XNextEvent(dpy, &ev);
XGetEventData(dpy, &ev);
XPutBackEvent(dpy, &ev);
XFreeEventData(dpy, &ev);

XNextEvent(dpy, &ev);
XGetEventData(dpy, &ev);
XFreeEventData(dpy, &ev);

Multi-threaded X clients

Multi-threaded clients must ensure XGetEventData is called before the next call to XNextEvent, XCheckTypedEvent, XCheckWindowEvent, XCheckTypedWindowEvent, XMaskEvent or XPending.

No comments: