diff --git a/bin/input-monitor.c b/bin/input-monitor.c index 0276aa0dd2687c38236c97ec85145d5f1b1a51d7..00089a6cf035db59cb46e6d3594337f1c6f3a701 100644 --- a/bin/input-monitor.c +++ b/bin/input-monitor.c @@ -159,6 +159,7 @@ interrupt_cb (void *user_data) return G_SOURCE_CONTINUE; } +static SrtInputDeviceMonitorFlags opt_mode = SRT_INPUT_DEVICE_MONITOR_FLAGS_NONE; static SrtInputDeviceMonitorFlags opt_flags = SRT_INPUT_DEVICE_MONITOR_FLAGS_NONE; static gboolean opt_evdev = FALSE; static gboolean opt_hidraw = FALSE; @@ -174,8 +175,30 @@ opt_once_cb (const gchar *option_name, return TRUE; } +static gboolean +opt_direct_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + opt_mode = SRT_INPUT_DEVICE_MONITOR_FLAGS_DIRECT; + return TRUE; +} + +static gboolean +opt_udev_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + opt_mode = SRT_INPUT_DEVICE_MONITOR_FLAGS_UDEV; + return TRUE; +} + static const GOptionEntry option_entries[] = { + { "direct", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_direct_cb, + "Find devices using /dev and /sys", NULL }, { "evdev", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_evdev, "List evdev event devices", NULL }, { "hidraw", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_hidraw, @@ -188,6 +211,8 @@ static const GOptionEntry option_entries[] = { "seq", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_seq, "Output application/json-seq [default: pretty-print as concatenated JSON]", NULL }, + { "udev", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_udev_cb, + "Find devices using udev", NULL }, { "version", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_print_version, "Print version number and exit", NULL }, { NULL } @@ -230,7 +255,7 @@ run (int argc, return FALSE; int_handler = g_unix_signal_add (SIGINT, interrupt_cb, &monitor); - monitor = srt_input_device_monitor_new (opt_flags); + monitor = srt_input_device_monitor_new (opt_mode | opt_flags); if (opt_evdev) srt_input_device_monitor_request_evdev (monitor); diff --git a/bin/input-monitor.md b/bin/input-monitor.md index a7ddca2bb95f25363adbab201fa9f558e1e92282..5066fd1c077a5561b19677486308f348a6aa5f8f 100644 --- a/bin/input-monitor.md +++ b/bin/input-monitor.md @@ -17,6 +17,11 @@ steam-runtime-input-monitor - list input devices # OPTIONS +**--direct** +: List input devices by reading `/dev` and `/sys`. + This currently only lists input devices for which at least read access + to the underlying device node is available. + **--evdev** : List all input event devices. @@ -37,6 +42,10 @@ steam-runtime-input-monitor - list input devices each event is printed as U+001E RECORD SEPARATOR, followed by a JSON object, followed by U+000A LINE FEED. +**--udev** +: List input devices by contacting udevd. This is usually not + expected to work inside a container. + **--version** : Print the version number as YAML and exit. @@ -56,6 +65,10 @@ Options to limit devices more selectively (for example, only evdev devices that have the **ID_INPUT_JOYSTICK** udev property) are likely to be added in future versions. +By default, an implementation is chosen automatically. The **--direct** +and **--udev** options override this, with the last one used +taking precedence. + Each event is a JSON object with a single key. Consumers should ignore unknown keys to allow for future expansion. diff --git a/debian/control b/debian/control index 4afc66f5889f02f79db1001cb0461b1e72cbd66c..9cda84983dceea1081e115b12bca603cb5178e84 100644 --- a/debian/control +++ b/debian/control @@ -14,6 +14,7 @@ Build-Depends: libglib2.0-dev, libjson-glib-dev (>= 1.0), libtheora0 <!nocheck>, + libudev1 <!nocheck> | libudev0 <!nocheck>, libva-dev, libvdpau-dev, libvulkan-dev, diff --git a/steam-runtime-tools/direct-input-device-internal.h b/steam-runtime-tools/direct-input-device-internal.h index 7f596f3ca27469662256bf1ae81de336d5adb9c5..310b6b9805ca62e90f3beb6ca94a1cfcfd06e5aa 100644 --- a/steam-runtime-tools/direct-input-device-internal.h +++ b/steam-runtime-tools/direct-input-device-internal.h @@ -57,3 +57,11 @@ GType _srt_direct_input_device_monitor_get_type (void); G_DEFINE_AUTOPTR_CLEANUP_FUNC (SrtDirectInputDevice, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (SrtDirectInputDeviceMonitor, g_object_unref) + +static inline SrtInputDeviceMonitor * +srt_direct_input_device_monitor_new (SrtInputDeviceMonitorFlags flags) +{ + return g_object_new (SRT_TYPE_DIRECT_INPUT_DEVICE_MONITOR, + "flags", flags, + NULL); +} diff --git a/steam-runtime-tools/input-device.c b/steam-runtime-tools/input-device.c index 9ffffc49830af9fb1916b51041078ae0b0703551..525265f4f9737fe669b1f720dded6d37060147d5 100644 --- a/steam-runtime-tools/input-device.c +++ b/steam-runtime-tools/input-device.c @@ -31,6 +31,7 @@ #include "steam-runtime-tools/enums.h" #include "steam-runtime-tools/direct-input-device-internal.h" +#include "steam-runtime-tools/udev-input-device-internal.h" /** * SrtInputDevice: @@ -174,6 +175,33 @@ static guint monitor_signal_added = 0; static guint monitor_signal_removed = 0; static guint monitor_signal_all_for_now = 0; +static const SrtInputDeviceMonitorFlags mode_flags = (SRT_INPUT_DEVICE_MONITOR_FLAGS_UDEV + | SRT_INPUT_DEVICE_MONITOR_FLAGS_DIRECT); + +/* + * @monitor_out: (out) (not optional): + * @error: (inout) (not optional): + */ +static gboolean +try_udev (SrtInputDeviceMonitorFlags flags, + SrtInputDeviceMonitor **monitor_out, + GError **error) +{ + /* Don't try it again if we already failed, to avoid another warning */ + if (*error != NULL) + return FALSE; + + *monitor_out = srt_udev_input_device_monitor_new (flags, error); + + if (*monitor_out != NULL) + return TRUE; + + /* We usually expect this to succeed, so log a warning if it fails */ + g_warning ("Unable to initialize udev input device monitor: %s", + (*error)->message); + return FALSE; +} + /** * srt_input_device_monitor_new: * @flags: Flags affecting the behaviour of the input device monitor. @@ -189,10 +217,35 @@ static guint monitor_signal_all_for_now = 0; SrtInputDeviceMonitor * srt_input_device_monitor_new (SrtInputDeviceMonitorFlags flags) { - /* For now we only have one implementation. */ - return g_object_new (SRT_TYPE_DIRECT_INPUT_DEVICE_MONITOR, - "flags", flags, - NULL); + SrtInputDeviceMonitor *udev = NULL; + g_autoptr(GError) udev_error = NULL; + + if (__builtin_popcount (flags & mode_flags) > 1) + g_warning ("Requesting more than one of UDEV and DIRECT " + "monitoring has undefined results: 0x%x", flags); + + /* First see whether the caller expressed a preference */ + + if (flags & SRT_INPUT_DEVICE_MONITOR_FLAGS_UDEV) + { + if (try_udev (flags, &udev, &udev_error)) + return udev; + } + + if (flags & SRT_INPUT_DEVICE_MONITOR_FLAGS_DIRECT) + return srt_direct_input_device_monitor_new (flags); + + /* Prefer a direct monitor if we're in a container */ + if (g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS) + || g_file_test ("/run/pressure-vessel", G_FILE_TEST_EXISTS) + || g_file_test ("/run/host", G_FILE_TEST_EXISTS)) + return srt_direct_input_device_monitor_new (flags); + + if (try_udev (flags, &udev, &udev_error)) + return udev; + + /* Fall back to direct monitoring if we don't have libudev */ + return srt_direct_input_device_monitor_new (flags); } static void diff --git a/steam-runtime-tools/input-device.h b/steam-runtime-tools/input-device.h index 3b8df8a91619ba47ecd29d1a381b6bc546db7d39..504522e6d2419164a135f6cefe21b5916c03a90b 100644 --- a/steam-runtime-tools/input-device.h +++ b/steam-runtime-tools/input-device.h @@ -55,6 +55,9 @@ const char *srt_input_device_get_subsystem (SrtInputDevice *device); * SrtInputDeviceMonitorFlags: * @SRT_INPUT_DEVICE_MONITOR_FLAGS_ONCE: Enumerate the devices that were * available when monitoring starts, and then stop monitoring. + * @SRT_INPUT_DEVICE_MONITOR_FLAGS_UDEV: Prefer to get devices from udev. + * @SRT_INPUT_DEVICE_MONITOR_FLAGS_DIRECT: Prefer to get devices by + * monitoring /dev, /sys directly. * @SRT_INPUT_DEVICE_MONITOR_FLAGS_NONE: No special behaviour. * * Flags affecting the behaviour of the input device monitor. @@ -62,6 +65,8 @@ const char *srt_input_device_get_subsystem (SrtInputDevice *device); typedef enum { SRT_INPUT_DEVICE_MONITOR_FLAGS_ONCE = (1 << 0), + SRT_INPUT_DEVICE_MONITOR_FLAGS_UDEV = (1 << 1), + SRT_INPUT_DEVICE_MONITOR_FLAGS_DIRECT = (1 << 2), SRT_INPUT_DEVICE_MONITOR_FLAGS_NONE = 0 } SrtInputDeviceMonitorFlags; diff --git a/steam-runtime-tools/meson.build b/steam-runtime-tools/meson.build index bf878fb09c5861637aae9004a41339a662d48772..c9c3edd5d4b424b82c226263c095ba5986b19026 100644 --- a/steam-runtime-tools/meson.build +++ b/steam-runtime-tools/meson.build @@ -53,6 +53,8 @@ libsteamrt_sources = [ 'steam-internal.h', 'steam.c', 'system-info.c', + 'udev-input-device-internal.h', + 'udev-input-device.c', 'utils-internal.h', 'utils.c', 'xdg-portal.c', diff --git a/steam-runtime-tools/udev-input-device-internal.h b/steam-runtime-tools/udev-input-device-internal.h new file mode 100644 index 0000000000000000000000000000000000000000..9ee0d36c83568935f1c0a62620eaa30eafb3a186 --- /dev/null +++ b/steam-runtime-tools/udev-input-device-internal.h @@ -0,0 +1,68 @@ +/* + * libudev-based input device monitor, loosely based on SDL code. + * + * Copyright © 1997-2020 Sam Lantinga <slouken@libsdl.org> + * Copyright © 2020 Collabora Ltd. + * + * SPDX-License-Identifier: Zlib + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#pragma once + +#include "steam-runtime-tools/glib-backports-internal.h" +#include "steam-runtime-tools/input-device.h" + +typedef struct _SrtUdevInputDevice SrtUdevInputDevice; +typedef struct _SrtUdevInputDeviceClass SrtUdevInputDeviceClass; + +#define SRT_TYPE_UDEV_INPUT_DEVICE (_srt_udev_input_device_get_type ()) +#define SRT_UDEV_INPUT_DEVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SRT_TYPE_UDEV_INPUT_DEVICE, SrtUdevInputDevice)) +#define SRT_IS_UDEV_INPUT_DEVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SRT_TYPE_UDEV_INPUT_DEVICE)) +#define SRT_UDEV_INPUT_DEVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SRT_TYPE_UDEV_INPUT_DEVICE, SrtUdevInputDeviceClass)) +#define SRT_UDEV_INPUT_DEVICE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), SRT_TYPE_UDEV_INPUT_DEVICE, SrtUdevInputDeviceClass)) +#define SRT_IS_UDEV_INPUT_DEVICE_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), SRT_TYPE_UDEV_INPUT_DEVICE)) + +G_GNUC_INTERNAL +GType _srt_udev_input_device_get_type (void); + +#define SRT_TYPE_UDEV_INPUT_DEVICE_MONITOR (_srt_udev_input_device_monitor_get_type ()) +#define SRT_UDEV_INPUT_DEVICE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SRT_TYPE_UDEV_INPUT_DEVICE_MONITOR, SrtUdevInputDeviceMonitor)) +#define SRT_IS_UDEV_INPUT_DEVICE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SRT_TYPE_UDEV_INPUT_DEVICE_MONITOR)) +#define SRT_UDEV_INPUT_DEVICE_MONITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SRT_TYPE_UDEV_INPUT_DEVICE_MONITOR, SrtUdevInputDeviceMonitorClass)) +#define SRT_UDEV_INPUT_DEVICE_MONITOR_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), SRT_TYPE_UDEV_INPUT_DEVICE_MONITOR, SrtUdevInputDeviceMonitorClass)) +#define SRT_IS_UDEV_INPUT_DEVICE_MONITOR_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), SRT_TYPE_UDEV_INPUT_DEVICE_MONITOR)) + +typedef struct _SrtUdevInputDeviceMonitor SrtUdevInputDeviceMonitor; +typedef struct _SrtUdevInputDeviceMonitorClass SrtUdevInputDeviceMonitorClass; + +G_GNUC_INTERNAL +GType _srt_udev_input_device_monitor_get_type (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SrtUdevInputDevice, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SrtUdevInputDeviceMonitor, g_object_unref) + +static inline SrtInputDeviceMonitor * +srt_udev_input_device_monitor_new (SrtInputDeviceMonitorFlags flags, + GError **error) +{ + return g_initable_new (SRT_TYPE_UDEV_INPUT_DEVICE_MONITOR, + NULL /* cancellable */, error, + "flags", flags, + NULL); +} diff --git a/steam-runtime-tools/udev-input-device.c b/steam-runtime-tools/udev-input-device.c new file mode 100644 index 0000000000000000000000000000000000000000..60701c92bd6607ee822dfbb0e161adbcec8c45a9 --- /dev/null +++ b/steam-runtime-tools/udev-input-device.c @@ -0,0 +1,671 @@ +/* + * libudev-based input device monitor, loosely based on SDL code. + * + * Copyright © 1997-2020 Sam Lantinga <slouken@libsdl.org> + * Copyright © 2020 Collabora Ltd. + * + * SPDX-License-Identifier: Zlib + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "steam-runtime-tools/udev-input-device-internal.h" + +#include <dlfcn.h> +#include <linux/input.h> + +#include <glib-unix.h> +#include <libglnx.h> + +#include "steam-runtime-tools/glib-backports-internal.h" +#include "steam-runtime-tools/input-device.h" +#include "steam-runtime-tools/input-device-internal.h" +#include "steam-runtime-tools/utils-internal.h" + +static void srt_udev_input_device_iface_init (SrtInputDeviceInterface *iface); +static void srt_udev_input_device_monitor_iface_init (SrtInputDeviceMonitorInterface *iface); +static void srt_udev_input_device_monitor_initable_iface_init (GInitableIface *iface); + +struct udev; +struct udev_list_entry; +struct udev_device; +struct udev_monitor; +struct udev_enumerate; + +static struct +{ + void *handle; + + struct udev *(*udev_new) (void); + struct udev *(*udev_unref) (struct udev *); + + const char *(*udev_list_entry_get_name) (struct udev_list_entry *); + struct udev_list_entry *(*udev_list_entry_get_next) (struct udev_list_entry *); + + struct udev_device *(*udev_device_new_from_syspath) (struct udev *, + const char *); + const char *(*udev_device_get_action) (struct udev_device *); + const char *(*udev_device_get_devnode) (struct udev_device *); + const char *(*udev_device_get_subsystem) (struct udev_device *); + const char *(*udev_device_get_syspath) (struct udev_device *); + struct udev_device *(*udev_device_ref) (struct udev_device *); + struct udev_device *(*udev_device_unref) (struct udev_device *); + + struct udev_monitor *(*udev_monitor_new_from_netlink) (struct udev *, + const char *); + int (*udev_monitor_filter_add_match_subsystem_devtype) (struct udev_monitor *, + const char *, + const char *); + int (*udev_monitor_enable_receiving) (struct udev_monitor *); + struct udev_device *(*udev_monitor_receive_device) (struct udev_monitor *); + int (*udev_monitor_get_fd) (struct udev_monitor *); + struct udev_monitor *(*udev_monitor_unref) (struct udev_monitor *); + + struct udev_enumerate *(*udev_enumerate_new) (struct udev *); + int (*udev_enumerate_add_match_subsystem) (struct udev_enumerate *, + const char *); + int (*udev_enumerate_scan_devices) (struct udev_enumerate *); + struct udev_list_entry *(*udev_enumerate_get_list_entry) (struct udev_enumerate *); + struct udev_enumerate *(*udev_enumerate_unref) (struct udev_enumerate *); +} symbols; + +struct _SrtUdevInputDevice +{ + GObject parent; + + struct udev_device *dev; +}; + +struct _SrtUdevInputDeviceClass +{ + GObjectClass parent; +}; + +struct _SrtUdevInputDeviceMonitor +{ + GObject parent; + + struct udev *context; + struct udev_monitor *monitor; + GHashTable *devices; + GMainContext *monitor_context; + GSource *monitor_source; + + gboolean want_evdev; + gboolean want_hidraw; + SrtInputDeviceMonitorFlags flags; + enum + { + NOT_STARTED = 0, + STARTED, + STOPPED + } state; +}; + +struct _SrtUdevInputDeviceMonitorClass +{ + GObjectClass parent; +}; + +G_DEFINE_TYPE_WITH_CODE (SrtUdevInputDevice, + _srt_udev_input_device, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (SRT_TYPE_INPUT_DEVICE, + srt_udev_input_device_iface_init)) + +G_DEFINE_TYPE_WITH_CODE (SrtUdevInputDeviceMonitor, + _srt_udev_input_device_monitor, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + srt_udev_input_device_monitor_initable_iface_init) + G_IMPLEMENT_INTERFACE (SRT_TYPE_INPUT_DEVICE_MONITOR, + srt_udev_input_device_monitor_iface_init)) + +static void +_srt_udev_input_device_init (SrtUdevInputDevice *self) +{ +} + +static void +srt_udev_input_device_finalize (GObject *object) +{ + SrtUdevInputDevice *self = SRT_UDEV_INPUT_DEVICE (object); + + g_clear_pointer (&self->dev, symbols.udev_device_unref); + + G_OBJECT_CLASS (_srt_udev_input_device_parent_class)->finalize (object); +} + +static void +_srt_udev_input_device_class_init (SrtUdevInputDeviceClass *cls) +{ + GObjectClass *object_class = G_OBJECT_CLASS (cls); + + object_class->finalize = srt_udev_input_device_finalize; +} + +static const char * +srt_udev_input_device_get_dev_node (SrtInputDevice *device) +{ + SrtUdevInputDevice *self = SRT_UDEV_INPUT_DEVICE (device); + + return symbols.udev_device_get_devnode (self->dev); +} + +static const char * +srt_udev_input_device_get_subsystem (SrtInputDevice *device) +{ + SrtUdevInputDevice *self = SRT_UDEV_INPUT_DEVICE (device); + + return symbols.udev_device_get_subsystem (self->dev); +} + +static const char * +srt_udev_input_device_get_sys_path (SrtInputDevice *device) +{ + SrtUdevInputDevice *self = SRT_UDEV_INPUT_DEVICE (device); + + return symbols.udev_device_get_syspath (self->dev); +} + +static void +srt_udev_input_device_iface_init (SrtInputDeviceInterface *iface) +{ +#define IMPLEMENT(x) iface->x = srt_udev_input_device_ ## x + + IMPLEMENT (get_dev_node); + IMPLEMENT (get_sys_path); + IMPLEMENT (get_subsystem); + +#undef IMPLEMENT +} + +typedef enum +{ + PROP_0, + PROP_FLAGS, + PROP_IS_ACTIVE, + N_PROPERTIES +} Property; + +static void +_srt_udev_input_device_monitor_init (SrtUdevInputDeviceMonitor *self) +{ + self->monitor_context = g_main_context_ref_thread_default (); + self->state = NOT_STARTED; + self->devices = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + g_object_unref); +} + +static void +srt_udev_input_device_monitor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SrtUdevInputDeviceMonitor *self = SRT_UDEV_INPUT_DEVICE_MONITOR (object); + + switch ((Property) prop_id) + { + case PROP_FLAGS: + g_value_set_flags (value, self->flags); + break; + + case PROP_IS_ACTIVE: + g_value_set_boolean (value, (self->state == STARTED)); + break; + + case PROP_0: + case N_PROPERTIES: + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +srt_udev_input_device_monitor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SrtUdevInputDeviceMonitor *self = SRT_UDEV_INPUT_DEVICE_MONITOR (object); + + switch ((Property) prop_id) + { + case PROP_FLAGS: + /* Construct-only */ + g_return_if_fail (self->flags == 0); + self->flags = g_value_get_flags (value); + break; + + case PROP_IS_ACTIVE: + case PROP_0: + case N_PROPERTIES: + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +srt_udev_input_device_monitor_dispose (GObject *object) +{ + srt_input_device_monitor_stop (SRT_INPUT_DEVICE_MONITOR (object)); + G_OBJECT_CLASS (_srt_udev_input_device_monitor_parent_class)->dispose (object); +} + +static void +srt_udev_input_device_monitor_finalize (GObject *object) +{ + srt_input_device_monitor_stop (SRT_INPUT_DEVICE_MONITOR (object)); + G_OBJECT_CLASS (_srt_udev_input_device_monitor_parent_class)->finalize (object); +} + +static void +_srt_udev_input_device_monitor_class_init (SrtUdevInputDeviceMonitorClass *cls) +{ + GObjectClass *object_class = G_OBJECT_CLASS (cls); + + object_class->get_property = srt_udev_input_device_monitor_get_property; + object_class->set_property = srt_udev_input_device_monitor_set_property; + object_class->dispose = srt_udev_input_device_monitor_dispose; + object_class->finalize = srt_udev_input_device_monitor_finalize; + + g_object_class_override_property (object_class, PROP_FLAGS, "flags"); + g_object_class_override_property (object_class, PROP_IS_ACTIVE, "is-active"); +} + +static gboolean +srt_udev_input_device_monitor_initable_init (GInitable *initable, + GCancellable *cancellable G_GNUC_UNUSED, + GError **error) +{ + static const char * const libs[] = { "libudev.so.1", "libudev.so.0" }; + void *handle = NULL; + const char *lib = NULL; + const char *which = NULL; + gsize i; + + /* Only initialize once per process */ + if (symbols.handle != NULL) + return TRUE; + + for (i = 0; i < G_N_ELEMENTS (libs); i++) + { + lib = libs[i]; + handle = dlopen (lib, RTLD_NOW|RTLD_LOCAL|RTLD_NODELETE); + + if (handle != NULL) + break; + } + + if (handle == NULL) + { + /* Try the first library again, for the side-effect of getting the + * error message */ + lib = libs[0]; + handle = dlopen (lib, RTLD_NOW|RTLD_LOCAL|RTLD_NODELETE); + } + + if (handle == NULL) + return glnx_throw (error, "Unable to load %s: %s", lib, dlerror ()); + +#define SYMBOL(name) \ + do \ + { \ + void *symbol; \ + \ + which = #name; \ + symbol = dlsym (handle, which); \ + \ + if (symbol != NULL) \ + symbols.name = (__typeof__ (symbols.name)) G_CALLBACK (symbol); \ + else \ + goto fail; \ + } \ + while (0) + + SYMBOL (udev_new); + SYMBOL (udev_unref); + + SYMBOL (udev_list_entry_get_name); + SYMBOL (udev_list_entry_get_next); + + SYMBOL (udev_device_new_from_syspath); + SYMBOL (udev_device_get_action); + SYMBOL (udev_device_get_devnode); + SYMBOL (udev_device_get_subsystem); + SYMBOL (udev_device_get_syspath); + SYMBOL (udev_device_ref); + SYMBOL (udev_device_unref); + + SYMBOL (udev_monitor_new_from_netlink); + SYMBOL (udev_monitor_filter_add_match_subsystem_devtype); + SYMBOL (udev_monitor_enable_receiving); + SYMBOL (udev_monitor_receive_device); + SYMBOL (udev_monitor_get_fd); + SYMBOL (udev_monitor_unref); + + SYMBOL (udev_enumerate_new); + SYMBOL (udev_enumerate_add_match_subsystem); + SYMBOL (udev_enumerate_scan_devices); + SYMBOL (udev_enumerate_get_list_entry); + SYMBOL (udev_enumerate_unref); + +#undef SYMBOL + + symbols.handle = handle; + return TRUE; + +fail: + return glnx_throw (error, "Unable to find symbol %s in %s: %s", + which, lib, dlerror ()); +} + +static void +add_device (SrtUdevInputDeviceMonitor *self, + struct udev_device *dev) +{ + SrtUdevInputDevice *device = NULL; + const char *syspath; + const char *devnode; + gboolean want = FALSE; + gboolean is_evdev = FALSE; + gboolean is_hidraw = FALSE; + const char *slash; + + syspath = symbols.udev_device_get_syspath (dev); + devnode = symbols.udev_device_get_devnode (dev); + + if (G_UNLIKELY (syspath == NULL)) + { + g_warning ("not adding udev_device with NULL syspath"); + return; + } + + /* If we added a device at this /sys path already, don't add it again. + * (This is for the "change" action, and the rarer "bind" action.) */ + if (g_hash_table_lookup_extended (self->devices, syspath, NULL, NULL)) + { + g_debug ("ignoring device \"%s\" which we already have", syspath); + return; + } + + if (devnode == NULL) + { + /* We only care about the devices that could, in principle, be + * opened */ + g_debug ("ignoring device \"%s\" with NULL device node", syspath); + return; + } + + slash = strrchr (syspath, '/'); + + if (slash != NULL) + { + if (self->want_hidraw && + g_str_has_prefix (slash + 1, "hidraw") && + _srt_str_is_integer (slash + 1 + strlen ("hidraw"))) + want = is_hidraw = TRUE; + + if (self->want_evdev && + g_str_has_prefix (slash + 1, "event") && + _srt_str_is_integer (slash + 1 + strlen ("event"))) + want = is_evdev = TRUE; + } + + if (!want) + { + g_debug ("ignoring uninteresting udev_device \"%s\"", syspath); + return; + } + + g_debug ("Adding device %s", syspath); + + device = g_object_new (SRT_TYPE_UDEV_INPUT_DEVICE, NULL); + device->dev = symbols.udev_device_ref (dev); + + g_hash_table_replace (self->devices, (char *) syspath, device); + _srt_input_device_monitor_emit_added (SRT_INPUT_DEVICE_MONITOR (self), + SRT_INPUT_DEVICE (device)); +} + +static void +remove_device (SrtUdevInputDeviceMonitor *self, + struct udev_device *dev) +{ + void *device = NULL; + const char *syspath; + + syspath = symbols.udev_device_get_syspath (dev); + + if (G_UNLIKELY (syspath == NULL)) + { + g_warning ("trying to remove udev_device with NULL syspath"); + symbols.udev_device_unref (dev); + return; + } + + g_debug ("Removing device %s", syspath); + + if (g_hash_table_lookup_extended (self->devices, syspath, NULL, &device)) + { + g_hash_table_steal (self->devices, syspath); + _srt_input_device_monitor_emit_removed (SRT_INPUT_DEVICE_MONITOR (self), + device); + g_object_unref (device); + } +} + +static gboolean +srt_udev_input_device_monitor_cb (int fd, + GIOCondition condition, + void *user_data) +{ + SrtUdevInputDeviceMonitor *self = SRT_UDEV_INPUT_DEVICE_MONITOR (user_data); + struct udev_device *dev; + const char *action; + + g_return_val_if_fail (SRT_IS_UDEV_INPUT_DEVICE_MONITOR (self), G_SOURCE_REMOVE); + + dev = symbols.udev_monitor_receive_device (self->monitor); + + if (dev == NULL) + return G_SOURCE_CONTINUE; + + action = symbols.udev_device_get_action (dev); + + if (g_strcmp0 (action, "remove") == 0) + remove_device (self, dev); + else + add_device (self, dev); + + g_clear_pointer (&dev, symbols.udev_device_unref); + return G_SOURCE_CONTINUE; +} + +static void +srt_udev_input_device_monitor_request_raw_hid (SrtInputDeviceMonitor *monitor) +{ + SrtUdevInputDeviceMonitor *self = SRT_UDEV_INPUT_DEVICE_MONITOR (monitor); + + g_return_if_fail (SRT_IS_UDEV_INPUT_DEVICE_MONITOR (monitor)); + g_return_if_fail (self->state == NOT_STARTED); + + self->want_hidraw = TRUE; +} + +static void +srt_udev_input_device_monitor_request_evdev (SrtInputDeviceMonitor *monitor) +{ + SrtUdevInputDeviceMonitor *self = SRT_UDEV_INPUT_DEVICE_MONITOR (monitor); + + g_return_if_fail (SRT_IS_UDEV_INPUT_DEVICE_MONITOR (monitor)); + g_return_if_fail (self->state == NOT_STARTED); + + self->want_evdev = TRUE; +} + +static gboolean +enumerate_cb (gpointer user_data) +{ + SrtUdevInputDeviceMonitor *self = SRT_UDEV_INPUT_DEVICE_MONITOR (user_data); + + if (self->want_hidraw || self->want_evdev) + { + struct udev_enumerate *enumerator = NULL; + struct udev_list_entry *devs; + struct udev_list_entry *item; + + enumerator = symbols.udev_enumerate_new (self->context); + + if (enumerator == NULL) + { + g_warning ("udev_enumerate_new: %s", g_strerror (errno)); + goto out; + } + + if (self->want_evdev) + symbols.udev_enumerate_add_match_subsystem (enumerator, "input"); + + if (self->want_hidraw) + symbols.udev_enumerate_add_match_subsystem (enumerator, "hidraw"); + + symbols.udev_enumerate_scan_devices (enumerator); + devs = symbols.udev_enumerate_get_list_entry (enumerator); + + for (item = devs; item != NULL; item = symbols.udev_list_entry_get_next (item)) + { + const char *syspath = symbols.udev_list_entry_get_name (item); + struct udev_device *dev; + + dev = symbols.udev_device_new_from_syspath (self->context, syspath); + + if (dev != NULL) + add_device (self, dev); + else + g_warning ("udev_device_new_from_syspath \"%s\": %s", + syspath, g_strerror (errno)); + + symbols.udev_device_unref (dev); + } + + symbols.udev_enumerate_unref (enumerator); + } + +out: + _srt_input_device_monitor_emit_all_for_now (SRT_INPUT_DEVICE_MONITOR (self)); + + return G_SOURCE_REMOVE; +} + +static gboolean +srt_udev_input_device_monitor_start (SrtInputDeviceMonitor *monitor, + GError **error) +{ + SrtUdevInputDeviceMonitor *self = SRT_UDEV_INPUT_DEVICE_MONITOR (monitor); + + g_return_val_if_fail (SRT_IS_UDEV_INPUT_DEVICE_MONITOR (monitor), FALSE); + g_return_val_if_fail (self->state == NOT_STARTED, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + /* If this assertion fails, g_initable_init() failed or wasn't called */ + g_return_val_if_fail (symbols.handle != NULL, FALSE); + + self->state = STARTED; + + self->context = symbols.udev_new (); + + if (self->context == NULL) + return glnx_throw_errno_prefix (error, "udev_new"); + + if ((self->flags & SRT_INPUT_DEVICE_MONITOR_FLAGS_ONCE) == 0 + && (self->want_hidraw || self->want_evdev)) + { + int fd; + + self->monitor = symbols.udev_monitor_new_from_netlink (self->context, "udev"); + + if (self->monitor == NULL) + return glnx_throw_errno_prefix (error, "udev_monitor_new_from_netlink"); + + if (self->want_evdev) + symbols.udev_monitor_filter_add_match_subsystem_devtype (self->monitor, "input", NULL); + + if (self->want_hidraw) + symbols.udev_monitor_filter_add_match_subsystem_devtype (self->monitor, "hidraw", NULL); + + symbols.udev_monitor_enable_receiving (self->monitor); + + fd = symbols.udev_monitor_get_fd (self->monitor); + + if (fd < 0) + { + errno = -fd; + return glnx_throw_errno_prefix (error, "udev_monitor_get_fd"); + } + + self->monitor_source = g_unix_fd_source_new (fd, G_IO_IN); + g_source_set_callback (self->monitor_source, + (GSourceFunc) G_CALLBACK (srt_udev_input_device_monitor_cb), + self, NULL); + g_source_set_priority (self->monitor_source, G_PRIORITY_DEFAULT); + g_source_attach (self->monitor_source, self->monitor_context); + } + + /* Make sure the signals for the initial batch of devices are emitted in + * the correct main-context */ + g_main_context_invoke_full (self->monitor_context, G_PRIORITY_DEFAULT, + enumerate_cb, g_object_ref (self), + g_object_unref); + + return TRUE; +} + +static void +srt_udev_input_device_monitor_stop (SrtInputDeviceMonitor *monitor) +{ + SrtUdevInputDeviceMonitor *self = SRT_UDEV_INPUT_DEVICE_MONITOR (monitor); + + self->state = STOPPED; + + if (self->monitor_source != NULL) + g_source_destroy (self->monitor_source); + + g_clear_pointer (&self->monitor_source, g_source_unref); + g_clear_pointer (&self->monitor_context, g_main_context_unref); + g_clear_pointer (&self->monitor, symbols.udev_monitor_unref); + g_clear_pointer (&self->context, symbols.udev_unref); + g_clear_pointer (&self->devices, g_hash_table_unref); +} + +static void +srt_udev_input_device_monitor_iface_init (SrtInputDeviceMonitorInterface *iface) +{ +#define IMPLEMENT(x) iface->x = srt_udev_input_device_monitor_ ## x + IMPLEMENT (request_raw_hid); + IMPLEMENT (request_evdev); + IMPLEMENT (start); + IMPLEMENT (stop); +#undef IMPLEMENT +} + +static void +srt_udev_input_device_monitor_initable_iface_init (GInitableIface *iface) +{ + iface->init = srt_udev_input_device_monitor_initable_init; +} diff --git a/sysroot/run-in-sysroot.py b/sysroot/run-in-sysroot.py index b22b927c63e6f3b4f94a1eebaf56e5edaea4aaa2..73d07ceebda0e76b45a750b95ebb565447089ffe 100755 --- a/sysroot/run-in-sysroot.py +++ b/sysroot/run-in-sysroot.py @@ -110,6 +110,8 @@ def main(): '--tmpfs', '/tmp', '--tmpfs', '/var/tmp', '--tmpfs', '/home', + '--tmpfs', '/run', + '--tmpfs', '/run/host', '--bind', abs_srcdir, abs_srcdir, '--bind', abs_builddir, abs_builddir, ] diff --git a/tests/input-device.c b/tests/input-device.c index 04c2897c3d8f4f5d11e4761cd75b9df82aacafc8..93ca598510126cccf74b5dd7ccc6f8238e6b0489 100644 --- a/tests/input-device.c +++ b/tests/input-device.c @@ -39,7 +39,7 @@ typedef struct { - enum { MOCK, DIRECT } type; + enum { MOCK, DIRECT, UDEV } type; } Config; static const Config defconfig = @@ -52,6 +52,11 @@ static const Config direct_config = .type = DIRECT, }; +static const Config udev_config = +{ + .type = UDEV, +}; + typedef struct { const Config *config; @@ -212,6 +217,11 @@ input_device_monitor_new (Fixture *f, switch (f->config->type) { case DIRECT: + flags |= SRT_INPUT_DEVICE_MONITOR_FLAGS_DIRECT; + return srt_input_device_monitor_new (flags); + + case UDEV: + flags |= SRT_INPUT_DEVICE_MONITOR_FLAGS_UDEV; return srt_input_device_monitor_new (flags); case MOCK: @@ -420,6 +430,10 @@ main (int argc, setup, test_input_device_monitor, teardown); g_test_add ("/input-device/monitor-once/direct", Fixture, &direct_config, setup, test_input_device_monitor_once, teardown); + g_test_add ("/input-device/monitor/udev", Fixture, &udev_config, + setup, test_input_device_monitor, teardown); + g_test_add ("/input-device/monitor-once/udev", Fixture, &udev_config, + setup, test_input_device_monitor_once, teardown); return g_test_run (); }