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.

No comments: