[Xcb] Ideal event handling

Jamey Sharp jamey at minilop.net
Tue Aug 9 17:04:55 EST 2005


Travis asked a simple question about how to port code using XIfEvent to
XCB, and here I'm about to try to explain exactly how I think event
handling should work in X apps generally. I got bogged down for a while
in details of Gdk's implementation, so lets start by speaking completely
generically.

Xlib exposes to callers the presence of an event queue, allowing
operations like XIfEvent that traverse the entire queue looking for an
event matching a particular set of criteria. Although XCB also has a
queue of events internally, it does not expose that queue publicly:
callers can only remove the first available event, in either a blocking
or non-blocking mode. [1] We expect this decision to allow us some
implementation flexibility later.

The simplest strategy for porting Xlib code that relies on the event
queue is to write your own event queue that supports the searching
operations you need. Given a trivial linked list implementation, these
operations are really easy to write, so this part of a porting effort
won't take long. I hope you can guess by now that I don't think this is
a very good strategy, though. Consider that the event accessed out of
the queue most often is the first one: these queue manipulation
functions are used rarely. We ought to be able to devise targeted
solutions suited to those rare occasions.

So lets talk requirements. Why do people use functions like XIfEvent,
and how *should* such code be ported to XCB? A common reason shows up in
code that makes a request that should trigger an event, then wants that
event. Travis' example is one of the simplest I know of:

  XChangeProperty (xdisplay, xwindow, timestamp_prop_atom,
                   timestamp_prop_atom,
                   8, PropModeReplace, &c, 1);
  XIfEvent (xdisplay, &xevent,
            timestamp_predicate, GUINT_TO_POINTER(xwindow));

Last fall, when I was exchanging a bunch of e-mail with Robert Bragg,
author of MetaWM (http://metawm.sourceforge.net/), he raised a similar
issue. When a window manager attempts to select for SubstructureRedirect
events on the root window, it needs to check for an error in response.
(Xlib doesn't intermingle events and errors like XCB does, so XIfEvent
wouldn't solve this problem for an Xlib app, but the similarities are
clear.)

Another common reason for using functions like XIfEvent is to collapse
sequences of related events, to avoid making wasted requests in response
to old but not-yet-processed events. The canonical example seems to be
compressing pointer motion events. Gdk does this under special
circumstances, as do at least some window managers.

In all of these cases, it seems to me that the problem is a desire to
peer into the future, rather than making decisions based solely on the
past. When viewed that way, this is obviously an insane plan, no?

Consider Expose event handling. Many X applications, including all those
using Gdk and presumably Qt, don't immediately paint when they process
an expose event: they add the exposed rectangle to a "region" data
structure and go on to the next event. At some point in the future, they
examine that region and repaint everything inside of it. This is
idiomatic for X: you read some Xlib event loops, you learn to recognize
this pattern.

Gdk does *precisely* the right thing with these exposure regions: on the
first Expose event, it registers a low-priority callback function for
when the Glib event loop becomes idle, and repaints all the collected
exposure regions then. Applications can choose to repaint selected
windows sooner if desired, and one could imagine using a timer to ensure
a maximum response time, but you're guaranteed at least to get repaints
as soon as there's nothing else to do.

I claim that X applications should follow the same pattern for all cases
where current applications try to look into the future. The programmer
can take advantage of knowledge about the particular kind of event being
compressed: in the Expose event case a Region data structure is better
than a list of events, for example.


So how does this apply to Gdk? (The following discussion entirely
applies equally well to both Xlib and XCB, since Xlib certainly allows
you to ignore all "future" events until you reach them in the course of
normal processing. I suggest that these changes would be reasonable ones
to apply to the existing Xlib backend of Gdk, on their own merits and
independent of making the XCB port easier.)

When Gdk compresses MotionNotify and ButtonRelease events, it's trying
to remain responsive while minimizing the number of calls to a function
that updates the position of some widget. The goal is to call update_pos
whenever either a ButtonRelease is encountered, or there aren't any more
MotionNotify events *in the queue*. Obviously, if a drag is in progress
Gdk should call update_pos from an idle callback, eliminating a bunch of
code and that irritating call to XCheckIfEvent.

The other calls to XIfEvent and XCheckIfEvent in Gdk are all there
because they (won't? can't?) just iterate the event loop. As those
callers walk over the event stream, they should handle the events that
they're *not* looking for just like the main event loop does. I think
that gdk_window_add_filter, g_main_iteration, and
gdk_window_remove_filter ought to allow these cases to be cleaned up,
though it's possible that there are constraints I don't understand on
these functions. Here's an example of what I mean for the
gdk_x11_get_server_time function Travis asked about.

        GdkFilterReturn timestamp_filter (GdkXEvent *gdkxevent,
                                          GdkEvent  *event,
                                          gpointer   data)
        {
                XEvent *xevent = (XEvent *) gdkxevent;
                if (xevent->type == PropertyNotify &&
                    xevent->xproperty.atom ==
                      gdk_x11_get_xatom_by_name_for_display
                        (GDK_WINDOW_DISPLAY (event->property.window),
                         "GDK_TIMESTAMP_PROP"))
                {
                        guint32 *time = data;
                        *time = event->property.time;
                        return GDK_FILTER_REMOVE;
                }
                return GDK_FILTER_CONTINUE;
        }
        
        guint32 gdk_x11_get_server_time (GdkWindow *window)
        {
                guint32 time = 0;
                ...
                
                gdk_window_add_filter (window, timestamp_filter, &time);
                while (!time)
                        g_main_iteration (TRUE);
                gdk_window_remove_filter (window, timestamp_filter,
                &time);
                return time;
        }

A harmless refactoring change that can be made to the Gdk Xlib backend
replaces XPending calls with calls to gdk_check_xpending. Both of the
XPending calls that aren't inside gdk_check_xpending itself can be
replaced with 'gdk_check_xpending (display)'. Then the one remaining
event function issue for the XCB port is one call each to XPending,
XPeekEvent, and XNextEvent. You can satisfy all of these with a
look-ahead buffer of one event. So add this to struct _GdkDisplayX11:

        XCBGenericEvent *next_event;

and re-write gdk_check_xpending like so:

        static gboolean
        gdk_check_xpending (GdkDisplay *display)
        {
                GdkDisplayX11 *display_x11 = GDK_DISPLAY_X11 (display);
                if (!display_x11->next_event)
                        display_x11->next_event = XCBPollForEvent (...);
                return display_x11->next_event != NULL;
        }

Then, instead of calling XPeekEvent, just look at next_event; and
instead of calling XNextEvent, copy next_event somewhere and then set
next_event to NULL. Of course, it's possible that the key auto-repeat
code that's using XPeekEvent should be rewritten to look backwards
rather than forwards as well, but given the Glib event source model Gdk
needs an XPending equivalent anyway, so you might as well use the one
event lookahead.

There. Porting event handling is easy, see? I just needed seventeen
hours to write this e-mail, that's all.

OK, that's what I wanted to say. Feedback would be greatly appreciated.

--Jamey

[1] Travis used the terms "queue" and "list" correctly, but I'm fudging.
When I say that Xlib exposes a queue and XCB doesn't, I mean that Xlib's
list-backed queue is traversable and XCB's isn't.



More information about the xcb mailing list