Friday, December 13, 2013

The T440 has a rather unusual touchpad with the buttons painted on top of the touchpad rather than the bottom. In addition, the separate set of buttons for the trackstick have gone the way of the dodo. Moving the software-emulated buttons up on the touchpad is obviously quite important for trackstick users but it throws up a bunch of problems. There are some limitations with the current synaptics X.Org driver: we can only have one region each designated for the right and the middle button. The rest of the touchpad is a left button click. In the case of the T440, the default Windows config has a right button up the top and another one at the bottom of the touchpad. An ASCII-art of that would look like this:

Drop that into /etc/X11/xorg.conf.d/99-t440-synaptics.conf and you're good to go.

The problem is finding a generic solution to this that we can ship in a distribution. That requires a two-step progress. The touchpads look the same as all others, the only differentiator we have is the DMI information on the box. We can't check that in the xorg.conf snippets yet (Daniel Martin is working on a MatchDMI tag, but it won't happen until server 1.16). For now, we need a udev rule to help the xserver.

I've pushed this configuration into Fedora now (rawhide, F20, F19), let's see what the feedback is. Having the whole right-side of the touchpad work as right button may cause a few issues so this is one change I may have to revert in the future.

Thursday, December 12, 2013

If you don't have one of the Wacom serial devices, you probably don't need to worry about the below. If you do, read on.

Wacom serial devices are supported by the xf86-input-wacom driver directly, but a much nicer way is to have the kernel deal with it and thus the device exposed as a normal evdev device to userspace. This way, the device is also picked up by libwacom and, in turn, by the GNOME Wacom panel for configuration and calibration. For the device to work as evdev device you need inputattach (in the linuxconsoletools package on Fedora).

The simplest approach if your device is connected to /dev/ttyS0 is:

$> inputattach --daemon --w8001 /dev/ttyS0

but of course we want to automate this. We used to have a udev rule that fires up inputattach but recent changes [1] in udev broke that ability: udev is now decidedly for short-running processes only, long-running processes started by a udev rule will be killed. The solution to that is a udev rule paired with a systemd service.

First, the systemd wacom-inputtattach@.service service file, which is quite simple:

Note the @ in the name, we use what comes after through the %I directive as the device file, e.g. ttyS0. Note also that we don't use the --daemon flag because we want systemd to take care of our process instead of forking it and letting it guess what the PID is, etc. This service can now be started with

$> systemctl start wacom-inputattach@ttyS0.service

and of course the information is available in the journal, etc.

Now we just need something to start the service automatically, and that's handled in a simple udev rule. Here's the whole udev file for easy copy/paste:

Wacom's serial devices have IDs starting with WACf, so we match on that. The first two "SUBSYSTEM..." lines match and set the name and some ID_foo tags so that the server recognises the tablet as input device and can match against xorg.conf.d InputClass snippets. With just those first two lines, we will have the xf86-input-wacom driver work for serial devices, even if inputattach didn't start. These two lines haven't changed in years either. The new and systemd-specific bit is the third line. Again we match but this time we set the systemd tag, tell systemd the service we want to start (%k is replaced with the kernel device, e.g. ttyS0) and we're done. Reboot [2] and you should be the happy viewer of something like this:

[1] I say recent but really, it could've been any time since F18 or so, I don't test this particular device very often
[2] yeah yeah, there's a command to trigger this directly, you don't need to reboot, I know

Wednesday, December 4, 2013

I guess about 80% of the time coding is spent handling error messages and dealing with the situation where the input isn't what it should be. That's ok for a real project, but sometimes it's just easier to say "this shouldn't happen but make sure I see an error if it does". For example when prototyping, it's enough to know that something went wrong so we can throw out the output. Or get notified when a value that should be within a range is outside that range, etc.
The traditional way of that is assert(3), which checks an expression and aborts the program when the condition is not met. That is a heavy hammer, and not flexible enough in many cases.

So that got me thinking: essentially I wanted a set of macros that I can throw into any function to alert me when something goes wrong. Similar to the BUG_ON in the kernel, or the BUG_WARN in X. But a bit more powerful than that, because the main issue I have with BUG_WARN is that it tells me when a condition fails, but not necessarily why, or what the condition was supposed to check. Two revisions later, Rusty Russell merged my new argcheck CCAN module. It provides a set of macros for error checking that do not abort and, more importantly, also work as conditions. The simplest use-case is:

If percent is outside the [0..100] range, a message with the file, function and line number is printed. And, possibly more useful, it will also print the actual value of percent. It gets more interesting when we use symbolic names, and we use the whole things as a condition:

So not only does it give us the actual values, it also provides the symbolic names (if there are any). Which, for debugging purposes, makes the whole thing quite useful. And we don't need to double-evaluate, we can use the macro as condition. argcheck takes care to only evaluates its arguments once, so there shouldn't be any side-effects. This also goes for disabling argcheck. If ARGCHECK_DISABLE_LOGGING is defined when including argcheck.h no messages are logged but the conditions still exist and the code runs as before. The same goes for a user-defined argcheck_log function (in case you need to override the default fprintf(stderr)).

I've added quite a few macros for various use-cases, including:

argcheck_flag_set(a, flag) - false if the flag isn't set, but also checks that the flag is not 0

argcheck_str_not_zero_len(str) - false if the string is NULL or the empty string

argcheck_str_max_len(str, len) - false if the string is NULL, or longer than len. Works great to warn about truncated string copies.

argcheck_ptr_null(ptr) - false if the pointer is not NULL. Works great to spot uninitialized variables.

Any comments or feedback let me know, I'm eager to improve this to make it even more useful (it already saved my hide a few times)

Friday, November 22, 2013

No, don't worry, evtest isn't actually dead. I'll keep maintaining it (it's not a lot of work, after all). But I'll discourage its use and in the future you should be using evemu instead.

evtest is a tool that prints out a human-readable description of an evdev kernel device and its events. evemu does the same, but it also records the events in a format that can be parsed and re-played easily on another machine, making it possible to reproduce bugs easily.

evemu was originally written by Henrik Rydberg, had a stint on launchpad and moved to freedesktop.org earlier this year. Since then, Benjamin Tissoires and I have been working quite a bit on it, trying to make improve on the usability. We're still polishing a few things, but since version 1.1 we have a UI that I'm now reasonably happy with it. And Benjamin we just released 1.2.

evemu has two modes: recording mode and replaying mode. In recording mode, evemu records the bits that the kernel exports for a device and writes them into a file. This includes both the device description and the events (if any) from the device. In replaying mode, evemu creates a virtual (uinput) device that looks exactly the same as the original file [1] and replays the events in the same order and timeframe. The result is that if you record your device and send it to me, chances are I can reproduce the bug without having the hardware. And of course, for the nastier bugs having a recording that reliably reproduces something is great for testing.

Let's look at it in more detail. The first step for a reporter is evemu-record [2].

This is obviously the list of devices on my machine, selecting a number will record that device (e.g. 4 will start recording the touchpad).
Recording the output produces a bunch of comments describing the device in human-readable form, followed by the bits that we use within evemu.

With the bits we need to replay on the left, and comments for humans on the right.

The comments with the human-readable description are new in version 1.1.0 which makes it finally useful as a replacement for evtest. Previously I had to either replay a device and look at the replay with evtest to make sense of something. But then again, previously the evemu repo was on launchpad, so having to use two tools was less painful than working with bzr...

To replay a device, you'll need two tools: evemu-device and evemu-play. The former creates a device based on the device description. The latter pumps the events into the newly created device.

And that's it. You've created a new device and that device now spits out the events as recorded. evemu-play will replay the events so that the timing is as close as possible to the original recording, meaning that you can reproduce most bugs.

Speaking of bugs: if you have an input-related bug that is triggered by a given event sequence, record your device. Make sure the recording reproduces the bug and attach the file to the bugreport. I almost always ask for an evemu recording anyway, so having that ready just speeds up the process.

[1] uinput doesn't let you set some fields but that is fine for 95% of the use cases
[2] There used to be evemu-describe, but that's a symlink to evemu-record now

Actually typed this in English first and then felt a bit silly. Anyway, here's the English version:

Ok, I'm a bit late here because I didn't notice and apparently most Fedora users with a German keyboard layout didn't either. Nevertheless, a PSA: the keyboard has changed upstream in version 2.8 which is the one we're shipping in Fedora 19 and thus later versions as well.

The tilde "~" key, on AltGr and the + key, left of the Enter key, used to be a dead key. Dead keys allow multi-key combinations. In this specific case the dead key tilde followed by n would produce ñ. It also means that to type an actual "~" the tilde key had to be typed twice. This layout was against the DIN norm and differed from the default Windows layout too, so it was changed to a normal tilde (see fdo bug 9753, opened in 2007!).

If you prefer the old workings, you'll need to switch to the German(legacy) variant. If you configure with setxkbmap, simply use setxkbmap -layout "de(legacy)" or setxkbmap -layout "de" -variant "legacy". If you're using GNOME, the configuration is a little hidden. Fire up the "Region & Language" dialog in the control center ("Region und Sprache"), hit the little + button and then the three vertical dots which open up the search field.

Search for "legacy" and you'll see German(legacy) pop up. Select it and you're good to go. If you have your desktop in German, the entry to search for "Deutsch (veraltet)".

Thursday, October 3, 2013

X.Org is not a single thing. One the one hand, X.Org is a collection of
projects that provide a window system stack, most notably the X server, its
drivers and Xlib. Some other tools are part of the X.Org software set (see
the X11R7.7 katamari
release).
Additionally, there are projects that are more or less associated with X.Org
but aren't necessarily X.Org (depends on who you ask, and it doesn't really
matter anyway for this post). These projects include Mesa and Wayland.
Let's call this the "Software".

On the other hand, X.Org is the X.Org Foundation, a non-profit corporation
to help advance X.Org and related projects. Let's call this the
"Foundation". Disclaimer upfront: I'm the current Secretary for the X.Org
Foundation.

The Software and the Foundation are not the same thing, but they do share
the name. The Foundation has zero technical influence over the
development of the Software, it cannot direct roadmaps, it cannot decide on
releases, it can't decide on technical features. The Foundation does
have money it can provide to developers of the Software though. This is done
indirectly, e.g. through financing developer conferences (XDC) and providing
travel sponsorship for attendees. The Foundation backs the
Endless Vacation of Code
(think Google Summer of Code without specific start dates).

Generally, what happens with the Foundation doesn't really matter to the
Software and that is by design. If the Foundation goes away, the worst
thing that would happen to the Software is that funding for conferences
would have to come from elsewhere. Think of the Foundation as being the rich
auntie for the developers. You can ask her for money to travel, but she
doesn't participate in day-to-day development work.

Recently, you may have read that the Foundation lost 501(c)(3) status
(temporarily anyway). For the non-US readers, 501(c)(3) is US legalese for a
non-profit organisation which allows for some donations to be tax-deductible.
The loss of the 501(c)(3) status caused the usual internet outrage and
insults towards the Foundation and the Software, so let's look at that in a
bit more detail.

The day-to-day work of the Foundation are largely handled by the Board of
Directors, 8 elected members in two-year terms, 4 of which are up for
election every year. One treasurer, one secretary, pretty much standard
stuff. The board has regular meetings, but if they get your blood
pressure up you really need to see a doctor.

Gaining, losing and re-gaining 501(c)(3) status

The reason we even have that status is historical. Back a few years ago the
old X Consortium got regular chunks money from large corporations (Sun, HP, to
name a few). After the change to X.Org, setting that up for tax-deductible
donations seemed like a good idea. Fast-forward a bit and with the help of
the great people at the SFLC X.Org got 501(c)(3) status in 2012
(retroactively applied to 2009).

Something else happened while fast-forwarding: we never actually solicited
or took any donations. The financial crisis had hit, so there wasn't that
much money around and anyway we had enough money to run conferences for
years to come. The closest thing to a donation we got was to allow
interested parties to host beer bashes at our conferences.

So since 2005, we've been slow-burning through money and at the current rate
we still have enough money for a couple of years of conferences and travel
sponsorship. 501(c)(3) limits the ways how to spend the money
(see
Wikipedia), so we pretty
much only spend it on conferences and travel.

So the summary: the Foundation is a 501(c)(3) that doesn't collect donations. But
if we did, they'd be tax-deductible. As long as you're in the US, that is.
Non-US people pretty much have nothing to gain either way since we're not
registered in any other country and 501(c)(3) doesn't apply to other (any?)
countries. (Correct me if I'm wrong. Also: don't listen to me for tax advice)

A couple of months ago 2013 we lost 501(c)(3) status. The reason we lost
it: we didn't file the required tax forms. As said above, no donations means
no income, so we never actually owed any taxes and we hadn't filed tax forms
for years before we go the 501(c)(3) status. So the IRS removing our status was a bit of a surprise. I should
also note that the Foundation is one step removed from from the IRS
communication: the IRS contacts the SFLC who then contacts us.

Anyway, once again the great people at the SFLC helped us and we got the
501(c)(3) re-instated. So we're back to normal, not collecting
tax-deductible donations. With the additional benefit of having
learned to double-check tax forms from now on.

Summary

We had 501(c)(3) status but didn't collect donations. Then we lost it, and
got it back, all the time not actually collecting donations. There was no
money owed to the IRS. No doubt: It's embarrassing, it shouldn't have
happened. But in reality it had zero effect.

So pack up the pitchforks, but keep them well oiled, I'm sure there's a
reason to get them out soon again.

Final note: you may have heard that the Foundation is planning to join
SPI. This is correct but still ongoing and
I'll talk about that when it's actually done.

Wednesday, September 18, 2013

What is uinput?

uinput is the kernel interface to create evdev devices that, for most purposes, look the same as real devices. This goes so far that around 80% (well, I'm guessing. actually, make this 83.45%) of all testing I do now is with emulated devices only. There are a few bits that can't be emulated, a few things that are different, but generally I found uinput devices to be close enough to the real thing. As the evdev interface, the uinput interface requires you to handle a few structs and ioctls, not necessarily in an obvious way. libevdev wraps that for you.

Creating a device

The simplest way to create a uinput device is to duplicate an existing device.

The above code will create a device from a fd, duplicate that device as a uinput device and then post a x/y relative event through that uinput device. Because we opened the uinput device as LIBEVDEV_UINPUT_OPEN_MANAGED, libevdev will handle access to the /dev/uinput node.

Duplicating devices is useful, but a more likely use-case is to create a device from scratch:

This time we created a blank device, set a few bits and created a uinput device from that. The result should be a device that looks like a normal three-button mouse to most of the stack.

As you can see, because this time we opened /dev/uinput ourselves, we need to close it ourselves too. libevdev won't touch the fd unless it's in LIBEVDEV_UINPUT_OPEN_MANAGED mode. Note that you can only ever have one active uinput device per fd, and closing the fd will destroy the uinput device (but won't free the memory, you'll still have to call libevdev_uinput_destroy).

Accessing uinput devices

We just created a uinput device, but how do we actually use it? Well, as shown above events are just written to the device directly. But sometimes we have to create a device and re-open it through libevdev.

Voila. That's all there is to it to complete the circle. You can now use that device to create a uinput device again, and so on, and so forth.

A word of warning: the kernel does not (yet) provide an ioctl to get the device number from a newly created uinput device. libevdev has to guess what the device is going to be. In some cases, this guess may come up with the wrong device. This can happen if you create multiple uinput devices with the same name at the same time. So, don't do that. Either change the name, or delay creation so that the timestamp (one-second resolution!) differs for each device.

Changing a device

So you have a device but for some reason it doesn't exactly reflect what you actually need. This can happen for broken devices that export random axes, or it can happen if the software stack needs certain bits that the device doesn't actually provide. The code below is C-style pseudocode, don't expect to be able to directly take and compile it.

Simple enough - we've disabled ABS_RX, so we'll never get an event from this instance. Note that this is a local change only, so anyone else reading the device will still receive ABS_RX events. Likewise, we enabled ABS_PRESSURE so that future calls to libevdev_has_event_code will return true, including the axis range we've provided. This too is a local change only and won't affect anyone else. For obvious reasons, enabling a bit on the device doesn't actually make the device generate events of that type. Otherwise, the HW industry would be out of business quickly.

For local changes, libevdev provides setters for almost every field. I won't go into more details here, the API should be obvious enough so that e.g. changing the device name is straightforward.

Modifying the kernel device

A few calls can actually modify the kernel device, so that other readers of the device will see modified data.

This call would actually change the axis ranges on the device. A future reader of the device would thus see the new range. Note that the kernel won't enable the bits as you upload the new data, so you can't actually create new axes on the device, only modify existing ones.

This post describes how to read input events from the kernel through the new libevdev library.

What is libevdev?

libevdev is a wrapper library to access /dev/input/eventX devices and provide their events through a C API. It buffers the device and is essentially a read(2) on steriods. Instead of read(2) on the file descriptor, you'd call libevdev_next_event() to fetch the next event that is waiting on the fd. And the buffering allows a process to access device data easily.

Why use a library though? The kernel interface is relatively simple, but it has a few pitfalls. For one, device data is accessed through ioctl(2) and can cause weird bugs [1]. Second, not all events work in the same way. e.g. EVIOCGABS doesn't work the same for multi-touch axes, simply because the slot protocol has different semantics than the normal EV_ABS protocol. EV_REP has different handling as EV_ABS, EV_SYN is a special case anyway, etc. libevdev tries to avoid having to think about the differences and does sanity checks for the various calls.

Status of libevdev

libevdev is currently in version 0.4, and the current API is expected stable. That is, we don't foresee any changes unless we discover some severe bug. If that is the case, I will update the blog post here.

Example code

The code snippets below are in C-style pseudocode. You won't be able to just take them and compile them, but look at libevdev-events for a real tool that does almost everything described below.

Initializing a device

The first step to get a device is to open it. That is not actually handled by libevdev directly, rather it expects an already opened file descriptor. The reason is simple: reading /dev/input/event devices usually requires root and the process accessing the device may not have these permissions. In weston for example, the fd is passed from the suid weston-launch binary. Ok, enough talk, let's see some code:

Getting information about the device is done by simply calling the various getters. And checking the device for functionality is done by checking the various event codes we care about. Note that the above code checks for the EV_REL event type first, then for the actual axes bits. This is just for completeness, it is not necessary. Checking for an event code also checks for the event type so we can skip libevdev_has_event_type(). Both approaches are allowed of course, whichever makes you feel more comfortable about the code.

Finally, cleaning up: Because we don't handle the fd in libevdev, we just use it, you'll have to close that separately.

Ok, the gist of how to access a device should be clear. Let's move on to
reading events from the device

Reading events

In the standard case, we just want to get the next event and process it.

The error handling should be clear by now: negative errno means something
has gone wrong. Except -EAGAIN, wich indicates that there are no events
to read at the moment. A return value of LIBEVDEV_READ_STATUS_SYNC is special, it signals a
SYN_DROPPED event which I'll describe later.

A return value of LIBEVDEV_READ_STATUS_SUCCESS means success, so we know we have an event and we can
print it. libevdev provides some helper functions to print the string value of
an event type or code. The code above could, for example print something like
this:

We have an event!
2 (EV_REL) 0 (REL_X) value -1

As you can see, all this effort just to read the same thing off the kernel
device that you would've otherwise with a read(2) call. But wait! There's
more!

Event buffering

libevdev buffers events internally and always tries to read the maximum number
of events off the kernel device. So when you call libevdev_next_event,
libevdev may read 50 events off the fd (or whatever is available) and only
give you the first. On the next call, it will simply give you the second event of
those first 50, but try to read more again to keep the kernel buffer as empty
as possible.

Whenever you request an event, libevdev will update its internal state to
match the current device state so the client doesn't have to. So if you need
to keep track of button states, you can rely on libevdev:

If no button is pressed, then pressed before the next event is read, this
snippet would print "Button is up" and "Button is down". Fairly obvious, I
think.

Important to point out is that the device state is always the state as seen
by the client, i.e. if the client would keep track of the device state
based on the events libevdev hands to it, libevdev and the client would always
have the same state. Why is this important? libevdev reads multiple events off
the wire whenever a client calls libevdev_next_event, but these events
do not update the state of the device until passed to the client. So
again, since libevdev reflects the state as seen by the client, the client
doesn't need to keep track of the state itself. Winners all 'round.

SYN_DROPPED device syncing

A EV_SYN/SYN_DROPPED event is relatively recent (kernel 2.6.39). If a device
sends events faster than userspace can read it, eventually the kernel buffers
are full and the kernel drops events. When it does so, it sends a
EV_SYN/SYN_DROPPED event to notify userspace. The userspace process then needs
to stop what it's doing, re-sync the device (i.e. query all axis, key, LED,
etc. values), update the internal state accordingly and then it can start
reading events again.

libevdev handles all this for you.
In the example code above, you saw that a return value of LIBEVDEV_READ_STATUS_SYNC signals a
SYN_DROPPED event and we called handle_syn_dropped(). This function is
actually incredibly easy:

You notice there is almost no difference to the normal event loop. A
different read flag, and instead of an rc of 0, we're now expecting an rc of
LIBEVDEV_READ_STATUS_SYNC. libevdev will give us events that all reflect the state change since the
SYN_DROPPED so we can update the client accordingly. Once the device is fully
synced, libevdev_next_event returns -EAGAIN to indicate there
are no more events to sync. The client can go back to reading events normally
with LIBEVDEV_READ_FLAG_NORMAL.

This is a lot simpler than having to ioctl the device and calculating the
state manually.

The state handling is the same as described above. Even though libevdev
knows that there are e.g. a few button events waiting in the sync queue it
will not update the client-visible state until it passed the respective event
to you.

Finally: you don't have to sync the device after a SYN_DROPPED event. You
can chose to keep reading with LIBEVDEV_READ_FLAG_NORMAL as if nothing
happened. If you do so, libevdev will drop the sync event queue, update the
internal state to match the sync status and pass you the next real event. So
even if you didn't get that button down event because you dropped the sync,
libevdev_get_event_value(dev, EV_KEY, BTN_LEFT) will now return 1 to
reflect the state of the device. So libevdev's device state still matches what
the client would otherwise see (had it processed all events).

This is a base overview of how libevdev works. In the next post, I'll show how to manipulate the device.

[1] look the kernel source, drivers/input/evdev.c:handle_eviocgbit, supplying the wrong size was common enough to warrant a warning in the kernel.

Friday, September 13, 2013

git-branch-tools is my little repo for git scripts to make a few things easier. I first talked about it here. The repository is available on https://github.com/whot/git-branch-tools, the latest addition is git patch-set.
I used to create git patch sets with just git format-patch, but too often I found some minor change on the last review and had to re-generate it. So ended up with multiple patch files in the directory, or worse, a combination of old and new ones in danger of being sent by git send-email later. git-patch-set fixes this for me:

So my patches are in the $GIT_DIR/patches/ directory, named after the
current date + time and the refs used for the list. This makes them
identifiable and sortable (to some degree anyway). And, to make things
easier, $GIT_DIR/patches/latest is a symlink to the latest patch set, so usually the workflow is

X servers receive notifications from HAL/udev about new input devices, even when you vt-switched to the tty or another server. Input devices added while the server is not the owner of the vt will be added but not enabled, so events from such devices are ignored. On vt-switch back, the device is enabled and the fd is added to the select set used by the server. Future events will trigger a SIGIO and will be processed as expected

evdev holds the fd open between PreInit and enabling the device. If the device is hot-plugged while the server is vt-switched away events accumulate on the fd. evdev calls xf86FlushInput() to discard these events but a bug in that function made it essentially a noop for evdev devices. Thus, once the server is the VT owner again, events from that device are still on the fd and are processed whenever the next event comes along on that device.

Reproducer is fairly simple: open a text editor, vt-switch, hotplug a keyboard, type something on that keyboard, vt-switch back and the events will be replayed on the existing server.

Monday, March 4, 2013

I'm using a lot of branches. Almost one per feature or bug, and they add up quickly. Why I'm doing this doesn't matter for this post, but I found it to be a good workflow. The problem with that is of course that after a while I forget which branch was for what, or what branch I worked on three weeks ago. So I started hacking up some git helpers.

The output above shows the branch name, last time that branch was checked out, last commit time and a marker that shows up if this branch doesn't exist anymore. There are a few more flags you can pass in too, including git log flags, so play around with it.

Thursday, January 24, 2013

So you thought moving a pointer/cursor on-screen is simple? Well... no.

Having recently spent a day fixing up freedesktop Bug 31636, I figured maybe I should take you on a journey that starts with naïveté and ends in insanity. Just like Twilight.

What is actually needed to move a pointer on the screen?
Move your mouse. The driver submits two relative coordinates to the server and expects the pointer to move. So we take the coordinates, add it to the last known coordinates and we're done.

A 1:1 movement of course only works for slow mouse movements. For larger deltas, we need to accelerate the pointer movement so we can easily cover larger distances on the screen. So we look at the timestamps of the events, their delta movements and use that to calculate some factor. This factor is applied to the latest deltas and then decides the actual movement.

Now we've pretty much covered traditional mice. Many devices however are absolute input devices. They don't use delta coordinates, they just give us the absolute position of the pointer. However, that position is not in pixels but some device coordinate system. So we need to remember the axis ranges and scale from the device coordinate system into the screen coordinate system.

Many users have more than one screen. Two or more screens, when not in mirrored mode, create a desktop that is larger than each single screen. For absolute devices, we map the device to the desktop coordinates so that each edge of the device maps to the corresponding edge on the desktop. Absolute events from such a device must be first mapped to desktop coordinates. From those coordinates we can gather the screen the pointer is to be on and clip the coordinates back to the per-screen coordinates to draw the visible cursor. For relative devices the process is somewhat similar, we add movement to the desktop coordinates, then clip back to per-screen for updates.

All of the above is pretty standard and doesn't require any X specifics. Let's get into what the X11 protocol requires.

evdev has a calibration feature that allows a device to be adjusted for differences in the actual vs. announced coordinates. This is needed when a device real axis ranges are actually different to what the device announces. For example, a device may claim that the axis starts at 0, but really the first value you get out of it is e.g. 50.
For historical reasons we cannot change the device axes once they are set up though. So evdev's calibration scales from the calibrated device range (e.g. 50-950) into the actual announced device range (0-1000). That scaled coordinate is then posted to the server. The wacom driver has a similar feature (called Area).

The X Input Extensions (XI) provides so-called "valuators" (== axes) to the clients as part of the various input events. Valuators 0 and 1 are x and y. XI requires valuator data to be in absolute device coordinates, but those are per protocol screen. In old-style multi-monitor setups with two Section Device entries in the xorg.conf, you have more than one protocol screen. The device itself however is still mapped to the whole desktop. So we convert device coordinates to desktop coordinates, then to screen coordinates on the current screen, and then that position is converted back into device coordinates. Bonus points for considering what happens in a setup with three monitors but only two protocol screens

