Handling XInput 2 Multitouch Events in Qt (Part 1)

July 13, 2012

So last time, I discovered that Ubuntu 12.04 has patched Qt to support multitouch. But since Qt doesn’t support MT on X11, I’m considering having a layer that allows me to either use Qt’s MT events or to handle XInput 2 events. Like last time… we start with Qt’s fingerpaint application and try to get it working with XInput 2.

To do this, we’re going to sub-class QApplication so that we can re-implement the virtual function QApplication::x11EventFilter().  This allows us to see, inspect, handle, intercept, or pass on any events that the X server sends us. We’ll call our class TApplication and make the function print something out when we get on of XInput’s touch events XI_TouchBegin, XI_TouchUpdate, and XI_TouchEnd:

bool TApplication::x11EventFilter(XEvent *ev)
{
    bool handled = false;
    XGenericEventCookie *cookie;

    // All XI2 events are GenericEvents
    if (ev->type != GenericEvent)
        return false;

    cookie = &ev->xcookie;

    if (cookie->extension != m_xi_opcode)
        return false;

    /**********************************************
     * BEYOND THIS POINT ARE ONLY XINPUT EVENTS
     **********************************************/

    switch (cookie->evtype)
    {
    case XI_TouchBegin:
        qDebug() << "XI_TouchBegin";
        break;
    case XI_TouchUpdate:
        qDebug() << "XI_TouchUpdate";
        break;
    case XI_TouchEnd:
        qDebug() << "XI_TouchEnd";
        break;
    default:
        qDebug() << "XI_somethingelse";
    }

    return handled;
}

Are you going to eat that cookie?

You might ask, “what’s the deal with the cookie?”  An XEvent isn’t actually a C struct, it’s a union of all the different event structs that the X sever uses.  All of them have a ‘type‘ as their first member so that you can discern what kind of struct this is.  We’re only interested in the type GenericEvent.  For these events it means that the ‘ev‘ pointer is a pointer to an XGenericEventCookie struct.  When we do ‘cookie = ev->xcookie‘, it’s equivalent to ‘cookie = (XGenericEventCookie*)ev‘ — but much more elegant.

The GenericEvent is the event type used for extensions to the X Server — things that weren’t thought of when they originally set up the specification.  While the XGenericEventCookie struct has some useful info… what you really want is the data in its ‘data‘ member.  Normally we have to call XGetEventData(), but in Ubuntu’s Qt it already fetches (and frees) it for us.  (See [2] for more details.)

Taking Responsibility

Because Ubuntu is patched with XInput 2 support… it was kind enough to set up everything for us in the background. For example, it tells the X server that we want touch events, pre-fetches our cookie, and then cleans up our cookie crumbs afterwards. However, since we can’t always depend on our mommy (Ubuntu) looking out for us… we need to be Big Boys and set up all the background stuff for ourselves.  We start by disabling multitouch from Qt in the ScribbleArea:

 ScribbleArea::ScribbleArea(QWidget *parent)
     : QWidget(parent)
 {
-    setAttribute(Qt::WA_AcceptTouchEvents);
+    /*setAttribute(Qt::WA_AcceptTouchEvents);*/
     setAttribute(Qt::WA_StaticContents);

If you re-run the application after doing this, you won’t see any X11 events. All events that you want to receive from the X server must be explicitly enabled (even mouse clicks). There is no notion of ‘cascading’ events. Every window. No exceptions. You can’t just set these flags to your top-most parent window and have all the windows get the events. Every single window has to do this for itself.[1]

So instead of setAttribute(), we call our own function:

    TApplication::sendTouchEventsTo(this);

And inside that function we declare that we want touch events for this window:

/* static */
void TApplication::sendTouchEventsTo(QWidget *w)
{
    XIEventMask mask;
    Window win;
    Display *dpy;

    dpy = QX11Info::display();
    win = w->winId();

    memset(&mask, 0, sizeof(XIEventMask));
    mask.deviceid = XIAllMasterDevices;
    mask.mask_len = XIMaskLen(XI_LASTEVENT);
    mask.mask = (unsigned char*) calloc(mask.mask_len, sizeof(char));

    XISetMask(mask.mask, XI_TouchBegin);
    XISetMask(mask.mask, XI_TouchUpdate);
    XISetMask(mask.mask, XI_TouchEnd);

    XISelectEvents(dpy, win, &mask, 1);

    free(mask.mask);
}

This must be run for every widget that you want to have touch events. Using setAttribute() more or less does the same thing.

Notice that we got the Window ID from the widget so that we could give that back to the XServer. The documentation for QWidget::winId() says:

Portable in principle, but if you use it you are probably about to do something non-portable. Be careful.

…This value may change at run-time. An event with type QEvent::WinIdChange will be sent to the widget following a change in window system identifier.

…and they’re not joking, either.  From the time that this code is run in the constructor to the time you receive your first touch event… the window ID changes twice for me.  In fact, I think this may be part of the reason why the pre-compiled fingerpaint demo will sometimes work and sometimes not.  So, obeying the docs, we handle the event:

bool ScribbleArea::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::WinIdChange:
    {
        TApplication::sendTouchEventsTo(this);
    }   break;
    /* ... */

Wrapping Up

You can download the code in the file xinput2-part1.tar.bz2 (or browse it here).  Next time we’ll talk about decoding the events and sending them to our widget.

Resources


[1] – …and every QWidget is an X11 Window (or two or three). That’s one advantage to QGraphicsView and QtQuick — they’re typically all contained in a single window.

[2] – The Qt convention seems to be that XGetEventData() is called before entering QApplication::x11EventFilter() and that XFreeEventData() is called in QApplication::x11ProcessEvent(). This is what the Ubuntu patch does, too. However, I also wrote the code to handle the non-Ubuntu-patched case. See tapplication.cpp for detailed comments in the code.

Advertisements

2 Responses to “Handling XInput 2 Multitouch Events in Qt (Part 1)”

  1. Pierre said

    hi, thank you for this tuto/post.
    i’m try to implement this xinput2 parser for Qt with my device.
    Unfortunatly, I never get the Generic Event.
    I’m on kernel 3.5.4 with xinput version 1.6.0 and XI version on server: 2.2
    the ‘xinput test-xi2’ give me RawTouchBegin/RawTouchUpdate/RawTouchEnd
    detail give me the touch number, and valuators give position.
    what do you think I made wrong.
    Thanks

    • gbeddingfield said

      First… sorry it took so long to approve your post. 🙂

      If you’re using the sendTouchEventsTo() example function… it’s possible that the window ID changed after you ran the function. If that happened, you’ll need to re-run the function on the window. Also, it’s possible that you ran it on a /parent/ window — which isn’t good enough. It has to be /the/ window.

      Another possibility is that there’s another window grabbing the events, but that seems much less likely.

      The program ‘xwininfo’ is a good tool when you’re debugging these things. (E.g. ‘xwininfo -root -all’ will show all windows, their hierarchy, and some of their flags.)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: