From: Peter Hutterer Date: Thu, 25 Jul 2013 05:56:11 +0000 (+1000) Subject: Add support for uinput device creation X-Git-Tag: libevdev-0.4~38 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=1acbfb35799485326f5d76280282f320828aec38;p=platform%2Fupstream%2Flibevdev.git 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 --- diff --git a/doc/libevdev.doxygen.in b/doc/libevdev.doxygen.in index 6eec0af..fc5d7bb 100644 --- a/doc/libevdev.doxygen.in +++ b/doc/libevdev.doxygen.in @@ -652,7 +652,8 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = @top_srcdir@/libevdev/libevdev.h +INPUT = @top_srcdir@/libevdev/libevdev.h \ + @top_srcdir@/libevdev/libevdev-uinput.h # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/libevdev/Makefile.am b/libevdev/Makefile.am index 3a67464..5ebbd42 100644 --- a/libevdev/Makefile.am +++ b/libevdev/Makefile.am @@ -1,17 +1,20 @@ lib_LTLIBRARIES=libevdev.la -AM_CPPFLAGS = $(GCC_CFLAGS) $(GCOV_CFLAGS) +AM_CPPFLAGS = $(GCC_CFLAGS) $(GCOV_CFLAGS) -I$(top_srcdir) libevdev_la_SOURCES = \ libevdev.h \ libevdev-int.h \ libevdev-util.h \ + libevdev-uinput.c \ + libevdev-uinput.h \ + libevdev-uinput-int.h \ libevdev.c libevdev_la_LDFLAGS = -version-info $(LIBEVDEV_LT_VERSION) -export-symbols-regex '^libevdev_' $(GCOV_LDFLAGS) libevdevincludedir = $(includedir)/libevdev-1.0/libevdev -libevdevinclude_HEADERS = libevdev.h +libevdevinclude_HEADERS = libevdev.h libevdev-uinput.h event-names.h: Makefile make-event-names.py $(srcdir)/make-event-names.py --output=c > $@ diff --git a/libevdev/libevdev-uinput-int.h b/libevdev/libevdev-uinput-int.h new file mode 100644 index 0000000..fbc1c29 --- /dev/null +++ b/libevdev/libevdev-uinput-int.h @@ -0,0 +1,31 @@ +/* + * 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. + */ + + +struct libevdev_uinput { + int fd; /**< file descriptor to uinput */ + int fd_is_managed; /**< do we need to close it? */ + char *name; /**< device name */ + char *syspath; /**< /sys path */ + char *devnode; /**< device node */ + time_t ctime[2]; /**< before/after UI_DEV_CREATE */ +}; 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/libevdev/libevdev-uinput.h b/libevdev/libevdev-uinput.h new file mode 100644 index 0000000..45b8a18 --- /dev/null +++ b/libevdev/libevdev-uinput.h @@ -0,0 +1,230 @@ +/* + * 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. + */ + +#ifndef libevdev_uinput_H +#define libevdev_uinput_H + +#include + +struct libevdev_uinput; + +/** + * @defgroup uinput uinput device creation + * + * Creation of uinput devices based on existing libevdev devices. These functions + * help to create uinput devices that emulate libevdev devices. In the simplest + * form it serves to duplicate an existing device: + * + * @code + * int err; + * int new_fd; + * struct libevdev *dev; + * struct libevdev_uinput *uidev; + * struct input_event ev[2]; + * + * err = libevdev_new_from_fd(&dev, fd); + * if (err != 0) + * return err; + * + * uifd = open("/dev/uinput", O_RDWR); + * if (uidev < 0) + * return -errno; + * + * err = libevdev_uinput_create_from_device(dev, uifd, &uidev); + * if (err != 0) + * return err; + * + * // post a REL_X event + * err = libevdev_uinput_write(event(uidev, EV_REL, REL_X, -1); + * if (err != 0) + * return err; + * libevdev_uinput_write(event(uidev, EV_SYN, SYN_REPORT, 0); + * if (err != 0) + * return err; + * + * libevdev_uinput_destroy(uidev); + * close(uifd); + * + * @endcode + * + * Alternatively, a device can be constructed from scratch: + * + * @code + * int err; + * struct libevdev *dev; + * struct libevdev_uinput *uidev; + * + * dev = libevdev_new(); + * libevdev_set_name(dev, "test 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); + * + * err = libevdev_uinput_create_from_device(dev, + * LIBEVDEV_UINPUT_OPEN_MANAGED, + * &uidev); + * if (err != 0) + * return err; + * + * // ... do something ... + * + * libevdev_uinput_destroy(uidev); + * + * @endcode + */ + +enum libevdev_uinput_open_mode { + /* intentionally -2 to avoid to avoid code like the below from accidentally working: + fd = open("/dev/uinput", O_RDWR); // fails, fd is -1 + libevdev_uinput_create_from_device(dev, fd, &uidev); // may hide the error */ + LIBEVDEV_UINPUT_OPEN_MANAGED = -2, /**< let libevdev open and close @c /dev/uinput */ +}; + +/** + * @ingroup uinput + * + * Create a uinput device based on the libevdev device given. The uinput device + * will be an exact copy of the libevdev device, minus the bits that uinput doesn't + * allow to be set. + * + * If uinput_fd is LIBEVDEV_UINPUT_OPEN_MANAGED, libevdev_uinput_create_from_device() + * will open @c /dev/uinput in read/write mode and manage the file descriptor. + * Otherwise, uinput_fd must be opened by the caller and opened with the + * appropriate permissions. + * + * The device's lifetime is tied to the uinput file descriptor, closing it will + * destroy the uinput device. You should call libevdev_uinput_destroy() before + * closing the file descriptor to free allocated resources. + * A file descriptor can only create one uinput device at a time; the second device + * will fail with -EINVAL. + * + * You don't need to keep the file descriptor variable around, + * libevdev_uinput_get_fd() will return it when needed. + * + * @note Due to limitations in the uinput kernel module, REP_DELAY and + * REP_PERIOD will default to the kernel defaults, not to the ones set in the + * source device. + * + * @param dev The device to duplicate + * @param uinput_fd LIBEVDEV_UINPUT_OPEN_MANAGED or a file descriptor to @c /dev/uinput, + * @param[out] uinput_dev The newly created libevdev device. + * + * @return 0 on success or a negative errno on failure. On failure, the value of + * uinput_dev is unmodified. + * + * @see libevdev_uinput_destroy + */ +int libevdev_uinput_create_from_device(const struct libevdev *dev, + int uinput_fd, + struct libevdev_uinput **uinput_dev); + +/** + * @ingroup uinput + * + * Destroy a previously created uinput device and free associated memory. + * + * If the device was opened with LIBEVDEV_UINPUT_OPEN_MANAGED, libevdev_uinput_destroy() + * also closes the file descriptor. Otherwise, the fd is left as-is and + * must be closed by the caller. + * + * @param uinput_dev A previously created uinput device. + * + * @return 0 on success or a negative errno on failure + */ +void libevdev_uinput_destroy(struct libevdev_uinput *uinput_dev); + +/** + * @ingroup uinput + * + * Return the file descriptor used to create this uinput device. This is the + * fd pointing to /dev/uinput. This file descriptor may be used to write + * events that are emitted by the uinput device. + * Closing this file descriptor will destroy the uinput device, you should + * call libevdev_uinput_destroy() first to free allocated resources. + * + * @param uinput_dev A previously created uinput device. + * + * @return The file descriptor used to create this device + */ +int libevdev_uinput_get_fd(const struct libevdev_uinput *uinput_dev); + +/** + * @ingroup uinput + * + * Return the syspath representing this uinput device. + * As of 3.11, the uinput kernel device does not + * provide a way to get the syspath directly through uinput so libevdev must guess. + * In some cases libevdev is unable to derive the syspath. If the running kernel + * supports the UI_GET_SYSPATH ioctl, the syspath is retrieved through that and will + * be reliable and not be NULL. + * + * @note This function may return NULL. libevdev currently uses ctime and + * the device name to guess devices. To avoid false positives, wait at least + * wait at least 1.5s between creating devices that have the same name. + * @param uinput_dev A previously created uinput device. + * @return The syspath for this device, including preceding /sys. + * + * @see libevdev_uinput_get_devnode + */ +const char*libevdev_uinput_get_syspath(struct libevdev_uinput *uinput_dev); + +/** + * @ingroup uinput + * + * Return the device node representing this uinput device. + * + * This relies on libevdev_uinput_get_syspath() to provide a valid syspath. + * See libevdev_uinput_get_syspath() for more details. + * + * @note This function may return NULL. libevdev currently has to guess the + * syspath and the device node. See libevdev_uinput_get_syspath() for details. + * @param uinput_dev A previously created uinput device. + * @return The device node for this device, in the form of /dev/input/eventN + * + * @see libevdev_uinput_get_syspath + */ +const char* libevdev_uinput_get_devnode(struct libevdev_uinput *uinput_dev); + +/** + * @ingroup uinput + * + * Post an event through the uinput device. It is the caller's responsibility + * that any event sequence is terminated with an EV_SYN/SYN_REPORT/0 event. + * Otherwise, listeners on the device node will not see the events until the + * next EV_SYN event is posted. + * + * @param uinput_dev A previously created uinput device. + * @param type Event type (EV_ABS, EV_REL, etc.) + * @param code Event code (ABS_X, REL_Y, etc.) + * @param value The event value + * @return 0 on success or a negative errno on error + */ +int libevdev_uinput_write_event(const struct libevdev_uinput *uinput_dev, + unsigned int type, + unsigned int code, + int value); + +#endif /* libevdev_uinput_H */ diff --git a/test/Makefile.am b/test/Makefile.am index 12f7176..d3642c6 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -5,6 +5,9 @@ TESTS = $(noinst_PROGRAMS) libevdev_sources = $(top_srcdir)/libevdev/libevdev.c \ $(top_srcdir)/libevdev/libevdev.h \ + $(top_srcdir)/libevdev/libevdev-uinput.h \ + $(top_srcdir)/libevdev/libevdev-uinput.c \ + $(top_srcdir)/libevdev/libevdev-uinput-int.h \ $(top_srcdir)/libevdev/libevdev-util.h \ $(top_srcdir)/libevdev/libevdev-int.h common_sources = $(libevdev_sources) \