Friday, December 13, 2013

Lenovo T440 touchpad button configuration

Update March 19 2014: this post is outdated, please read X.Org synaptics support for the Lenovo T440, T540, X240, Helix, Yoga, X1 Carbon instead.

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:

+----------------------------+
| LLLLLLLLLL MMMMM RRRRRRRRR |
|                            |
|                            |
|                            |
|                            |
|                            |
|                            |
| LLLLLLLL          RRRRRRRR |
+----------------------------+
We simply can't do that at the moment, best we can is split the touchpad so that the whole right side is a right-click and a strip in the middle that is a middle click. So the best we can do is:
+----------------------------+
| LLLLLLLLLL MMMMM RRRRRRRRR |
| LLLLLLLLLL MMMMM RRRRRRRRR |
| LLLLLLLLLL MMMMM RRRRRRRRR |
| LLLLLLLLLL MMMMM RRRRRRRRR |
| LLLLLLLLLL MMMMM RRRRRRRRR |
| LLLLLLLLLL MMMMM RRRRRRRRR |
| LLLLLLLLLL MMMMM RRRRRRRRR |
| LLLLLLLLLL MMMMM RRRRRRRRR |
| LLLLLLLLLL MMMMM RRRRRRRRR |
+----------------------------+
I'm working on a solution for the proper config, but for now you'll have to be content with this.

The easiest approach for local configuration is a new InputClass section in the form:

Section "InputClass"
    Identifier "t440 top buttons"
    MatchDriver "synaptics"
    #                         right btn|middle btn
    Option "SoftButtonAreas" "60% 0 0 0 40% 60% 0 0"
EndSection
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.

ACTION!="add|change", GOTO="touchpad_quirks_end"
KERNEL!="event*", GOTO="touchpad_quirks_end"
ENV{ID_INPUT_TOUCHPAD}!="1", GOTO="touchpad_quirks_end"

ATTR{[dmi/id]product_version}=="*T440*", \
  ENV{ID_INPUT.tags}="touchpad_softbutton_top"

LABEL="touchpad_quirks_end"
If our product matches T440, we tag the touchpad, and that tag is something we can match against. Our revised InputClass section now looks like this:
Section "InputClass"
    Identifier "t440 top buttons"
    MatchDriver "synaptics"
    MatchTag "touchpad_softbutton_top"
    Option "SoftButtonAreas" "60% 0 0 0 40% 60% 0 0"
EndSection
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

Wacom Serial devices and inputattach in a systemd world

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:

[Unit]
Description=inputattach for Wacom ISDv4-compatible serial devices

[Service]
Type=simple
ExecStart=/usr/bin/inputattach -w8001 /dev/%I
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:

ACTION!="add|change", GOTO="wacom_end"

SUBSYSTEM=="tty|pnp", SUBSYSTEMS=="pnp", ATTRS{id}=="WACf*", \
  ENV{ID_MODEL}="Serial Wacom Tablet $attr{id}", \
  ENV{ID_INPUT}="1", ENV{ID_INPUT_TABLET}="1", \
  ENV{NAME}="Serial Wacom Tablet $attr{id}"
SUBSYSTEM=="tty|pnp", SUBSYSTEMS=="pnp", ATTRS{id}=="FUJ*", \
  ENV{ID_MODEL}="Serial Wacom Tablet $attr{id}", \
  ENV{ID_INPUT}="1", ENV{ID_INPUT_TABLET}="1", \
  ENV{NAME}="Serial Wacom Tablet $attr{id}"

SUBSYSTEM=="tty|pnp", KERNEL=="ttyS[0-9]*", ATTRS{id}=="WACf*", \
  TAG+="systemd", ENV{SYSTEMD_WANTS}+="wacom-inputattach@%k.service"

LABEL="wacom_end"
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:
$> systemctl status wacom-inputattach@ttyS0.service   
wacom-inputattach@ttyS0.service - "inputattach for Wacom ISDv4-compatible serial devices"
   Loaded: loaded (/usr/lib/systemd/system/wacom-inputattach@.service; static)
   Active: active (running) since Thu 2013-12-12 08:03:00 EST; 24min ago
 Main PID: 562 (inputattach)
   CGroup: /system.slice/system-wacom\x2dinputattach.slice/wacom-inputattach@ttyS0.service
           └─562 /usr/bin/inputattach -w8001 /dev/ttyS0

[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

Update 2013/12/13: remove quotes around Description in unit file

Wednesday, December 4, 2013

argcheck - assert on steroids

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:

#include <ccan/argcheck/argcheck.h>

void calculate(int percent) {
   argcheck_int_range(percent, 0, 100);
   // now do something
}
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:
#define MAXIMUM 100

void something(int a) {
     if (!argcheck_int_lt(a, MAXIMUM))
          return;
     // now do something
}

int main(void) {
    something(500);
    return 0;
}
If a doesn't meet the condition, the error message is:
test.c:73 something(): condition "(a < MAXIMUM)" (500 < 100) failed

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

evtest is dead. long live evemu

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].

$> sudo evemu-record
Available devices:
/dev/input/event0: Lid Switch
/dev/input/event1: Sleep Button
/dev/input/event2: Power Button
/dev/input/event3: AT Translated Set 2 keyboard
/dev/input/event4: SynPS/2 Synaptics TouchPad
/dev/input/event5: PIXART USB OPTICAL MOUSE
/dev/input/event6: Microsoft Microsoft® Digital Media Keyboard
/dev/input/event7: Video Bus
/dev/input/event8: TPPS/2 IBM TrackPoint
/dev/input/event9: Wacom ISDv4 E6 Pen
/dev/input/event10: Wacom ISDv4 E6 Finger
/dev/input/event11: Microsoft Microsoft® Digital Media Keyboard
/dev/input/event12: Integrated Camera
/dev/input/event13: ThinkPad Extra Buttons
/dev/input/event14: HDA Intel PCH HDMI/DP,pcm=8
/dev/input/event15: HDA Intel PCH HDMI/DP,pcm=7
/dev/input/event16: HDA Intel PCH HDMI/DP,pcm=3
Select the device event number [0-16]: 
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.
# EVEMU 1.2
# Input device name: "SynPS/2 Synaptics TouchPad"
# Input device ID: bus 0x11 vendor 0x02 product 0x07 version 0x1b1
# Supported events:
#   Event type 0 (EV_SYN)
#     Event code 0 (SYN_REPORT)
#     Event code 1 (SYN_CONFIG)
#     Event code 3 (SYN_DROPPED)
#   Event type 1 (EV_KEY)
#     Event code 272 (BTN_LEFT)
#     Event code 325 (BTN_TOOL_FINGER)
#     Event code 328 (BTN_TOOL_QUINTTAP)
#     Event code 330 (BTN_TOUCH)
#     Event code 333 (BTN_TOOL_DOUBLETAP)
#     Event code 334 (BTN_TOOL_TRIPLETAP)
#     Event code 335 (BTN_TOOL_QUADTAP)
#   Event type 3 (EV_ABS)
#     Event code 0 (ABS_X)
#       Value   2680
#       Min     1472
#       Max     5472
#       Fuzz       0
#       Flat       0
#       Resolution 75
#     Event code 1 (ABS_Y)
#       Value   4550
#       Min     1408
#       Max     4448
#       Fuzz       0
#       Flat       0
#       Resolution 129
#     Event code 24 (ABS_PRESSURE)
#       Value      0
#       Min        0
#       Max      255
#       Fuzz       0
#       Flat       0
#       Resolution 0

[...]

N: SynPS/2 Synaptics TouchPad
I: 0011 0002 0007 01b1
P: 05 00 00 00 00 00 00 00
B: 00 0b 00 00 00 00 00 00 00
B: 01 00 00 00 00 00 00 00 00
B: 01 00 00 00 00 00 00 00 00
B: 01 00 00 00 00 00 00 00 00
B: 01 00 00 00 00 00 00 00 00
B: 01 00 00 01 00 00 00 00 00
B: 01 20 e5 00 00 00 00 00 00
B: 01 00 00 00 00 00 00 00 00
B: 01 00 00 00 00 00 00 00 00
B: 01 00 00 00 00 00 00 00 00
B: 01 00 00 00 00 00 00 00 00
B: 01 00 00 00 00 00 00 00 00
B: 01 00 00 00 00 00 00 00 00
B: 02 00 00 00 00 00 00 00 00
B: 03 03 00 00 11 00 80 60 06
B: 04 00 00 00 00 00 00 00 00
B: 05 00 00 00 00 00 00 00 00
B: 11 00 00 00 00 00 00 00 00
B: 12 00 00 00 00 00 00 00 00
B: 15 00 00 00 00 00 00 00 00
B: 15 00 00 00 00 00 00 00 00
A: 00 1472 5472 0 0 75
A: 01 1408 4448 0 0 129
[...]
And that is followed by events that look something like this:
E: 1382571966.639767 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
E: 1382571966.650972 0003 0036 4044 # EV_ABS / ABS_MT_POSITION_Y    4044
E: 1382571966.650972 0003 003a 0041 # EV_ABS / ABS_MT_PRESSURE      41
E: 1382571966.650972 0003 0001 4044 # EV_ABS / ABS_Y                4044
E: 1382571966.650972 0003 0018 0041 # EV_ABS / ABS_PRESSURE         41
E: 1382571966.650972 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
E: 1382571966.663486 0003 0039 -001 # EV_ABS / ABS_MT_TRACKING_ID   -1
E: 1382571966.663486 0001 014a 0000 # EV_KEY / BTN_TOUCH            0
E: 1382571966.663486 0003 0018 0000 # EV_ABS / ABS_PRESSURE         0
E: 1382571966.663486 0001 0145 0000 # EV_KEY / BTN_TOOL_FINGER      0
E: 1382571966.663486 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
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.

$> sudo evemu-device my-device-recording.txt
CyPS/2 Cypress Trackpad: /dev/input/event17
$> sudo evemu-play /dev/input/event17 < my-device-recording.txt
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

Friday, November 1, 2013

Neues deutsches Tastaturlayout (Changes to the German keyboard layout)

Ich hab's zuerst nicht bemerkt, darum kommt diese Warnung etwas spät. Dennoch: seit Fedora 19 (xkeyboard-config 2.8) gibt es eine kleine Änderung im deutschen Tastaturlayout.

Die Tilde taste ("~"), zu finden neben der Entertaste, betätigt mit AltGr und "+" war ein sogenannter dead key. Solche dead keys ermöglichen es mit mehreren Eingaben Buchstaben wie etwa "ñ" zu erzeugen (tilde + n). dead key hieß allerdings auch dass, um eine tatsächliche Tilde einzugeben, "~", die Taste zweimal betätigt werden musste. Dieses Layout war anders als die DIN und das Standardlayout in Windows und wurde daher geändert. Siehe (fdo bug 9753). Die neue Belegung ist Tilde, ohne dead key Funktionalität.

Falls das alte Layout bevorzugt wird, muss das Layout auf Deutsch(veraltet) gestellt werden. Dies kann entweder über setxkbmap -layout "de(legacy)" bzw. setxkbmap -layout "de" -variant "legacy" geändert werden, bzw in GNOME über eine etwas versteckte Option. Im "Region und Sprache" Dialog den "+" Button betätigen, danach die drei vertikalen Punkte anklicken. Das öffnet das Suchfenster, wo dann nach Deutsch(veraltet) gesucht werden kann. Diesen Eintrag wählen und fertig.

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

The X.Org Foundation and the 501(c)(3) status

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 X.Org Foundation structure

There is a link between the members of the Foundation and the Software: to become a member, you have to "i) be actively involved in the activities relating to the technologies of X.Org, as set forth in the Membership Agreement". So as long as you're hacking on X or you're otherwise affiliated, you can join and membership is free.

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

libevdev - creating uinput devices

This post describes how to create uinput devices through the new libevdev library. For more information about libevdev, please refer to the first post in this series.

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.

struct libevdev *dev;
struct libevdev_uinput *uidev;
int rc;

rc = libevdev_new_from_fd(fd, &dev);
if (rc < 0)
     handle_error();

rc = libevdev_uinput_create_from_device(dev,
                                        LIBEVDEV_UINPUT_OPEN_MANAGED,
                                        &uidev);
if (rc < 0)
     handle_error();
/* don't need the source device anymore */
libevdev_free(dev);

libevdev_uinput_write_event(uidev, EV_REL, REL_X, -1);
libevdev_uinput_write_event(uidev, EV_REL, REL_Y, 1);
libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0);
libevdev_uinput_destroy(uidev);

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:

int fd;

dev = libevdev_new();
libevdev_set_name(dev, "my device");
libevdev_enable_event_type(dev, EV_REL);
libevdev_enable_event_code(dev, EV_REL, REL_X);
libevdev_enable_event_code(dev, EV_REL, REL_Y);
libevdev_enable_event_type(dev, EV_KEY);
libevdev_enable_event_code(dev, EV_KEY, BTN_LEFT);
libevdev_enable_event_code(dev, EV_KEY, BTN_MIDDLE);
libevdev_enable_event_code(dev, EV_KEY, BTN_RIGHT);

fd = open("/dev/uinput", O_RDWR);

rc = libevdev_uinput_create_from_device(dev, fd, &uidev);
if (rc < 0)
     handle_error();

/* don't need the source device anymore */
libevdev_free(dev);

... do something

libevdev_uinput_destroy(uidev);
close(fd);

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.

int fd;
struct libevdev *dev;
const char *devnode;

devnode = libevdev_uinput_get_devnode(uidev);
fd = open(devnode, O_RDWR|O_NONBLOCK);
rc = libevdev_new_from_fd(fd, &dev);
if (rc < 0)
   handle_error();

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.

libevdev - accessing and modifying devices

This post describes how to change the appearance of a device through the new libevdev library. For more information about libevdev, please refer to the previous post on libevdev.

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.

struct libevdev *dev;
int rc;

rc = libevdev_new_from_fd(fd, &dev));
if (rc < 0)
     handle_error();

/* broken device, shouldn't have ABS_RX */
if (libevdev_has_event_code(dev, EV_ABS, ABS_RX))
    libevdev_disable_event_code(dev, EV_ABS, ABS_RX));

/* will never return a ABS_RX event now */
rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);

if (!libevdev_has_event_code(dev, EV_ABS, ABS_PRESSURE)) {
    struct input_absinfo abs;
    abs.minimum = 0;
    abs.maximum = 100;
    abs.resolution = 1;
    abs.fuzz = abs.flat = 0;
    libevdev_enable_event_code(dev, EV_ABS, ABS_PRESSURE, &abs);
}

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.

if (libevdev_has_event_code(dev, EV_ABS, ABS_PRESSURE))
     rc = libevdev_kernel_set_abs_info(dev, ABS_PRESSURE, &abs));

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.

Modifying LEDs

A slightly more common scenario is toggling LEDs on a device:

if (libevdev_has_event_code(dev, EV_LED, LED_NUML))
   rc = libevdev_set_led_value(dev, LED_NUML, LIBEVDEV_LED_ON);

/* for the lazy: */
rc = libevdev_set_led_values(dev, LED_NUML, LIBEVDEV_LED_ON,
                                  LED_CAPSL, LIBEVDEV_LED_OFF,
                                  LED_SCROLLL, LIBEVDEV_LED_ON,
                                  -1);

Again, this shouldn't need much explanation. The first call toggles a single LED, the second call toggles multiple LEDs in one go.

libevdev - handling input events

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.

Repositories and documentation

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:

struct libevdev *dev;
int fd;
int rc;

fd = open("/dev/input/event0", O_RDONLY|O_NONBLOCK);
if (fd < 0)
   fprintf(stderr, "error: %d %s\n", errno, strerror(errno));
rc = libevdev_new_from_fd(fd, &dev);
if (rc < 0)
   fprintf(stderr, "error: %d %s\n", -rc, strerror(-rc));

Fairly straightforward. Open a new device from the file descriptor and initialize it. On error, the return value is a negative errno.

printf("Device: %s\n", libevdev_get_name(dev));
printf("vendor: %x product: %x\n",
       libevdev_get_id_vendor(dev),
       libevdev_get_id_product(dev));

if (libevdev_has_event_type(dev, EV_REL) &&
    libevdev_has_event_code(dev, EV_REL, REL_X) &&
    libevdev_has_event_code(dev, EV_REL, REL_Y) &&
    libevdev_has_event_code(dev, EV_KEY, BTN_LEFT) &&
    libevdev_has_event_code(dev, EV_KEY, BTN_MIDDLE) &&
    libevdev_has_event_code(dev, EV_KEY, BTN_RIGHT))
    printf("Looks like we got ourselves a mouse\n");

libevdev_free(dev);
close(fd);

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.

struct input_event ev;

rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
if (rc < 0) {
    if (rc != -EAGAIN)
        fprintf(stderr, "error: %d %s\n", -rc, strerror(-rc));
else if (rc == LIBEVDEV_READ_STATUS_SYNC)
    handle_syn_dropped(dev);
else if (rc == LIBEVDEV_READ_STATUS_SUCCESS)
    print("We have an event!\n%d (%s) %s (%d) value %d\n",
          ev.type, libevdev_event_type_get_name(ev.type),
          ev.code, libevdev_event_code_get_name(ev.type, ev.code),
          ev.value);

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 (!libevdev_has_event_code(dev, EV_KEY, BTN_LEFT))
        return;

if (libevdev_get_event_value(dev, EV_KEY, BTN_LEFT) == 0)
   printf("Button is up\n");
else
   printf("Button is down\n");

/* BTN_LEFT event happens */

rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev))
...

if (libevdev_get_event_value(dev, EV_KEY, BTN_LEFT) == 0)
   printf("Button is up\n");
else
   printf("Button is down\n");

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:

void handle_syn_dropped(struct libevdev *dev) {
    struct input_event ev;
    int rc = LIBEVDEV_READ_STATUS_SYNC;

    while (rc == LIBEVDEV_READ_STATUS_SYNC) {
        rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_SYNC, &ev);
        if (rc < 0) {
            if (rc != -EAGAIN)
                fprintf(stderr, "error %d (%s)\n", -rc, strerror(-rc));
            return;
        }

        printf("State change since SYN_DROPPED for %s %s value %d\n",
                libevdev_event_type_get_name(ev.type),
                libevdev_event_code_get_name(ev.type, ev.code),
                ev.value);
    }
}

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: creating patch sets

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:
$> git patch-set HEAD~2
patches/patches-201309130933-HEAD~2/0001-test-provide-wrapper-for-fetching-the-devnode-from-a.patch
patches/patches-201309130933-HEAD~2/0002-wrap-EVIOCSCLOCKID-into-an-API-call.patch
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
$> git patch-set HEAD~2
patches/patches-201309130933-HEAD~2/0001-test-provide-wrapper-for-fetching-the-devnode-from-a.patch
patches/patches-201309130933-HEAD~2/0002-wrap-EVIOCSCLOCKID-into-an-API-call.patch
$> git send-email patches/latest/*.patch
That's not all though. I've added two hooks, pre-patch-set and post-patch-set to be run before/after the actual patch generation.
$> cat .git/hooks/pre-patch-set
#!/bin/bash -e
echo "running make check"
make check
$> git patch-set HEAD~2
running make check
Making check in doc
doxygen libevdev.doxygen
Making check in libevdev
make  check-am
make[2]: Nothing to be done for `check-am'.
Making check in tools
make[1]: Nothing to be done for `check'.
Making check in test
make  check-TESTS check-local
PASS: test-libevdev
make[4]: Nothing to be done for `all'.
============================================================================
Testsuite summary for libevdev 0.3
============================================================================
# TOTAL: 1
# PASS:  1
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================
  GEN      gcov-report.txt
========== coverage report ========
libevdev-uinput.c: total lines: 172 not tested: 28 (83%)
libevdev.c: total lines: 689 not tested: 78 (88%)
========== =============== ========
patches/patches-2013091309:33-HEAD~2/0001-test-provide-wrapper-for-fetching-the-devnode-from-a.patch
patches/patches-2013091309:33-HEAD~2/0002-wrap-EVIOCSCLOCKID-into-an-API-call.patch
I've been using that script for quite a while now and it did make sending patch sets a bit easier. Plus, now I'm not in danger of sending out patch sets that don't pass make check :)

Wednesday, April 17, 2013

CVE-2013-1940: VT-switched servers receive input from hot-plugged devices

Rather by accident, Dave Airlie and I found a minor security issue in the X server last week (read the story here) This issue has been assigned CVE-2013-1940 and is now publicly available. The corresponding bug reports are here: https://bugs.freedesktop.org/63353 and https://bugzilla.redhat.com/950438.

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.

This issue is now fixed upstream and I have released xserver 1.13.4 and xserver 1.14.1. It is of relatively low impact but the fix is easy so I recommend to patch your X servers.

Monday, March 4, 2013

git branch-tools: some helpers for managing git branches

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.

I pushed them to https://github.com/whot/git-branch-tools today, feel free to use them or improve on them.

Archiving branches

Some branches are not actively developed anymore but should still be preserved for posterity. These branches are clogging up the branch view.

git archive-branch mybranch
moves mybranch to archive/2013/mybranch and tags the current top commit with a message about the branch history. An example git branch output would look like this now:
  ...
  archive/2013/touch-test-libtool-linker-issues
  archive/2013/two-screen-coordinates
  archive/2013/wrong-signal-logging-merge
  archive/2013/xi2-protocol-tests
  archive/2013/xi21-confine-to
  archive/2013/xorg-conf-init-cleanup
  attic
  bugfix/xts-segfault
  devel
  fedora-17-branch
  fedora-rawhide-branch
  for-keith
  high-keycodes
  master
  memleak
* next
  ...

Showing recent branches

Working on many branches can mean you forget which branch you worked on last week, or the week before.

git recent-branches
lists the various branches checked out over the history, including the date and last commit date on that branch. Example:
next                                 4 hours ago    last commit 6 days ago
server-1.13-branch                   4 hours ago    last commit 2 weeks ago
touch-grab-race-condition-56578-v2   3 days ago     last commit 3 days ago
touch-grab-race-condition-56578      4 days ago     last commit 6 days ago      †
bug/xts-segfaults                    6 days ago     last commit 6 days ago      †
master                               6 days ago     last commit 3 weeks ago
for-keith                            10 days ago    last commit 2 weeks ago
memleak                              13 days ago    last commit 2 weeks ago
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.

Better branch descriptions

Can't remember what branch "fix-race-condition" was? Me neither. That's what

git branch-description [branchname] [upstream]
will tell you. If upstream is given, it'll also show you what has been merged into upstream already (by patch, not by commit). Example again:
:: whot@yabbi:~/xorg/xserver (next)> git-branch-description touch-grab-race-condition-56578-v2
Branch       touch-grab-race-condition-56578-v2
Branched:    Thu Feb 14 11:05:48 2013 -0800
Last commit: Fri Mar 1 16:37:49 2013 +1000

Fixes for https://bugs.freedesktop.org/show_bug.cgi?id=56578, second attempt

============================ Unmerged commits =============================
Commits on touch-grab-race-condition-56578-v2 not in next:
68b937046f278d53de14b586dbf7fd5aa7367f59 Xi: return !Success from DeliverTouchEmulatedEvent if we didn't deliver
f8baab8ac32e5abb31bcd1bb4f74e82d40208221 Xi: use a temp variable for the new listener
9cbb956765c7b4f1572ab2100f46504bf6313330 dix: don't set non-exisiting flags on touch events
2a5b3f2f2293f4a428142fffdb1b6e8ffbbb5db0 dix: fix a comment
76e8756545951d7f13ca84a4bd24fe5f367c5de2 Xi: compress two if statements with the same body
61b06226a43839ed75126f9c54d47bc440285e21 dix: update coords for touch events in PlayReleasedEvents
bd1a5423bbb02a349991a52f4997e830a0dc1992 Xi: add a comment to make a condition a bit clearer
78b26498085a7589e1f4d9ac3c21b69dc3227f87 Xi: not having an ownership mask does not mean automatic acceptance
c7271c7e05cdbeb35a3558223f9c2d6544504c4c dix: don't prepend an activated passive grab to the listeners
71ee72c97e459ef76984e6da64e5dab0ce6e4465 Xi: if we delivered a TouchEnd to a passive grab, end it
9b6966187fd0e6fb7ad3c2c1073456d96e3adab0 Xi: if a pointer grabbing listener gets the touch end, the touch is over
5afef18196ce70faec3e94379c3e6d3767660c4a FIXME: Xi: fix lookup in ActivateEarlyAccept
3784283be1f482a0f039f2eb790c0c8c2cc4bedb Xi: update the core listener state if we delivered the event
3570ef1244c87aef92db97df6e2b921529ffb75a Xi: if a passive async grab is activated from an emulated touch, accept
9bef901d8e28d48f43da3167219b02ad1dba27d8 Xi: save state for early acceptance
7d51022becd5af124896817030a10eedf7f1783a Xi: when punting to a new owner, always create TouchEnd events
4775cdb0d9a2513edcf27a9c4c1916e8213c397b Xi: use public.processInputProc to replay the touch history
431b128b9138af7a208b63d4eb5b917d94c08129 Xi: Don't emit a TouchEnd event to a frozen device
4126d64f6a40d5568b2d1412d519325c02786c9a dix: AllowSome is equivalent to TouchAccept
33421e91a52be91d7121c7c2146ff7bb53bea638 dix: move EmitTouchEnd to touch.c
54f8884aef275b15f2c42e3350e2b4968124af01 dix: XAllowEvents() on a touch event means accepting it

Commits on touch-grab-race-condition-56578-v2 already merged to next:

================================= Activity =================================
e7b4b83 HEAD@{5 hours ago}: checkout: moving from touch-grab-race-condition-56578-v2 to server-1.13-branch
9cbb956 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 9cbb956
d58ddeb HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to d58ddeb
7c3968b HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 7c3968b
a354dd8 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to a354dd8
fdf4869 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to fdf4869
82be6b2 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 82be6b2
82be6b2 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 82be6b2
68b9370 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 68b9370
151eff1 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 151eff1
57fa0b9 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 57fa0b9
b43e866 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to b43e866
ef6a120 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to ef6a120
9064294 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 90642948cc78834d95f7a3bddaac7ff77b68ed7e
9064294 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 90642948cc78834d95f7a3bddaac7ff77b68ed7e
6513e0e HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 6513e0e
0d60ba6 HEAD@{3 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 0d60ba6
dd23302 HEAD@{4 days ago}: checkout: moving from f21354da571dcd39ae1423388298d5c61d3e736d to touch-grab-race-condition-56578-v2
f21354d HEAD@{4 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to f21354da571dcd39ae1423388298d5c61d3e736d
0d60ba6 HEAD@{4 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 0d60ba6
ae2cac9 HEAD@{4 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to ae2cac99a75917d6c4d34b8aa4aeaec0b5d32da7
c2b3d37 HEAD@{4 days ago}: checkout: moving from d75925b9fb8b24c8134b5082294e82abf83294af to touch-grab-race-condition-56578-v2
0d60ba6 HEAD@{4 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 0d60ba683e7e95049c01ac5dba48a2f5fd80d9b9
c2b3d37 HEAD@{4 days ago}: checkout: moving from 0d60ba683e7e95049c01ac5dba48a2f5fd80d9b9 to touch-grab-race-condition-56578-v2
0d60ba6 HEAD@{4 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 0d60ba683e7e95049c01ac5dba48a2f5fd80d9b9
0d60ba6 HEAD@{4 days ago}: checkout: moving from 90642948cc78834d95f7a3bddaac7ff77b68ed7e to touch-grab-race-condition-56578-v2
9064294 HEAD@{4 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 90642948cc78834d95f7a3bddaac7ff77b68ed7e
8e5adb4 HEAD@{4 days ago}: checkout: moving from touch-grab-race-condition-56578-v2 to 8e5adb4ef8bafa2a3188e69409e2908f80288311
033b932 HEAD@{4 days ago}: checkout: moving from touch-grab-race-condition-56578 to touch-grab-race-condition-56578-v2

And install the git-post-checkout-nagging-hook as your .git/hooks/post-checkout to make sure you get reminded to set the branch description.

Thursday, January 24, 2013

How to move a cursor

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:

  1. device coordinate system
  2. adjusted device coordinate system after calibration is applied
  3. desktop-wide coordinate system
  4. per-screen coordinate system
  5. 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:
  1. relative coordinates
  2. device-specific acceleration
  3. desktop-wide coordinate system
  4. 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.

Proposed XI2.3 addition: XIGetSupportedVersion

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.
  • Raw event delivery changes if a client supports XI 2.1 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

Getting rid of the GNOME "Oh No! Something has gone wrong." dialog

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 :)

The dialog is displayed by gnome-session and it's named the fail whale (code). It's triggered only for required apps and those can be configured.

$ cat /usr/share/gnome-session/sessions/gnome.session | grep Required
RequiredComponents=gnome-shell;gnome-settings-daemon;
Drop g-s-d from the required components, restart the session, and you won't see the error anymore. Try it by sending SIGABRT to g-s-d.
$ kill -ABRT `pidof gnome-settings-daemon`
Doing so twice (g-s-d restarts once) will trigger the error unless g-s-d is dropped from the required components.

It should go without saying, but the above will only display the error message, it won't fix the actual error that causes the message to be displayed.