config: add libudev input-hotplug backend

Add a backend using libudev for input hotplug, and disable the hal and
dbus backends if this one is enabled.

XKB configuration happens using xkb{rules,model,layout,variant,options}
properties (case-insensitive) on the device.  We fill in InputAttributes
to allow configuration through InputClass in Xorg.

Requires udev 148 for the input_id helper and ID_INPUT* properties.

Signed-off-by: Julien Cristau <jcristau@debian.org>
Acked-by: Dan Nicholson <dbn.lists@gmail.com>
Reviewed-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Julien Cristau 2009-09-27 14:45:47 +02:00
parent b8b12e41c4
commit 435f27667f
11 changed files with 416 additions and 82 deletions

View File

@ -1,12 +1,20 @@
AM_CFLAGS = @DIX_CFLAGS@
AM_CFLAGS = $(DIX_CFLAGS)
noinst_LTLIBRARIES = libconfig.la
libconfig_la_SOURCES = config.c config-backends.h
if CONFIG_UDEV
AM_CFLAGS += $(UDEV_CFLAGS)
libconfig_la_SOURCES += udev.c
libconfig_la_LIBADD = $(UDEV_LIBS)
else
if CONFIG_NEED_DBUS
AM_CFLAGS += @DBUS_CFLAGS@
AM_CFLAGS += $(DBUS_CFLAGS)
libconfig_la_SOURCES += dbus-core.c
endif
libconfig_la_LIBADD = $(DBUS_LIBS)
if CONFIG_DBUS_API
dbusconfigdir = $(sysconfdir)/dbus-1/system.d
@ -16,7 +24,13 @@ libconfig_la_SOURCES += dbus.c
endif
if CONFIG_HAL
AM_CFLAGS += $(HAL_CFLAGS)
libconfig_la_SOURCES += hal.c
libconfig_la_LIBADD += $(HAL_LIBS)
endif
endif # CONFIG_NEED_DBUS
endif # !CONFIG_UDEV
EXTRA_DIST = xorg-server.conf x11-input.fdi

View File

