Skip to content
Snippets Groups Projects
Commit 31de3793 authored by Simon McVittie's avatar Simon McVittie
Browse files

input-device: Add a monitor based on dlopening libudev


This is the same approach used by SDL. It doesn't work well in most
containers.

To facilitate testing this, change sysroot/run-in-sysroot.py so that it
doesn't share /run with the host, and signals "we're in a container"
by creating /run/host.

Signed-off-by: default avatarSimon McVittie <smcv@collabora.com>
parent a08464d9
No related branches found
No related tags found
1 merge request!158Input device interface
......@@ -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);
......
......@@ -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.
......
......@@ -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,
......
......@@ -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);
}
......@@ -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
......
......@@ -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;
......
......@@ -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',
......
/*
* 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);
}
This diff is collapsed.
......@@ -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,
]
......
......@@ -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 ();
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment