http://wearables.unisa.edu.au/mpx/


The use of Xlib for XInput has changed a bit, but not that much. This mini-tutorial shows you how to get the list of devices and how to register for events. For many applications, this is pretty much all you need.

Let's get started. The two most important changes to previous versions of XInput are:

  • applications have to announce support for XI 2 (MPX).
  • the list of input devices contains multiple core devices.

Announcing support for XI 2 is easy, just issue an XQueryInputVersion() before you use any other XI requests. The reason why this is necessary is simple: the behaviour of XInput has changed, and the server must know which clients suports XI 1.x, and which clients support XI 2. This way, it can change some replies accordingly to make XI 1.x clients not break. In practice, your code should look something like this:

int main (int argc, char* * argv) {
    Display * dpy;

    dpy = XOpenDisplay(NULL);
    XQueryInputVersion(dpy, XI_2_Major, XI_2_Minor);

    /* do stuff */

    XCloseDisplay(dpy);
    return 0;
}

Next, we want to know which devices are actually available. XListInputDevices() does this for us. It returns an array with XDeviceInfo structs, each of which specifies the name, id and the capabilities of the devices. In addition, the XDeviceInfo also contains a "use" field, which is of some importance. The use field can have one of 5 values:

  • IsXPointer for any master pointer (i.e. cursor).
  • IsXKeyboard for any master keyboard (i.e. keyboard focus)
  • IsXExtensionPointer for any physical pointer device.
  • IsXExtensionKeyboard for any physical keyboard device.
  • IsXExtensionDevice for any physical device that is neither pointer nor keyboard (this is fairly uncommon).

As a rule, you never register for events on the physical devices. Really. The cursor and the keyboard focus are the user's input points, so focus on them. Only special applications like the GIMP or configuration apps have to worry about the others. Again some code:

XDeviceInfo* info;
int ndevices;
int i;

info = XListInputDevices(dpy, &ndevices);

for (i = 0; i < ndevices; i++) {
    XDeviceInfo* current = &info[i];
    printf("Found device: %s (%d)\n", current->name, current->id);
    switch(current->use) {
        case IsXExtensionPointer:
        case IsXExtensionKeyboard:
        case IsXExtensionDevice:
                /* foo() */
                break;
        case IsXPointer:
        case IsXKeyboard:
                /* bar() */
                break;
    }
}

XFreeDeviceList(info);

Try calling XListInputDevices before and after a call to XQueryInputVersion().You'll notice that the device list is significantly shorter if the server thinks you do not support XI 2.

Ok. Now we know the devices, let's start using them. Writing most multi-device apps is incredibly easy: Open the device, register for events, wait for events to roll in.

XDevice *pointer1;
XDevice *pointer2;
XEvent ev;

pointer1 = XOpenDevice(id1); /* get the ID from ListInputDevices */
pointer2 = XOpenDevice(id2);

/* register for events, see below */

while(1) {
   XNextEvent(dpy, &ev);
   printf("Event type %d received\n", ev.type);
}

Now, I left out the registering for event for now because this is historically painful. You see, the X Protocol specification requires that extension event codes are dynamically assigned at runtime. That is, while the core protocol's type ButtonPress is always the same, the XI type DeviceButtonPress can change from server to server (and even when you restart the server). So we need to get the type. In addition, because we only want to register for certain device's events, we need to get the EventClass. It's easiest to think of the EventClass as the mask to register for a specific event on a specific device. Xlib provides some macros for us here:

int type_bpress;
XEventClass cls[2];

DeviceButtonPress(pointer1, type_bpress, cls[0]);
DeviceButtonPress(pointer2, type_bpress, cls[1]);

Now, type_bpress is set to the event type for DeviceButtonPress events. We can pass the same variable in both times since the type remains static for the lifetime of the server. The class however is device-specific. (Oh - and by the way, the macros re-use the arguments twice, so don't try to pass in something like (evclass++), it can be pain to debug...)

Now we can complete the above example:

XDevice *pointer1;
XDevice *pointer2;
XEvent ev;
int type_bpress;
XEventClass cls[2];

pointer1 = XOpenDevice(id1); /* get the ID from ListInputDevices */
pointer2 = XOpenDevice(id2);

/* register for events */
DeviceButtonPress(pointer1, type_bpress, cls[0]);
DeviceButtonPress(pointer2, type_bpress, cls[1]);
XSelectExtensionEvent(dpy, myWindow, cls, 2);

while(1) {
   XNextEvent(dpy, &ev);
   printf("Event type %d received\n", ev.type);
   if (ev.type == type_bpress)
   {
        XDeviceButtonEvent* bev = (XDeviceButtonEvent*)&ev;
        printf("Press received by device %d\n", bev->deviceid);
   }
}

Here's another important change in XI 2. Events always contain coordinates in the screen's coordinate system, only valuators are device-specific. So for most apps, you don't have to worry about coordinate conversion, just use x_root, y_root or x/y of the XDeviceButtonEvent structure.

That's it for now. There's a number of other macros such as DeviceMotionNotify, all to be found in XInput.h. All the functions described here have man pages that explain the parameters better than I can.


Reply via email to