@ -26,8 +26,18 @@
#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif
#include "input.h"
#ifdef CONFIG_NEED_DBUS
void remove_devices(const char *backend, const char *config_info);
BOOL device_is_duplicate(const char *config_info);
void add_option(InputOption **options, const char *key, const char *value);
#ifdef CONFIG_UDEV
int config_udev_init(void);
void config_udev_fini(void);
#else
# ifdef CONFIG_NEED_DBUS
#include <dbus/dbus.h>
typedef void (*config_dbus_core_connect_hook)(DBusConnection *connection,
@ -46,14 +56,15 @@ int config_dbus_core_init(void);
void config_dbus_core_fini(void);
int config_dbus_core_add_hook(struct config_dbus_core_hook *hook);
void config_dbus_core_remove_hook(struct config_dbus_core_hook *hook);
#endif
# endif
#ifdef CONFIG_DBUS_API
# ifdef CONFIG_DBUS_API
int config_dbus_init(void);
void config_dbus_fini(void);
#endif
# endif
#ifdef CONFIG_HAL
# ifdef CONFIG_HAL
int config_hal_init(void);
void config_hal_fini(void);
# endif
#endif

View File

@ -28,13 +28,17 @@
#endif
#include "os.h"
#include "inputstr.h"
#include "hotplug.h"
#include "config-backends.h"
void
config_init(void)
{
#if defined(CONFIG_DBUS_API) || defined(CONFIG_HAL)
#ifdef CONFIG_UDEV
if (!config_udev_init())
ErrorF("[config] failed to initialise udev\n");
#elif defined(CONFIG_NEED_DBUS)
if (config_dbus_core_init()) {
# ifdef CONFIG_DBUS_API
if (!config_dbus_init())
@ -54,7 +58,9 @@ config_init(void)
void
config_fini(void)
{
#if defined(CONFIG_DBUS_API) || defined(CONFIG_HAL)
#if defined(CONFIG_UDEV)
config_udev_fini();
#elif defined(CONFIG_NEED_DBUS)
# ifdef CONFIG_HAL
config_hal_fini();
# endif
@ -64,3 +70,70 @@ config_fini(void)
config_dbus_core_fini();
#endif
}
static void
remove_device(const char *backend, DeviceIntPtr dev)
{
/* this only gets called for devices that have already been added */
LogMessage(X_INFO, "config/%s: removing device %s\n", backend, dev->name);
/* Call PIE here so we don't try to dereference a device that's
* already been removed. */
OsBlockSignals();
ProcessInputEvents();
DeleteInputDeviceRequest(dev);
OsReleaseSignals();
}
void
remove_devices(const char *backend, const char *config_info)
{
DeviceIntPtr dev, next;
for (dev = inputInfo.devices; dev; dev = next) {
next = dev->next;
if (dev->config_info && strcmp(dev->config_info, config_info) == 0)
remove_device(backend, dev);
}
for (dev = inputInfo.off_devices; dev; dev = next) {
next = dev->next;
if (dev->config_info && strcmp(dev->config_info, config_info) == 0)
remove_device(backend, dev);
}
}
BOOL
device_is_duplicate(const char *config_info)
{
DeviceIntPtr dev;
for (dev = inputInfo.devices; dev; dev = dev->next)
{
if (dev->config_info && (strcmp(dev->config_info, config_info) == 0))
return TRUE;
}
for (dev = inputInfo.off_devices; dev; dev = dev->next)
{
if (dev->config_info && (strcmp(dev->config_info, config_info) == 0))
return TRUE;
}
return FALSE;
}
void
add_option(InputOption **options, const char *key, const char *value)
{
if (!value || *value == '\0')
return;
for (; *options; options = &(*options)->next)
;
*options = xcalloc(sizeof(**options), 1);
if (!*options) /* Yeesh. */
return;
(*options)->key = xstrdup(key);
(*options)->value = xstrdup(value);
(*options)->next = NULL;
}

View File

@ -58,25 +58,9 @@ struct xkb_options {
char* options;
};
static void
remove_device(DeviceIntPtr dev)
{
/* this only gets called for devices that have already been added */
LogMessage(X_INFO, "config/hal: removing device %s\n", dev->name);
/* Call PIE here so we don't try to dereference a device that's
* already been removed. */
OsBlockSignals();
ProcessInputEvents();
DeleteInputDeviceRequest(dev);
OsReleaseSignals();
}
static void
device_removed(LibHalContext *ctx, const char *udi)
{
DeviceIntPtr dev, next;
char *value;
value = xalloc(strlen(udi) + 5); /* "hal:" + NULL */
@ -84,36 +68,11 @@ device_removed(LibHalContext *ctx, const char *udi)
return;
sprintf(value, "hal:%s", udi);
for (dev = inputInfo.devices; dev; dev = next) {
next = dev->next;
if (dev->config_info && strcmp(dev->config_info, value) == 0)
remove_device(dev);
}
for (dev = inputInfo.off_devices; dev; dev = next) {
next = dev->next;
if (dev->config_info && strcmp(dev->config_info, value) == 0)
remove_device(dev);
}
remove_devices("hal", value);
xfree(value);
}
static void
add_option(InputOption **options, const char *key, const char *value)
{
if (!value || *value == '\0')
return;
for (; *options; options = &(*options)->next)
;
*options = xcalloc(sizeof(**options), 1);
if (!*options) /* Yeesh. */
return;
(*options)->key = xstrdup(key);
(*options)->value = xstrdup(value);
(*options)->next = NULL;
}
static char *
get_prop_string(LibHalContext *hal_ctx, const char *udi, const char *name)
{
@ -166,26 +125,6 @@ get_prop_string_array(LibHalContext *hal_ctx, const char *udi, const char *prop)
return ret;
}
static BOOL
device_is_duplicate(char *config_info)
{
DeviceIntPtr dev;
for (dev = inputInfo.devices; dev; dev = dev->next)
{
if (dev->config_info && (strcmp(dev->config_info, config_info) == 0))
return TRUE;
}
for (dev = inputInfo.off_devices; dev; dev = dev->next)
{
if (dev->config_info && (strcmp(dev->config_info, config_info) == 0))
return TRUE;
}
return FALSE;
}
static void
device_added(LibHalContext *hal_ctx, const char *udi)
{

260
config/udev.c Normal file
View File

@ -0,0 +1,260 @@
/*
* Copyright © 2009 Julien Cristau
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Author: Julien Cristau <jcristau@debian.org>
*/
#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif
#include <libudev.h>
#include "input.h"
#include "inputstr.h"
#include "hotplug.h"
#include "config-backends.h"
#include "os.h"
#define UDEV_XKB_PROP_KEY "xkb"
static struct udev_monitor *udev_monitor;
static void
device_added(struct udev_device *udev_device)
{
const char *path, *name = NULL;
char *config_info = NULL;
const char *syspath;
const char *key, *value, *tmp;
InputOption *options = NULL, *tmpo;
InputAttributes attrs = {};
DeviceIntPtr dev = NULL;
struct udev_list_entry *set, *entry;
struct udev_device *parent;
int rc;
path = udev_device_get_devnode(udev_device);
syspath = udev_device_get_syspath(udev_device);
if (!path || !syspath)
return;
if (!udev_device_get_property_value(udev_device, "ID_INPUT"))
return;
options = xcalloc(sizeof(*options), 1);
if (!options)
return;
options->key = xstrdup("_source");
options->value = xstrdup("server/udev");
if (!options->key || !options->value)
goto unwind;
parent = udev_device_get_parent(udev_device);
if (parent)
name = udev_device_get_property_value(parent, "NAME");
if (!name)
name = "(unnamed)";
else
attrs.product = name;
add_option(&options, "name", name);
add_option(&options, "path", path);
add_option(&options, "device", path);
attrs.device = path;
config_info = Xprintf("udev:%s", syspath);
if (!config_info)
goto unwind;
if (device_is_duplicate(config_info)) {
LogMessage(X_WARNING, "config/udev: device %s already added. "
"Ignoring.\n", name);
goto unwind;
}
set = udev_device_get_properties_list_entry(udev_device);
udev_list_entry_foreach(entry, set) {
key = udev_list_entry_get_name(entry);
if (!key)
continue;
value = udev_list_entry_get_value(entry);
if (!strncasecmp(key, UDEV_XKB_PROP_KEY,
sizeof(UDEV_XKB_PROP_KEY) - 1)) {
tmp = key + sizeof(UDEV_XKB_PROP_KEY) - 1;
if (!strcasecmp(tmp, "rules"))
add_option(&options, "xkb_rules", value);
else if (!strcasecmp(tmp, "layout"))
add_option(&options, "xkb_layout", value);
else if (!strcasecmp(tmp, "variant"))
add_option(&options, "xkb_variant", value);
else if (!strcasecmp(tmp, "model"))
add_option(&options, "xkb_model", value);
else if (!strcasecmp(tmp, "options"))
add_option(&options, "xkb_options", value);
} else if (!strcmp(key, "ID_VENDOR")) {
attrs.vendor = value;
} else if (!strcmp(key, "ID_INPUT_KEY")) {
attrs.flags |= ATTR_KEYBOARD;
} else if (!strcmp(key, "ID_INPUT_MOUSE")) {
attrs.flags |= ATTR_POINTER;
} else if (!strcmp(key, "ID_INPUT_JOYSTICK")) {
attrs.flags |= ATTR_JOYSTICK;
} else if (!strcmp(key, "ID_INPUT_TABLET")) {
attrs.flags |= ATTR_TABLET;
} else if (!strcmp(key, "ID_INPUT_TOUCHPAD")) {
attrs.flags |= ATTR_TOUCHPAD;
} else if (!strcmp(key, "ID_INPUT_TOUCHSCREEN")) {
attrs.flags |= ATTR_TOUCHSCREEN;
}
}
LogMessage(X_INFO, "config/udev: Adding input device %s (%s)\n",
name, path);
rc = NewInputDeviceRequest(options, &attrs, &dev);
if (rc != Success)
goto unwind;
for (; dev; dev = dev->next) {
xfree(dev->config_info);
dev->config_info = xstrdup(config_info);
}
unwind:
xfree(config_info);
while (!dev && (tmpo = options)) {
options = tmpo->next;
xfree(tmpo->key);
xfree(tmpo->value);
xfree(tmpo);
}
return;
}
static void
device_removed(struct udev_device *device)
{
char *value;
const char *syspath = udev_device_get_syspath(device);
value = Xprintf("udev:%s", syspath);
if (!value)
return;
remove_devices("udev", value);
xfree(value);
}
static void
wakeup_handler(pointer data, int err, pointer read_mask)
{
int udev_fd = udev_monitor_get_fd(udev_monitor);
struct udev_device *udev_device;
const char *action;
if (err < 0)
return;
if (FD_ISSET(udev_fd, (fd_set *)read_mask)) {
udev_device = udev_monitor_receive_device(udev_monitor);
if (!udev_device)
return;
action = udev_device_get_action(udev_device);
if (action) {
if (!strcmp(action, "add"))
device_added(udev_device);
else if (!strcmp(action, "remove"))
device_removed(udev_device);
}
udev_device_unref(udev_device);
}
}
static void
block_handler(pointer data, struct timeval **tv, pointer read_mask)
{
}
int
config_udev_init(void)
{
struct udev *udev;
struct udev_enumerate *enumerate;
struct udev_list_entry *devices, *device;
int rc;
udev = udev_new();
if (!udev)
return 0;
udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
if (!udev_monitor)
return 0;
rc = udev_monitor_filter_add_match_subsystem_devtype(udev_monitor,
"input", NULL);
if (rc < 0)
return 0;
if (udev_monitor_enable_receiving(udev_monitor)) {
ErrorF("config/udev: failed to bind the udev monitor\n");
return 0;
}
enumerate = udev_enumerate_new(udev);
if (!enumerate)
return 0;
udev_enumerate_add_match_subsystem(enumerate, "input");
udev_enumerate_scan_devices(enumerate);
devices = udev_enumerate_get_list_entry(enumerate);
udev_list_entry_foreach(device, devices) {
const char *syspath = udev_list_entry_get_name(device);
struct udev_device *udev_device = udev_device_new_from_syspath(udev, syspath);
device_added(udev_device);
udev_device_unref(udev_device);
}
udev_enumerate_unref(enumerate);
RegisterBlockAndWakeupHandlers(block_handler, wakeup_handler, NULL);
AddGeneralSocket(udev_monitor_get_fd(udev_monitor));
return 1;
}
void
config_udev_fini(void)
{
struct udev *udev;
if (!udev_monitor)
return;
udev = udev_monitor_get_udev(udev_monitor);
RemoveGeneralSocket(udev_monitor_get_fd(udev_monitor));
RemoveBlockAndWakeupHandlers(block_handler, wakeup_handler, udev_monitor);
udev_monitor_unref(udev_monitor);
udev_monitor = NULL;
udev_unref(udev);
}

View File

@ -618,6 +618,7 @@ AC_ARG_ENABLE(multibuffer, AS_HELP_STRING([--enable-multibuffer], [Build Mult
AC_ARG_ENABLE(dbe, AS_HELP_STRING([--disable-dbe], [Build DBE extension (default: enabled)]), [DBE=$enableval], [DBE=yes])
AC_ARG_ENABLE(xf86bigfont, AS_HELP_STRING([--disable-xf86bigfont], [Build XF86 Big Font extension (default: disabled)]), [XF86BIGFONT=$enableval], [XF86BIGFONT=no])
AC_ARG_ENABLE(dpms, AS_HELP_STRING([--disable-dpms], [Build DPMS extension (default: enabled)]), [DPMSExtension=$enableval], [DPMSExtension=yes])
AC_ARG_ENABLE(config-udev, AS_HELP_STRING([--enable-config-udev], [Build udev support (default: no)]), [CONFIG_UDEV=$enableval], [CONFIG_UDEV=no])
AC_ARG_ENABLE(config-dbus, AS_HELP_STRING([--enable-config-dbus], [Build D-BUS API support (default: no)]), [CONFIG_DBUS_API=$enableval], [CONFIG_DBUS_API=no])
AC_ARG_ENABLE(config-hal, AS_HELP_STRING([--disable-config-hal], [Build HAL support (default: auto)]), [CONFIG_HAL=$enableval], [CONFIG_HAL=auto])
AC_ARG_ENABLE(xfree86-utils, AS_HELP_STRING([--enable-xfree86-utils], [Build xfree86 DDX utilities (default: enabled)]), [XF86UTILS=$enableval], [XF86UTILS=yes])
@ -775,6 +776,26 @@ LIBXI="xi >= 1.2.99.1"
LIBXTST="xtst >= 1.0.99.2"
LIBPCIACCESS="pciaccess >= 0.8.0"
LIBGLIB="glib-2.0 >= 2.16"
LIBUDEV="libudev >= 143"
if test "x$CONFIG_UDEV" = xyes &&
{ test "x$CONFIG_DBUS_API" = xyes || test "x$CONFIG_HAL" = xyes; }; then
AC_MSG_ERROR([Hotplugging through both libudev and dbus/hal not allowed])
fi
PKG_CHECK_MODULES(UDEV, $LIBUDEV, [HAVE_LIBUDEV=yes], [HAVE_LIBUDEV=no])
if test "x$CONFIG_UDEV" = xauto; then
CONFIG_UDEV="$HAVE_LIBUDEV"
fi
AM_CONDITIONAL(CONFIG_UDEV, [test "x$CONFIG_UDEV" = xyes])
if test "x$CONFIG_UDEV" = xyes; then
CONFIG_DBUS_API=no
CONFIG_HAL=no
if ! test "x$HAVE_LIBUDEV" = xyes; then
AC_MSG_ERROR([udev configuration API requested, but libudev is not installed])
fi
AC_DEFINE(CONFIG_UDEV, 1, [Use libudev for input hotplug])
fi
dnl HAVE_DBUS is true if we actually have the D-Bus library, whereas
dnl CONFIG_DBUS_API is true if we want to enable the D-Bus config
@ -808,13 +829,11 @@ if test "x$CONFIG_HAL" = xyes; then
fi
AC_DEFINE(CONFIG_HAL, 1, [Use the HAL hotplug API])
REQUIRED_LIBS="$REQUIRED_LIBS hal"
CONFIG_NEED_DBUS="yes"
fi
AM_CONDITIONAL(CONFIG_HAL, [test "x$CONFIG_HAL" = xyes])
if test "x$CONFIG_NEED_DBUS" = xyes; then
REQUIRED_LIBS="$REQUIRED_LIBS dbus-1"
AC_DEFINE(CONFIG_NEED_DBUS, 1, [Use D-Bus for input hotplug])
fi
AM_CONDITIONAL(CONFIG_NEED_DBUS, [test "x$CONFIG_NEED_DBUS" = xyes])

View File

@ -2280,6 +2280,14 @@ NewInputDeviceRequest(InputOption *options, InputAttributes *attrs,
ErrorF("Ignoring device from HAL.\n");
return BadValue;
}
#endif
#ifdef CONFIG_UDEV
else if (strcmp(option->key, "_source") == 0 &&
strcmp(option->value, "server/udev") == 0)
{
ErrorF("Ignoring device from udev.\n");
return BadValue;
}
#endif
}

View File

@ -1461,12 +1461,19 @@ checkCoreInputDevices(serverLayoutPtr servlayoutp, Bool implicitLayout)
}
if (xf86Info.allowEmptyInput && !(foundPointer && foundKeyboard)) {
#ifdef CONFIG_HAL
xf86Msg(X_INFO, "The server relies on HAL to provide the list of "
"input devices.\n\tIf no devices become available, "
"reconfigure HAL or disable AutoAddDevices.\n");
#if defined(CONFIG_HAL) || defined(CONFIG_UDEV)
const char *config_backend;
#if defined(CONFIG_HAL)
config_backend = "HAL";
#else
xf86Msg(X_INFO, "HAL is disabled and no input devices were configured.\n"
config_backend = "udev";
#endif
xf86Msg(X_INFO, "The server relies on %s to provide the list of "
"input devices.\n\tIf no devices become available, "
"reconfigure %s or disable AutoAddDevices.\n",
config_backend, config_backend);
#else
xf86Msg(X_INFO, "Hotplugging is disabled and no input devices were configured.\n"
"\tTry disabling AllowEmptyInput.\n");
#endif
}

View File

@ -132,7 +132,7 @@ xf86InfoRec xf86Info = {
.kbdCustomKeycodes = FALSE,
.disableRandR = FALSE,
.randRFrom = X_DEFAULT,
#ifdef CONFIG_HAL
#if defined(CONFIG_HAL) || defined(CONFIG_UDEV)
.allowEmptyInput = TRUE,
.autoAddDevices = TRUE,
.autoEnableDevices = TRUE

View File

@ -692,9 +692,9 @@ NewInputDeviceRequest (InputOption *options, InputAttributes *attrs,
}
}
/* Right now, the only automatic config we know of is HAL. */
if (strcmp(option->key, "_source") == 0 &&
strcmp(option->value, "server/hal") == 0) {
(strcmp(option->value, "server/hal") == 0 ||
strcmp(option->value, "server/udev") == 0)) {
is_auto = 1;
if (!xf86Info.autoAddDevices) {
rval = BadMatch;

View File

@ -399,6 +399,9 @@
/* Support D-Bus */
#undef HAVE_DBUS
/* Use libudev for input hotplug */
#undef CONFIG_UDEV
/* Use D-Bus for input hotplug */
#undef CONFIG_NEED_DBUS