Xinput 1 -> XInput 2 conversion guide

Posted by Benjamin Close on July 30, 2009 under Computers, FreeDesktop, OpenSource, Programming | 2 Comments to Read

Recently I was tasked with porting GTK+ to support Xinput 2 (XI2), and whilst the port isn’t finished I quickly discovered just how big the conversion was. This page is designed to help someone who is familiar with XInput 1 switch to XInput 2 – it’s certainly is by no means a complete guide and anyone reading this should also take a look at the various Input2 recipies published.

XI2 is the first major rewrite of the XInput specification in years and with it comes a lot of enhanced functionality, but also a new API. The API was deliberately designed not to be backward compatible. Why? To prevent both XI1 and XI2 features to be intermingled. XI2 brings with it a new device model and a new input model. Both of these are incompatible with XI1. The decision to break the API was not a light one by Peter Hutterer, the main XI2 developer. Supporting XI1 would have ment future changes to XI2 would be impossible – there’s only so far you can push an out dated protocol and api. The added complexity of trying to maintain 2 code paths was also determined to be too high. XI2 is more than just a api/abi change. It literally change the entire input subsystem of the Xorg Xserver. XI2 supports:

With that in mind, the new XI2 API often leaves someone who’s been working with the XI1 wondering what the equivilant XI2 API call is. Most functions have an equivilant in XI2 function though the usage has more often than not, changed dramatically. Below is some examples of how to convert XI1 code to XI2 compatible code and a list of some of the changes which have occurred. If you find something missing, please add to the comments.

Change Overview

The biggest change that any XI1 developer will notice is the XDevice * type has vanished. All XI2 devices make use of the XID type instead. Under the hoods, in the Xserver and XDevice really equates back to an XID anyway now. The next biggest change XI1 developers will notice is the entire XInput2 API has had shifted namespaces. All XInput2 functions, types, etc are now prefixed with XI (ie XIEventMask). This was designed to force segregation of XI1 -> XI2 code.

The header file you include has also changed. Instead of:

#include <X11/extensions/XInput.h>

It’s now:

#include <X11/extensions/XInput2.h>

Notable Changes

  • Proximity events are no longer supported, these are now reported as regular events on another axis (ie think of a pressure axis)
  • Events are longer use device classes, instead all events are type XIEvent (a type of XEvent). Hence there is no need to pass around an array of integer classes anymore when registering for events, instead a single XIEventMask will do what is needed. The macros XISetMask and XIGetMask help with using the mask.

There’s also been a lot of changes to structures, functions, types and how they are used.

Event Changes

XI1 Event XI2 Event Notes
XDeviceKeyEvent,  XDeviceKeyPressedEvent, XDeviceKeyReleasedEvent, XDeviceButtonEvent,  XDeviceButtonPressedEvent,  XDeviceButtonReleasedEvent ,XDeviceMotionEvent XIDeviceEvent The various events are now incorporated into the one event type. XIDeviceEvent->evtype indicates the subtype of event. Ie XIMotion is a motion event for that device.
XProximityNotifyEvent, XProximityInEvent, XProximityOutEvent XIDeviceEvent (See Note) Proximity Events have been replaced with device events. Proximity is represented as a value in the valuator on one of the axes

Structure/Type Changes

XI1 Structure/Type XI2 Structure/Type Notes
XDeviceState XIDeviceInfo XIDeviceInfo also contains a name and classes
XAnyClassPtr XIAnyClassInfo *
XValuatorInfo XIValuatorClassInfo
XInputClass XIAnyClassInfo
XEventClass XIEventMask Used for selecting events to be monitored.
One mask is used per device. Standard usage is:

XIEventMask mask;
mask.deviceid=//some device or XIAllDevices for all devices&lt;
mask.mask_len=2;
mask.mask=calloc(mask.mask_len, sizeof(char));
XISetMask(mask.mask, XI_ButtonPress);
..
XISelectEvents(display, win, &mask, 1)
XExtensionVersion Struct Removed XIQueryVersion should be used instead
XDeviceKeyEvent, XDeviceKeyPressedEvent, XDeviceKeyReleasedEvent, XDeviceButtonEvent, XDeviceButtonPressedEvent, XDeviceButtonReleasedEvent, Structs Removed XIDeviceEvents are now used instead
XDeviceFocusChangeEvnet, XDeviceFocusInEvent, XDeviceFocusOutEvent Structs Removed XIDeviceEvents are now used
XProximityNotifyEvent, XProximityInEvent, XProximityOutEvent Structs Removed XIDeviceEvents are now used

Function/Macro Changes

XI1 Function XI2 Function Notes
XFreeDeviceList XIFreeDeviceInfo
XDefineDeviceCursor XIDefineCursor
DeviceButton1Motion

DeviceButton2Motion

(No Equivelant)
XWarpPointer

XWarpDevicePointer

XIWarpPointer XIWarpPointer makes use of the device id like XWarpDevicePointer (XI1.5). XWarpPointer is considered obsolete as it has no concept of a device
XQueryDeviceState XIQueryDevice Return type is XIDeviceInfo *, can be called with deviceid = XIAllDevices to query all devices hence the ndevices return
XFreeDeviceState XIFreeDeviceInfo
DeviceGrabButton XIGrabButton
XSelectExtensionEvent XISelectEvents Instead of a list of classes now a list of XIEventMask is used, one mask per device. XSelectExtensionEvent is still used for other extension events not related to XInput2
XGetExtensionVersion XIQueryVersion If only XI1 is present this will be returned vi the major/minor numbers (ie major = 1)

Examples

Below are some examples of how to use some of the new XI2 functions.

Event Processing – Registering For Events

A simple indication how to register for events (XI2 greatly simplifies this)

static int           motion_type = INVALID_EVENT_TYPE;
static int           button_press_type = INVALID_EVENT_TYPE;
static int           button_release_type = INVALID_EVENT_TYPE;
static int           key_press_type = INVALID_EVENT_TYPE;
static int           key_release_type = INVALID_EVENT_TYPE;
static int           proximity_in_type = INVALID_EVENT_TYPE;
static int           proximity_out_type = INVALID_EVENT_TYPE;
 
static int register_event(Display *dpy, XDeviceInfo *info)
{
    XEventClass         event_list[7];
    int                 i;
    XDevice             *device;
    XInputClassInfo     *ip;
 
    device = XOpenDevice(dpy, info->id);
 
    // Check for open error
    if (device->num_classes > 0) {
        for (ip = device->classes, i=0; i<info->num_classes; ip++, i++) {
            switch (ip->input_class) {
            case ButtonClass:
                DeviceButtonPress(device, button_press_type, event_list[number]); number++;
                DeviceButtonRelease(device, button_release_type, event_list[number]); number++;
                break;
 
            case ValuatorClass:
                DeviceMotionNotify(device, motion_type, event_list[number]); number++;
                if (handle_proximity) {
                    ProximityIn(device, proximity_in_type, event_list[number]); number++;
                    ProximityOut(device, proximity_out_type, event_list[number]); number++;
                }
                break;
 
            default:
                fprintf(stderr, "unknown class\n");
                break;
            }
      }
 
   if (XSelectExtensionEvent(dpy, root_win, event_list, number)) {
            fprintf(stderr, "error selecting extended events\n");
            return 0;
        }
   }
  • XInput 2:
    (Taken from: http://who-t.blogspot.com/2009/05/xi2-recipes-part-1.html)
  • XIEventMask eventmask;
    unsigned char mask[1] = { 0 }; /* the actual mask */
     
    eventmask.deviceid = 2;
    eventmask.mask_len = sizeof(mask); /* always in bytes */
    eventmask.mask = mask;
    /* now set the mask */
    XISetMask(mask, XI_ButtonPress);
    XISetMask(mask, XI_Motion);
    XISetMask(mask, XI_KeyPress);
     
    /* select on the window */
    XISelectEvents(display, window, &eventmask, 1);

Event Processing – Listening/Processing Events

A simple indication how to get events from XI1 and XI2

  • XInput 1:
    //setup via other means
    int motion_type, button_press_type,button_release_type;
     
    void doEvents(Display    *dpy)
    {
     
     XEvent        Event;
     
        while(1) {
            XNextEvent(dpy, &Event);
            if (Event.type == motion_type) {
                XDeviceMotionEvent *motion = (XDeviceMotionEvent *) &Event;
                ...
     
            } else if ((Event.type == button_press_type) ||
                       (Event.type == button_release_type)) {
                          XDeviceButtonEvent *button = (XDeviceButtonEvent *) &Event;
     
                ...
            }else if ((Event.type= .... )){
                ...
         }
    }
  • XInput 2
    //Somewhere else...
     
    int xi_opcode;
    if (!XQueryExtension(display, "XInputExtension",&xi_opcode,&event, &error)) {
    printf("X Input extension not available.\n");
    return EXIT_FAILURE;
    }

    void doEvents (Display *dpy )
    {
        while(1) {
            XEvent        ev;
            XGenericEventCookie *cookie = &ev.xcookie;
            XNextEvent(dpy, &ev);
     
            if (XGetEventData(dpy, cookie) && cookie->type == GenericEvent
                && cookie->extension==xi_opcode)
            {
                XIDeviceEvent *event = cookie->data;
     
                printf("EVENT type %d\n", event->evtype);
                switch (event->evtype)
                {
                    //
                    // In all below event>deviceid contains the id of the device
                    //
                    case XI_DeviceChanged:
                        XIDeviceChangedEvent *dc = cookie->data;
                        ...
                        break;
                    case XI_HierarchyChanged:
                        XIHierarchyEvent *he = cookie->data;
                        ...
                        break;
                    case XI_RawEvent:
                        XIRawEvent *re = cookie->data;
                        ...
                        break;
                    case XI_FocusIn:
                    case XI_Enter:
                        XIEnterEvent *ee = cookie->data;
                        ...
                        break;
                    case XI_FocusOut:
                    case XI_Leave:
                        XILeaveEvent *le = cookie->data;
                        ...
                        break;
                    case XI_PropertyEvent:
                        XIPropertyEvent *pe = cookie->data;
                        ...
                        break;
                    case XI_Motion:
                    case XI_ButtonPress:
                    case XI_ButtonRelease:
                    case XI_KeyPress:
                    case XI_KeyRelease:
                        // do something with event (XIDeviceEvent contains data)
                        break;
                }
            }
         }
      }

jhbuild just won’t build! (aka Include path madness)

Posted by Benjamin Close on November 18, 2008 under FreeDesktop, OpenSource, Programming, UniSA | Read the First Comment

I’ve been recently trying to work out why jhbuild fails to build xorg on my FreeBSD box. Traditionally I compile to /usr/local/ however after wanting to experiment with MPX I’ve set things up so that I compile to /usr/local/MPX

Sadly this kept breaking in xorg/lib/libX11 with the error:

Read more of this article »

stl::set

Posted by Benjamin Close on November 13, 2008 under Programming | 3 Comments to Read

Working with Mark’s dnl code, we came across an interesting issue. Mark had used the stl::set class for some of his code and whilst it compiled fine under Visual Studio, it failed to compile under gcc/g++. With the error:

dnlCommon/Logger.cpp: In member function 'void dnlCommon::Logger::setMask(std::ostream&, std::bitset<5ul>)':
dnlCommon/Logger.cpp:64: error: invalid initialization of reference of type 'dnlCommon::Logger::LoggerPair&' from expression of type 'const dnlCommon::Logger::LoggerPair'
/usr/local/lib/gcc-4.0.4/include/c++/bits/stl_algo.h: In function '_OutputIterator std::remove_copy(_InputIterator, _InputIterator, _OutputIterator, const _Tp&) [with _InputIterator = std::_Rb_tree_const_iterator<dnlCommon::Logger::LoggerPair>, _OutputIterator = std::_Rb_tree_const_iterator<dnlCommon::Logger::LoggerPair>, _Tp = dnlCommon::Logger::LoggerPair]':
/usr/local/lib/gcc-4.0.4/include/c++/bits/stl_algo.h:1112:   instantiated from '_ForwardIterator std::remove(_ForwardIterator, _ForwardIterator, const _Tp&) [with _ForwardIterator = std::_Rb_tree_const_iterator<dnlCommon::Logger::LoggerPair>, _Tp = dnlCommon::Logger::LoggerPair]'
dnlCommon/Logger.cpp:54:   instantiated from here
/usr/local/lib/gcc-4.0.4/include/c++/bits/stl_algo.h:1037: error: passing 'const dnlCommon::Logger::LoggerPair' as 'this' argument of 'dnlCommon::Logger::LoggerPair& dnlCommon::Logger::LoggerPair::operator=(const dnlCommon::Logger::LoggerPair&)' discards qualifiers

It turned out that the issue occurred with the assignment operator (operator = ) on the stl::set class. Tracing the problem we found that set<TYPE>::iterator is const rather than non cost. Hence calling stl functions that made use of the iterators but also the assignment operator would fail.

Looking further into the problem I found:

      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 103. set::iterator is required to be modifiable,
      // but this allows modification of keys.
      typedef typename _Rep_type::const_iterator iterator;
      typedef typename _Rep_type::const_iterator const_iterator;

Hmm, that’s strange. A bit of googling revieled the problem to be an issue with the stl standard. Fixes to gcc/g++ are listed at http://gcc.gnu.org/onlinedocs/libstdc++/ext/howto.html in particular error 103 is listed at http://gcc.gnu.org/onlinedocs/libstdc++/ext/lwg-defects.html#103
So it looks like the bug was ratified by the C++ committee in 1998 and it still isn’t fixed in Visual Studio 2005 (VC++ 8.0) !

Stl::reverse iterator

Posted by Benjamin Close on under Programming | Be the First to Comment

I was coding a little program a while back when I found a Quirk about the stl reverse_iterator. Use of the iterator is quite easy until you have to delete from one. I tried the normal way of:

vector<sometype>::reverse_iterator myiterator...
..
..
myvector.erase(myiterator).

only to find the code didn’t compile. A little baffled I started to looking into why. It turns out you Can’t’ delete from a reverse iterator directly. So the question is How do you delete from a reverse iterator?.

Not wanting to do a full forward traversal to get to the same point I did some Googling. Eventually I found the page: http://www.ddj.com/dept/cpp/184401406

which explains why you can’t delete from the reverse iterator. More importantly it indicates a way that you can delete from the reverse_iterator. So how?

 void Manager::raiseWindow(WMWindow* wmwindow)
 {
   vector<WMWindow*>::reverse_iterator it = windows.rbegin();
   while(it != windows.rend())
   {
     if (*it == wmwindow)
     {
      // why ++it see http://www.ddj.com/dept/cpp/184401406
      windows.erase((++it).base());
      windows.push_back(wmwindow);
      break;
     }
     it++;
   }

   wmwindow->raise();
 }

</code>

Gives an example of how to do it. However, be aware, doing the above erase, invalidates the iterator so don’t go trying to use it again afterwards! }

gdb

Posted by admin on November 7, 2008 under Computers, Programming | Read the First Comment

This page documents some tips on how to use GDB for advanced debugging. It was created as all information I could find on the web lacked some useful information.

 

Read more of this article »

CVS

Posted by Benjamin Close on under Programming | Be the First to Comment

This page contains various helpful hints for using CVS.

Read more of this article »

Autotools

Posted by Benjamin Close on under OpenSource, Programming | Be the First to Comment

The Autotools build system is a great system. It consists of a number of tools. These are

aclocal[1]autoconf[2]automake[3]autoheader[4], and to some extent libtool[5]

These tools all work together to aid in building applications, libraries and providing a consistent framework for doing so. The problem however, is the tools are hard to use, poorly documented and the learning curve to getting things done is extremely high. Added to this, the tutorials on the web show only the basics of using the system and leave all the useful stuff to the user. The problem is the useful stuff is hard to work out.

Read more of this article »

Was this helpful?


Throw some money in the tip jar