From 3347841c719ff29bfd719a732141f6b9a5410879 Mon Sep 17 00:00:00 2001 From: Simon McVittie <smcv@collabora.com> Date: Wed, 28 Oct 2020 18:55:01 +0000 Subject: [PATCH] input-device: Add interface flags The name is to leave space for "type flags" (joystick, etc.) later. Signed-off-by: Simon McVittie <smcv@collabora.com> --- bin/input-monitor.c | 41 ++++++++ steam-runtime-tools/direct-input-device.c | 76 +++++++++++++++ steam-runtime-tools/input-device-internal.h | 12 +++ steam-runtime-tools/input-device.c | 102 ++++++++++++++++++++ steam-runtime-tools/input-device.h | 24 +++++ steam-runtime-tools/udev-input-device.c | 36 +++++++ tests/input-device.c | 10 ++ tests/mock-input-device.c | 12 +++ tests/mock-input-device.h | 2 + 9 files changed, 315 insertions(+) diff --git a/bin/input-monitor.c b/bin/input-monitor.c index 49f092b68..036b1b13a 100644 --- a/bin/input-monitor.c +++ b/bin/input-monitor.c @@ -45,6 +45,41 @@ static gboolean opt_one_line = FALSE; static gboolean opt_seq = FALSE; static gboolean opt_verbose = FALSE; +static void +jsonify_flags (JsonBuilder *builder, + GType flags_type, + unsigned int values) +{ + GFlagsClass *class; + GFlagsValue *flags_value; + + g_return_if_fail (G_TYPE_IS_FLAGS (flags_type)); + + class = g_type_class_ref (flags_type); + + while (values != 0) + { + flags_value = g_flags_get_first_value (class, values); + + if (flags_value == NULL) + break; + + json_builder_add_string_value (builder, flags_value->value_nick); + values &= ~flags_value->value; + } + + if (values) + { + gchar *rest = g_strdup_printf ("0x%x", values); + + json_builder_add_string_value (builder, rest); + + g_free (rest); + } + + g_type_class_unref (class); +} + static void print_json (JsonBuilder *builder) { @@ -124,6 +159,12 @@ added (SrtInputDeviceMonitor *monitor, g_auto(GStrv) udev_properties = NULL; const char *sys_path; + json_builder_set_member_name (builder, "interface_flags"); + json_builder_begin_array (builder); + jsonify_flags (builder, SRT_TYPE_INPUT_DEVICE_INTERFACE_FLAGS, + srt_input_device_get_interface_flags (dev)); + json_builder_end_array (builder); + json_builder_set_member_name (builder, "dev_node"); json_builder_add_string_value (builder, srt_input_device_get_dev_node (dev)); diff --git a/steam-runtime-tools/direct-input-device.c b/steam-runtime-tools/direct-input-device.c index 7cd31f930..b182082c7 100644 --- a/steam-runtime-tools/direct-input-device.c +++ b/steam-runtime-tools/direct-input-device.c @@ -170,6 +170,23 @@ struct _SrtDirectInputDevice { gchar *sys_path; } usb_device_ancestor; + + struct + { + guint32 bus_type; + guint32 product_id; + guint32 vendor_id; + guint32 version; + } evdev; + + struct + { + guint32 bus_type; + guint32 product_id; + guint32 vendor_id; + } hid; + + SrtInputDeviceInterfaceFlags iface_flags; }; struct _SrtDirectInputDeviceClass @@ -241,6 +258,14 @@ _srt_direct_input_device_class_init (SrtDirectInputDeviceClass *cls) object_class->finalize = srt_direct_input_device_finalize; } +static SrtInputDeviceInterfaceFlags +srt_direct_input_device_get_interface_flags (SrtInputDevice *device) +{ + SrtDirectInputDevice *self = SRT_DIRECT_INPUT_DEVICE (device); + + return self->iface_flags; +} + static const char * srt_direct_input_device_get_dev_node (SrtInputDevice *device) { @@ -294,6 +319,7 @@ srt_direct_input_device_iface_init (SrtInputDeviceInterface *iface) { #define IMPLEMENT(x) iface->x = srt_direct_input_device_ ## x + IMPLEMENT (get_interface_flags); IMPLEMENT (get_dev_node); IMPLEMENT (get_sys_path); IMPLEMENT (get_subsystem); @@ -441,6 +467,9 @@ add_device (SrtDirectInputDeviceMonitor *self, return; } + if (subsystem == quark_hidraw) + device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID; + g_debug ("Trying to open %s", devnode); fd = open (devnode, O_RDONLY | O_NONBLOCK | ALWAYS_OPEN_FLAGS); @@ -449,6 +478,36 @@ add_device (SrtDirectInputDeviceMonitor *self, { /* The permissions are already OK for use */ g_debug ("Opened %s", devnode); + device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE; + + if (_srt_get_identity_from_raw_hid (fd, + &device->hid.bus_type, + &device->hid.vendor_id, + &device->hid.product_id)) + { + g_debug ("%s is raw HID: bus type 0x%04x, vendor 0x%04x, product 0x%04x", + devnode, + device->hid.bus_type, + device->hid.vendor_id, + device->hid.product_id); + device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID; + } + + if (_srt_get_identity_from_evdev (fd, + &device->evdev.bus_type, + &device->evdev.vendor_id, + &device->evdev.product_id, + &device->evdev.version)) + { + g_debug ("%s is evdev: bus type 0x%04x, vendor 0x%04x, product 0x%04x, version 0x%04x", + devnode, + device->evdev.bus_type, + device->evdev.vendor_id, + device->evdev.product_id, + device->evdev.version); + device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT; + } + close (fd); } else @@ -459,6 +518,23 @@ add_device (SrtDirectInputDeviceMonitor *self, return; } + fd = open (devnode, O_RDWR | O_NONBLOCK | ALWAYS_OPEN_FLAGS); + + if (fd >= 0) + { + g_debug ("Opened %s rw", devnode); + device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE; + close (fd); + } + + if ((device->iface_flags & (SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT + | SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID)) == 0) + { + g_debug ("%s is neither evdev nor raw HID, ignoring", devnode); + g_object_unref (device); + return; + } + device->hid_ancestor.sys_path = get_ancestor_with_subsystem_devtype (device->sys_path, "hid", NULL, NULL); diff --git a/steam-runtime-tools/input-device-internal.h b/steam-runtime-tools/input-device-internal.h index 4b5589ff1..820f34fa2 100644 --- a/steam-runtime-tools/input-device-internal.h +++ b/steam-runtime-tools/input-device-internal.h @@ -32,6 +32,8 @@ struct _SrtInputDeviceInterface { GTypeInterface parent; + SrtInputDeviceInterfaceFlags (*get_interface_flags) (SrtInputDevice *device); + const char * (*get_dev_node) (SrtInputDevice *device); const char * (*get_sys_path) (SrtInputDevice *device); const char * (*get_subsystem) (SrtInputDevice *device); @@ -73,6 +75,16 @@ void _srt_input_device_monitor_emit_removed (SrtInputDeviceMonitor *monitor, SrtInputDevice *device); void _srt_input_device_monitor_emit_all_for_now (SrtInputDeviceMonitor *monitor); + +gboolean _srt_get_identity_from_evdev (int fd, + guint32 *bus_type, + guint32 *vendor, + guint32 *product, + guint32 *version); +gboolean _srt_get_identity_from_raw_hid (int fd, + guint32 *bus_type, + guint32 *vendor, + guint32 *product); gchar *_srt_input_device_uevent_field (const char *text, const char *key); gboolean _srt_input_device_uevent_field_equals (const char *text, diff --git a/steam-runtime-tools/input-device.c b/steam-runtime-tools/input-device.c index 3054cc2ff..753847648 100644 --- a/steam-runtime-tools/input-device.c +++ b/steam-runtime-tools/input-device.c @@ -26,6 +26,9 @@ #include "steam-runtime-tools/input-device.h" #include "steam-runtime-tools/input-device-internal.h" +#include <linux/hidraw.h> +#include <linux/input.h> + #include <libglnx.h> #include "steam-runtime-tools/enums.h" @@ -60,6 +63,30 @@ srt_input_device_default_get_null_strv (SrtInputDevice *device) return NULL; } +/** + * srt_input_device_get_interface_flags: + * @device: An object implementing #SrtInputDeviceInterface + * + * Return flags describing how the input device can be used. + * + * Returns: Flags describing the input device. + */ +SrtInputDeviceInterfaceFlags +srt_input_device_get_interface_flags (SrtInputDevice *device) +{ + SrtInputDeviceInterface *iface = SRT_INPUT_DEVICE_GET_INTERFACE (device); + + g_return_val_if_fail (iface != NULL, SRT_INPUT_DEVICE_INTERFACE_FLAGS_NONE); + + return iface->get_interface_flags (device); +} + +static SrtInputDeviceInterfaceFlags +srt_input_device_default_get_interface_flags (SrtInputDevice *device) +{ + return SRT_INPUT_DEVICE_INTERFACE_FLAGS_NONE; +} + /** * srt_input_device_get_dev_node: * @device: An object implementing #SrtInputDeviceInterface @@ -373,6 +400,7 @@ srt_input_device_default_init (SrtInputDeviceInterface *iface) #define IMPLEMENT2(x, y) iface->x = srt_input_device_default_ ## y #define IMPLEMENT(x) IMPLEMENT2 (x, x) + IMPLEMENT (get_interface_flags); IMPLEMENT2 (get_dev_node, get_null_string); IMPLEMENT2 (get_sys_path, get_null_string); IMPLEMENT2 (get_subsystem, get_null_string); @@ -759,6 +787,80 @@ _srt_input_device_monitor_emit_all_for_now (SrtInputDeviceMonitor *monitor) g_signal_emit (monitor, monitor_signal_all_for_now, 0); } +gboolean +_srt_get_identity_from_evdev (int fd, + guint32 *bus_type, + guint32 *vendor, + guint32 *product, + guint32 *version) +{ + struct input_id iid = {}; + + if (bus_type != NULL) + *bus_type = 0; + + if (vendor != NULL) + *vendor = 0; + + if (product != NULL) + *product = 0; + + if (ioctl (fd, EVIOCGID, &iid) < 0) + { + g_debug ("EVIOCGID: %s", g_strerror (errno)); + return FALSE; + } + + if (bus_type != NULL) + *bus_type = iid.bustype; + + if (vendor != NULL) + *vendor = iid.vendor; + + if (product != NULL) + *product = iid.product; + + if (version != NULL) + *version = iid.version; + + return TRUE; +} + +gboolean +_srt_get_identity_from_raw_hid (int fd, + guint32 *bus_type, + guint32 *vendor, + guint32 *product) +{ + struct hidraw_devinfo devinfo = {}; + + if (bus_type != NULL) + *bus_type = 0; + + if (vendor != NULL) + *vendor = 0; + + if (product != NULL) + *product = 0; + + if (ioctl (fd, HIDIOCGRAWINFO, &devinfo) < 0) + { + g_debug ("HIDIOCGRAWINFO: %s", g_strerror (errno)); + return FALSE; + } + + if (bus_type != NULL) + *bus_type = devinfo.bustype; + + if (vendor != NULL) + *vendor = devinfo.vendor; + + if (product != NULL) + *product = devinfo.product; + + return TRUE; +} + /* * Returns: The field @key from @text, or %NULL if not found. */ diff --git a/steam-runtime-tools/input-device.h b/steam-runtime-tools/input-device.h index 037e4c992..0f1b13493 100644 --- a/steam-runtime-tools/input-device.h +++ b/steam-runtime-tools/input-device.h @@ -33,6 +33,28 @@ #include "steam-runtime-tools/macros.h" +/** + * SrtInputDeviceInterfaceFlags: + * @SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT: evdev event device nodes, + * typically /dev/input/event* + * @SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID: Raw USB or Bluetooth HID + * device nodes, typically /dev/hidraw* + * @SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE: Only report device nodes that appear to be + * openable in read-only mode + * @SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE: Only report device nodes that appear to be + * openable in read/write mode + * + * Flags describing the interface offered by an input device. + */ +typedef enum +{ + SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT = (1 << 0), + SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID = (1 << 1), + SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE = (1 << 2), + SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE = (1 << 3), + SRT_INPUT_DEVICE_INTERFACE_FLAGS_NONE = 0 +} SrtInputDeviceInterfaceFlags; + #define SRT_TYPE_INPUT_DEVICE (srt_input_device_get_type ()) #define SRT_INPUT_DEVICE(x) (G_TYPE_CHECK_INSTANCE_CAST ((x), SRT_TYPE_INPUT_DEVICE, SrtInputDevice)) #define SRT_IS_INPUT_DEVICE(x) (G_TYPE_CHECK_INSTANCE_TYPE ((x), SRT_TYPE_INPUT_DEVICE)) @@ -44,6 +66,8 @@ typedef struct _SrtInputDeviceInterface SrtInputDeviceInterface; _SRT_PUBLIC GType srt_input_device_get_type (void); +_SRT_PUBLIC +SrtInputDeviceInterfaceFlags srt_input_device_get_interface_flags (SrtInputDevice *device); _SRT_PUBLIC const char *srt_input_device_get_dev_node (SrtInputDevice *device); _SRT_PUBLIC diff --git a/steam-runtime-tools/udev-input-device.c b/steam-runtime-tools/udev-input-device.c index ca3594cb8..04bb48244 100644 --- a/steam-runtime-tools/udev-input-device.c +++ b/steam-runtime-tools/udev-input-device.c @@ -36,6 +36,8 @@ #include "steam-runtime-tools/input-device-internal.h" #include "steam-runtime-tools/utils-internal.h" +#define ALWAYS_OPEN_FLAGS (O_CLOEXEC | O_NOCTTY) + 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); @@ -123,6 +125,8 @@ struct _SrtUdevInputDevice { struct udev_device *dev; /* borrowed from child dev */ } usb_device_ancestor; + + SrtInputDeviceInterfaceFlags iface_flags; }; struct _SrtUdevInputDeviceClass @@ -193,6 +197,14 @@ _srt_udev_input_device_class_init (SrtUdevInputDeviceClass *cls) object_class->finalize = srt_udev_input_device_finalize; } +static SrtInputDeviceInterfaceFlags +srt_udev_input_device_get_interface_flags (SrtInputDevice *device) +{ + SrtUdevInputDevice *self = SRT_UDEV_INPUT_DEVICE (device); + + return self->iface_flags; +} + static const char * srt_udev_input_device_get_dev_node (SrtInputDevice *device) { @@ -320,6 +332,7 @@ srt_udev_input_device_iface_init (SrtInputDeviceInterface *iface) { #define IMPLEMENT(x) iface->x = srt_udev_input_device_ ## x + IMPLEMENT (get_interface_flags); IMPLEMENT (get_dev_node); IMPLEMENT (get_sys_path); IMPLEMENT (get_subsystem); @@ -535,6 +548,7 @@ add_device (SrtUdevInputDeviceMonitor *self, gboolean is_evdev = FALSE; gboolean is_hidraw = FALSE; const char *slash; + int fd; syspath = symbols.udev_device_get_syspath (dev); devnode = symbols.udev_device_get_devnode (dev); @@ -587,6 +601,28 @@ add_device (SrtUdevInputDeviceMonitor *self, device = g_object_new (SRT_TYPE_UDEV_INPUT_DEVICE, NULL); device->dev = symbols.udev_device_ref (dev); + if (is_evdev) + device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT; + + if (is_hidraw) + device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID; + + fd = open (devnode, O_RDONLY | O_NONBLOCK | ALWAYS_OPEN_FLAGS); + + if (fd >= 0) + { + device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE; + close (fd); + } + + fd = open (devnode, O_RDWR | O_NONBLOCK | ALWAYS_OPEN_FLAGS); + + if (fd >= 0) + { + device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE; + close (fd); + } + device->hid_ancestor.dev = symbols.udev_device_get_parent_with_subsystem_devtype (device->dev, "hid", NULL); diff --git a/tests/input-device.c b/tests/input-device.c index af8493858..c1389154e 100644 --- a/tests/input-device.c +++ b/tests/input-device.c @@ -108,6 +108,8 @@ test_input_device_usb (Fixture *f, g_autofree gchar *input_uevent = NULL; g_autofree gchar *usb_uevent = NULL; + mock_device->iface_flags = (SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT + | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE); mock_device->dev_node = g_strdup ("/dev/input/event0"); mock_device->sys_path = g_strdup ("/sys/devices/mock/usb/hid/input/input0/event0"); mock_device->subsystem = g_strdup ("input"); @@ -128,6 +130,9 @@ test_input_device_usb (Fixture *f, /* TODO: Fill in somewhat realistic details for a USB-attached * Steam Controller */ + g_assert_cmpuint (srt_input_device_get_interface_flags (device), ==, + SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT + | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE); g_assert_cmpstr (srt_input_device_get_dev_node (device), ==, "/dev/input/event0"); g_assert_cmpstr (srt_input_device_get_sys_path (device), ==, @@ -196,6 +201,11 @@ device_added_cb (SrtInputDeviceMonitor *monitor, g_autofree gchar *usb_uevent = NULL; g_auto(GStrv) udev_properties = NULL; + g_assert_cmpuint (srt_input_device_get_interface_flags (device), ==, + SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT + | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE + | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE); + uevent = srt_input_device_dup_uevent (device); g_assert_cmpstr (uevent, ==, "ONE=1\nTWO=2\n"); diff --git a/tests/mock-input-device.c b/tests/mock-input-device.c index 3a2bda45d..31e08abf1 100644 --- a/tests/mock-input-device.c +++ b/tests/mock-input-device.c @@ -86,6 +86,14 @@ mock_input_device_class_init (MockInputDeviceClass *cls) object_class->finalize = mock_input_device_finalize; } +static SrtInputDeviceInterfaceFlags +mock_input_device_get_interface_flags (SrtInputDevice *device) +{ + MockInputDevice *self = MOCK_INPUT_DEVICE (device); + + return self->iface_flags; +} + static const char * mock_input_device_get_dev_node (SrtInputDevice *device) { @@ -179,6 +187,7 @@ mock_input_device_iface_init (SrtInputDeviceInterface *iface) { #define IMPLEMENT(x) iface->x = mock_input_device_ ## x + IMPLEMENT (get_interface_flags); IMPLEMENT (get_dev_node); IMPLEMENT (get_sys_path); IMPLEMENT (get_subsystem); @@ -341,6 +350,9 @@ add_steam_controller (MockInputDeviceMonitor *self, g_autoptr(MockInputDevice) device = mock_input_device_new (); g_autoptr(GPtrArray) arr = g_ptr_array_new_full (1, g_free); + device->iface_flags = (SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT + | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE + | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE); device->dev_node = g_strdup_printf ("/dev/input/event%s", tail); device->sys_path = g_strdup_printf ("/sys/devices/mock/usb/hid/input/input0/event%s", tail); diff --git a/tests/mock-input-device.h b/tests/mock-input-device.h index b3255af37..11c3e32dd 100644 --- a/tests/mock-input-device.h +++ b/tests/mock-input-device.h @@ -72,6 +72,8 @@ struct _MockInputDevice gchar *sys_path; gchar *uevent; } usb_device_ancestor; + + SrtInputDeviceInterfaceFlags iface_flags; }; struct _MockInputDeviceClass -- GitLab