Wednesday, September 18, 2013

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.

No comments: