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