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>
This commit is contained in:
Peter Hutterer 2013-07-25 15:56:11 +10:00
parent 1b7c46b2f1
commit 1acbfb3579
6 changed files with 632 additions and 3 deletions

View file

@ -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

View file

@ -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 > $@

View file

@ -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 */
};

361
libevdev/libevdev-uinput.c Normal file
View file

@ -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;
}

230
libevdev/libevdev-uinput.h Normal file
View file

@ -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 <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);
*
* 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 <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.
*
* @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 */

View file

@ -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) \