diff options
| author | Peter Hutterer <peter.hutterer@who-t.net> | 2013-07-25 15:56:11 +1000 |
|---|---|---|
| committer | Peter Hutterer <peter.hutterer@who-t.net> | 2013-08-29 13:54:46 +1000 |
| commit | 1acbfb35799485326f5d76280282f320828aec38 (patch) | |
| tree | fb7de085ba8996a043e5f801fb50bae073dccebe /libevdev/libevdev-uinput.c | |
| parent | 1b7c46b2f16f22bb87e76e55c28a717f5f8f248f (diff) | |
| download | libevdev-1acbfb35799485326f5d76280282f320828aec38.tar.gz | |
Add support for uinput device creation
This lets libevdev provide a relatively generic interface for the
creation of uinput devices so we don't need to duplicate this across
multiple projects.
Most of this is lifted from the current test implementation, with a
couple of minor changes.
EV_REP needs special handling:
Kernel allows to set the EV_REP bit, it doesn't set REP_* bits (which we
wrap anyway) but it will also set the default values (500, 33).
Device node is guessed based on the sysfs path:
The sysfs path contains a eventN file, that corresponds to our
/dev/input/eventN number. Use it so clients can quickly get the device
node, without a libudev dependency.
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Diffstat (limited to 'libevdev/libevdev-uinput.c')
| -rw-r--r-- | libevdev/libevdev-uinput.c | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/libevdev/libevdev-uinput.c b/libevdev/libevdev-uinput.c new file mode 100644 index 0000000..45a95fa --- /dev/null +++ b/libevdev/libevdev-uinput.c @@ -0,0 +1,361 @@ +/* + * Copyright © 2013 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#define _GNU_SOURCE +#include <config.h> +#include <fcntl.h> +#include <poll.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <dirent.h> +#include <sys/stat.h> +#include <time.h> +#include <linux/uinput.h> + +#include "libevdev.h" +#include "libevdev-int.h" +#include "libevdev-uinput.h" +#include "libevdev-uinput-int.h" +#include "libevdev-util.h" + +#define SYS_INPUT_DIR "/sys/devices/virtual/input/" + +static struct libevdev_uinput * +alloc_uinput_device(const char *name) +{ + struct libevdev_uinput *uinput_dev; + + uinput_dev = calloc(1, sizeof(struct libevdev_uinput)); + if (uinput_dev) { + uinput_dev->name = strdup(name); + uinput_dev->fd = -1; + } + + return uinput_dev; +} + +static int +set_evbits(const struct libevdev *dev, int fd, struct uinput_user_dev *uidev) +{ + int rc = 0; + unsigned int type; + + for (type = 0; type < EV_MAX; type++) { + unsigned int code; + int max; + int uinput_bit; + const unsigned long *mask; + + if (!libevdev_has_event_type(dev, type)) + continue; + + rc = ioctl(fd, UI_SET_EVBIT, type); + if (rc == -1) + break; + + /* uinput can't set EV_REP */ + if (type == EV_REP) + continue; + + max = type_to_mask_const(dev, type, &mask); + if (max == -1) + continue; + + switch(type) { + case EV_KEY: uinput_bit = UI_SET_KEYBIT; break; + case EV_REL: uinput_bit = UI_SET_RELBIT; break; + case EV_ABS: uinput_bit = UI_SET_ABSBIT; break; + case EV_MSC: uinput_bit = UI_SET_MSCBIT; break; + case EV_LED: uinput_bit = UI_SET_LEDBIT; break; + case EV_SND: uinput_bit = UI_SET_SNDBIT; break; + case EV_FF: uinput_bit = UI_SET_FFBIT; break; + case EV_SW: uinput_bit = UI_SET_SWBIT; break; + default: + rc = -1; + errno = EINVAL; + goto out; + } + + for (code = 0; code < (unsigned int)max; code++) { + if (!libevdev_has_event_code(dev, type, code)) + continue; + + rc = ioctl(fd, uinput_bit, code); + if (rc == -1) + goto out; + + if (type == EV_ABS) { + const struct input_absinfo *abs = libevdev_get_abs_info(dev, code); + uidev->absmin[code] = abs->minimum; + uidev->absmax[code] = abs->maximum; + uidev->absfuzz[code] = abs->fuzz; + uidev->absflat[code] = abs->flat; + /* uinput has no resolution in the device struct, this needs + * to be fixed in the kernel */ + } + } + + } + +out: + return rc; +} + +static int +set_props(const struct libevdev *dev, int fd, struct uinput_user_dev *uidev) +{ + unsigned int prop; + int rc = 0; + + for (prop = 0; prop < INPUT_PROP_MAX; prop++) { + if (!libevdev_has_property(dev, prop)) + continue; + + rc = ioctl(fd, UI_SET_PROPBIT, prop); + if (rc == -1) + break; + } + return rc; +} + +static int +open_uinput(void) +{ + int fd = open("/dev/uinput", O_RDWR|O_CLOEXEC); + if (fd < 0) + return -errno; + + return fd; +} + +LIBEVDEV_EXPORT int +libevdev_uinput_get_fd(const struct libevdev_uinput *uinput_dev) +{ + return uinput_dev->fd; +} + +static int is_event_device(const struct dirent *dent) { + return strncmp("event", dent->d_name, 5) == 0; +} + +static char * +fetch_device_node(const char *path) +{ + char *devnode = NULL; + struct dirent **namelist; + int ndev, i; + + ndev = scandir(path, &namelist, is_event_device, alphasort); + if (ndev <= 0) + return NULL; + + /* ndev should only ever be 1 */ + + for (i = 0; i < ndev; i++) { + asprintf(&devnode, "/dev/input/%s", namelist[i]->d_name); + free(namelist[i]); + } + + free(namelist); + + return devnode; +} + +static int is_input_device(const struct dirent *dent) { + return strncmp("input", dent->d_name, 5) == 0; +} + +static int +fetch_syspath_and_devnode(struct libevdev_uinput *uinput_dev) +{ + struct dirent **namelist; + int ndev, i; + + /* FIXME: use new ioctl() here once kernel supports it */ + + ndev = scandir(SYS_INPUT_DIR, &namelist, is_input_device, alphasort); + if (ndev <= 0) + return -1; + + for (i = 0; i < ndev; i++) { + int fd, len; + char buf[sizeof(SYS_INPUT_DIR) + 64]; + struct stat st; + + strcpy(buf, SYS_INPUT_DIR); + strcat(buf, namelist[i]->d_name); + + if (stat(buf, &st) == -1) + continue; + + /* created before UI_DEV_CREATE, or after it finished */ + if (st.st_ctime < uinput_dev->ctime[0] || + st.st_ctime > uinput_dev->ctime[1]) + continue; + + /* created within time frame */ + strcat(buf, "/name"); + fd = open(buf, O_RDONLY); + if (fd < 0) + continue; + + len = read(fd, buf, sizeof(buf)); + close(fd); + if (len <= 0) + continue; + + buf[len - 1] = '\0'; /* file contains \n */ + if (strcmp(buf, uinput_dev->name) == 0) { + strcpy(buf, SYS_INPUT_DIR); + strcat(buf, namelist[i]->d_name); + uinput_dev->syspath = strdup(buf); + uinput_dev->devnode = fetch_device_node(buf); + } + } + + for (i = 0; i < ndev; i++) + free(namelist[i]); + free(namelist); + + return uinput_dev->devnode ? 0 : -1; +} + + + +LIBEVDEV_EXPORT int +libevdev_uinput_create_from_device(const struct libevdev *dev, int fd, struct libevdev_uinput** uinput_dev) +{ + int rc; + struct uinput_user_dev uidev; + struct libevdev_uinput *new_device; + + new_device = alloc_uinput_device(libevdev_get_name(dev)); + if (!new_device) + return -ENOMEM; + + if (fd == LIBEVDEV_UINPUT_OPEN_MANAGED) { + fd = open_uinput(); + if (fd < 0) + return fd; + + new_device->fd_is_managed = 1; + } else if (fd < 0) + return -EBADF; + + memset(&uidev, 0, sizeof(uidev)); + + strncpy(uidev.name, libevdev_get_name(dev), UINPUT_MAX_NAME_SIZE - 1); + uidev.id.vendor = libevdev_get_id_vendor(dev); + uidev.id.product = libevdev_get_id_product(dev); + uidev.id.bustype = libevdev_get_id_bustype(dev); + uidev.id.version = libevdev_get_id_version(dev); + + if (set_evbits(dev, fd, &uidev) != 0) + goto error; + if (set_props(dev, fd, &uidev) != 0) + goto error; + + rc = write(fd, &uidev, sizeof(uidev)); + if (rc < 0) + goto error; + else if ((size_t)rc < sizeof(uidev)) { + errno = EINVAL; + goto error; + } + + /* ctime notes time before/after ioctl to help us filter out devices + when traversing /sys/devices/virtual/input to find the device + node. + + this is in seconds, so ctime[0]/[1] will almost always be + identical but /sys doesn't give us sub-second ctime so... + */ + new_device->ctime[0] = time(NULL); + + rc = ioctl(fd, UI_DEV_CREATE, NULL); + if (rc == -1) + goto error; + + new_device->ctime[1] = time(NULL); + new_device->fd = fd; + + if (fetch_syspath_and_devnode(new_device) == -1) { + errno = ENODEV; + goto error; + } + + *uinput_dev = new_device; + + return 0; + +error: + libevdev_uinput_destroy(new_device); + return -errno; +} + +LIBEVDEV_EXPORT void +libevdev_uinput_destroy(struct libevdev_uinput *uinput_dev) +{ + ioctl(uinput_dev->fd, UI_DEV_DESTROY, NULL); + if (uinput_dev->fd_is_managed) + close(uinput_dev->fd); + free(uinput_dev->syspath); + free(uinput_dev->devnode); + free(uinput_dev->name); + free(uinput_dev); +} + +LIBEVDEV_EXPORT const char* +libevdev_uinput_get_syspath(struct libevdev_uinput *uinput_dev) +{ + return uinput_dev->syspath; +} + +LIBEVDEV_EXPORT const char* +libevdev_uinput_get_devnode(struct libevdev_uinput *uinput_dev) +{ + return uinput_dev->devnode; +} + +LIBEVDEV_EXPORT int +libevdev_uinput_write_event(const struct libevdev_uinput *uinput_dev, + unsigned int type, + unsigned int code, + int value) +{ + struct input_event ev = { {0,0}, type, code, value }; + int fd = libevdev_uinput_get_fd(uinput_dev); + int rc, max; + + if (type > EV_MAX) + return -EINVAL; + + max = libevdev_get_event_type_max(type); + if (max == -1 || code > (unsigned int)max) + return -EINVAL; + + rc = write(fd, &ev, sizeof(ev)); + + return rc < 0 ? -errno : 0; +} |
