Wednesday, September 18, 2013

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.

1 comment:

Unknown said...

Hi,

I set some calibration/settings on input device (turn device wheel to left. I did this with write() function) while this happens input system gathers data. So when I first call libevdev_next_event it gives me old garbage data. But I dont want those.

How can I flush/clear buffered data with libevdev? Is there any way?

(Execuse me for my english I am not native)

Yours Sincerely..