summaryrefslogtreecommitdiff
path: root/libevdev/libevdev-uinput.c
diff options
context:
space:
mode:
authorPeter Hutterer <peter.hutterer@who-t.net>2013-07-25 15:56:11 +1000
committerPeter Hutterer <peter.hutterer@who-t.net>2013-08-29 13:54:46 +1000
commit1acbfb35799485326f5d76280282f320828aec38 (patch)
treefb7de085ba8996a043e5f801fb50bae073dccebe /libevdev/libevdev-uinput.c
parent1b7c46b2f16f22bb87e76e55c28a717f5f8f248f (diff)
downloadlibevdev-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.c361
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;
+}