[PATCH libevdev 1/6] Add support for uinput device creation
Peter Hutterer
peter.hutterer at who-t.net
Tue Aug 13 03:39:50 PDT 2013
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).
Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
doc/libevdev.doxygen.in | 3 +-
libevdev/Makefile.am | 5 +-
libevdev/libevdev-uinput-int.h | 29 +++++
libevdev/libevdev-uinput.c | 285 +++++++++++++++++++++++++++++++++++++++++
libevdev/libevdev-uinput.h | 232 +++++++++++++++++++++++++++++++++
libevdev/libevdev.c | 1 -
test/Makefile.am | 3 +
7 files changed, 555 insertions(+), 3 deletions(-)
create mode 100644 libevdev/libevdev-uinput-int.h
create mode 100644 libevdev/libevdev-uinput.c
create mode 100644 libevdev/libevdev-uinput.h
diff --git a/doc/libevdev.doxygen.in b/doc/libevdev.doxygen.in
index 4511ae8..855b317 100644
--- a/doc/libevdev.doxygen.in
+++ b/doc/libevdev.doxygen.in
@@ -668,7 +668,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 9879c0e..2e99c2a 100644
--- a/libevdev/Makefile.am
+++ b/libevdev/Makefile.am
@@ -6,12 +6,15 @@ 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..70fa1e1
--- /dev/null
+++ b/libevdev/libevdev-uinput-int.h
@@ -0,0 +1,29 @@
+/*
+ * 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 */
+ char *name; /**< device name */
+ char *syspath; /**< /sys path */
+ 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..3009fba
--- /dev/null
+++ b/libevdev/libevdev-uinput.c
@@ -0,0 +1,285 @@
+/*
+ * 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);
+
+ return uinput_dev;
+}
+
+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 < 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;
+}
+
+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;
+}
+
+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;
+
+ 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 (rc < sizeof(uidev)) {
+ errno = EINVAL;
+ goto error;
+ }
+
+ /* ctime notes before/after ioctl to help us filter out devices
+ when traversing /sys/devices/virtual/input inl
+ libevdev_uinput_get_syspath.
+
+ 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;
+
+ *uinput_dev = new_device;
+
+ return 0;
+
+error:
+ return -errno;
+}
+
+void libevdev_uinput_destroy(struct libevdev_uinput *uinput_dev)
+{
+ ioctl(uinput_dev->fd, UI_DEV_DESTROY, NULL);
+ free(uinput_dev->name);
+ free(uinput_dev);
+}
+
+int libevdev_uinput_get_fd(const struct libevdev_uinput *uinput_dev)
+{
+ return uinput_dev->fd;
+}
+
+const char* libevdev_uinput_get_syspath(struct libevdev_uinput *uinput_dev)
+{
+ DIR *dir;
+ struct dirent *dent;
+
+ if (uinput_dev->syspath != NULL)
+ return uinput_dev->syspath;
+
+ /* FIXME: use new ioctl() here once kernel supports it */
+
+ dir = opendir(SYS_INPUT_DIR);
+ if (!dir)
+ return NULL;
+
+ while((dent = readdir(dir))) {
+ struct stat st;
+ int fd;
+ char path[sizeof(SYS_INPUT_DIR) + 64];
+ char buf[256];
+ int len;
+
+ if (strncmp("input", dent->d_name, 5) != 0)
+ continue;
+
+ strcpy(path, SYS_INPUT_DIR);
+ strcat(path, dent->d_name);
+
+ if (stat(path, &st) == -1)
+ continue;
+
+ /* created before/after UI_DEV_CREATE */
+ if (st.st_ctime < uinput_dev->ctime[0] ||
+ st.st_ctime > uinput_dev->ctime[1])
+ continue;
+
+ /* created after UI_DEV_CREATE */
+ strcat(path, "/name");
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ continue;
+
+ len = read(fd, buf, sizeof(buf));
+ if (len > 1) {
+ buf[len - 1] = '\0'; /* file contains \n */
+ if (strcmp(buf, uinput_dev->name) == 0) {
+ strcpy(path, SYS_INPUT_DIR);
+ strcat(path, dent->d_name);
+ uinput_dev->syspath = strdup(path);
+ close(fd);
+ break;
+ }
+ }
+
+ close(fd);
+ }
+
+ closedir(dir);
+
+ return uinput_dev->syspath;
+}
+
+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;
+
+ if (type > EV_MAX)
+ return -EINVAL;
+
+ if (code > libevdev_get_event_type_max(type))
+ 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..9e19ad2
--- /dev/null
+++ b/libevdev/libevdev-uinput.h
@@ -0,0 +1,232 @@
+/*
+ * 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 <libevdev/libevdev.h>
+
+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);
+ *
+ * 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;
+ *
+ * // ... do something ...
+ *
+ * libevdev_uinput_destroy(uidev);
+ * close(uifd);
+ *
+ * @endcode
+ */
+
+/**
+ * @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.
+ *
+ * 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 A file descriptor to @c /dev/uinput, opened with the required
+ * permissions to create a device. This fd may only be used once to create a
+ * uinput device.
+ * @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.
+ *
+ * @note libevdev_uinput_destroy() does not close the fd.
+ *
+ * @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 <strong>/dev/uinput</strong>. 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.
+ *
+ * To obtain a /dev/input/eventX device node from this path use the following code:
+ *
+ * @code
+ * DIR *dir;
+ * const char *syspath;
+ * struct dirent *dp;
+ * struct udev *udev;
+ * struct udev_device *udev_device;
+ *
+ * syspath = libevdev_uinput_get_syspath(uidev);
+ * udev = udev_new();
+ * dir = opendir(syspath);
+ * while ((dp = readdir(dir))) {
+ * char *path;
+ *
+ * if (strncmp(dp->d_name, "event", 5) != 0)
+ * continue;
+ *
+ * asprintf(&path, "%s/%s", syspath, dp->d_name);
+ * udev_device = udev_device_new_from_syspath(udev, path);
+ * printf("device node: %s\n", udev_device_get_devnode(udev_device));
+ * udev_device_unref(udev_device);
+ * free(path);
+ * }
+ *
+ * closedir(dir);
+ * udev_unref(udev);
+ * @endcode
+ *
+ *
+ * @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.
+ */
+const char*libevdev_uinput_get_syspath(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/libevdev/libevdev.c b/libevdev/libevdev.c
index 5883447..9448f1c 100644
--- a/libevdev/libevdev.c
+++ b/libevdev/libevdev.c
@@ -27,7 +27,6 @@
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
-#include <linux/uinput.h>
#include "libevdev.h"
#include "libevdev-int.h"
diff --git a/test/Makefile.am b/test/Makefile.am
index 12f7176..8cad6c7 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -5,8 +5,11 @@ 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-util.h \
$(top_srcdir)/libevdev/libevdev-int.h
+ $(top_srcdir)/libevdev/libevdev-uinput-int.h
common_sources = $(libevdev_sources) \
test-common-uinput.c \
test-common-uinput.h \
--
1.8.2.1
More information about the Input-tools
mailing list