If you kept counting, you should be up to 5 coordinate systems now:

device coordinate system

adjusted device coordinate system after calibration is applied

desktop-wide coordinate system

per-screen coordinate system

per-screen device coordinate system

Yep, that's right. A coordinate from an absolute input device passes through all 5 before the pointer position is defined and the data can be appended to the event. And that happens on every single pointer event. Compare this to a relative event, which has four steps:

relative coordinates

device-specific acceleration

desktop-wide coordinate system

per-screen coordinate system

The bug that triggered this blog post was an actual use-case. If an absolute device is used in relative mode, the coordinates were still applied according to the device coordinate range. Thus, relative motion on the device was dependent on the desktop dimensions and attaching a second monitor would increase movement in one axes but not the other. To avoid this, we have an extra layer of scaling, where we pre-scale the coordinates first. That scaling is then undone by the second conversion into desktop coordinates. Whoopee.

Update March 7 2013: This addition was not merged into XI 2.3, largely because there is no real need for it. XI 1.x' XGetExtensionVersion() returns the server version without locking in a client version and at this point there was no perceived need for getting the already-requested client version back.
I'll leave this here for archival purposes but again, this request was not merged into XI 2.3

Original post below

Posting this here too to get a bit more exposure.

XIQueryVersion(3) is the first XI2 request clients should send to the server. The client announces its supported version and in return receives the server version (which is always less or equal to the client, never higher).

As XI 2.1 - 2.3 progressed, we started using this information in the server. Clients are treated slightly differently depending on their announced version. The current differences are:

XIQueryPointer will not set the button 1 mask for pointer-emulated events if the client supports XI 2.2 or newer.

XIAllowEvents will allow XIRejectTouch and XIAcceptTouch for clients supporting XI 2.2 or newer.

The client can issue multiple XIQueryVersion requests, but they need to have the same version numbers to provide for consistent server behaviour.

So far, so good. This works fine as long as the client supports one specific version. However, as toolkits like GTK have come to support XI2, the requirements changed a bit. An application and its toolkit usually look like a single client to the server. However, the client may support XI 2.0, but the toolkit may support XI 2.3. And neither knows of the other's version support. If the client requests XIQueryVersion before the toolkit, the toolkit is locked into the client version. But if the toolkit first requests XIQueryVersion, the client is locked into the version supported by the toolkit. Worst case the client may get a BadValue and quit because it may not be built for this case.

Jasper St. Pierre and Owen Taylor brought this up on #xorg-devel today, and I've send a proposed solution to the mailing list.

A new XIGetSupportedVersion request simply returns the server's major/minor version number. Uncapped, so really what the server supports. And the same request also returns the client version previously announced with XIQueryVersion. Or zero, if the client hasn't called it yet.

This request enables toolkits to query what the client has already set, and of course what the server supports without modifying the client state. The request is currently an RFC, but I do hope we may get this into XI 2.3.

If you're working on XI2-aware clients or toolkits and you have a use-case that requires this or would break by this addition, please speak up now.

Wednesday, January 2, 2013

In some error cases, GNOME will display a full-screen window with only a single button. The window claims that "Oh no! Something has gone wrong." and "A problem has occurred and the system can't recover. Please log out and try again." The button merely allows a user to log-out and thus quit the current session. Killing that window with xkill also quits the session.

Most of the crashes I get is from experimental code crashing gnome-settings-daemon. Certainly not something fatal, certainly not something that should prevent me from continuing to work in my current session. After all, the menu key still works, the hot corner works, everything works, but closing the dialog will throw me out of my session. And because that pesky dialog is always on-top, I'm down to one monitor. Luckily, the dialog can be disabled.

Update Jan 3: As Jasper points out in the comments, Alt+F4 will close the window. Though I tried Ctrl+W and Ctrl+Q, I haven't used Alt+F4 in ages. Sometimes the right solution is so much simpler :)