diff --git a/bin/input-monitor.c b/bin/input-monitor.c index 7d9efd21c39d90d295dbc0e251cf31a95b91b72a..49f092b68072315d6ceb6401ee40fc7f95360834 100644 --- a/bin/input-monitor.c +++ b/bin/input-monitor.c @@ -71,6 +71,44 @@ print_json (JsonBuilder *builder) g_warning ("Unable to write final newline: %s", g_strerror (errno)); } +static void +add_uevent_member (JsonBuilder *builder, + const char *name, + const char *value) +{ + const char *start = value; + const char *end; + + /* value is conceptually a single string, but we + * present it as an array of lines here, because that's + * a lot easier to read! */ + json_builder_set_member_name (builder, name); + json_builder_begin_array (builder); + + while (*start != '\0') + { + g_autofree gchar *valid = NULL; + + while (*start == '\n') + start++; + + if (*start == '\0') + break; + + end = strchrnul (start, '\n'); + + valid = g_utf8_make_valid (start, end - start); + json_builder_add_string_value (builder, valid); + + if (*end == '\0') + break; + + start = end + 1; + } + + json_builder_end_array (builder); +} + static void added (SrtInputDeviceMonitor *monitor, SrtInputDevice *dev, @@ -84,6 +122,7 @@ added (SrtInputDeviceMonitor *monitor, json_builder_begin_object (builder); { g_auto(GStrv) udev_properties = NULL; + const char *sys_path; json_builder_set_member_name (builder, "dev_node"); json_builder_add_string_value (builder, @@ -120,39 +159,74 @@ added (SrtInputDeviceMonitor *monitor, g_autofree gchar *uevent = srt_input_device_dup_uevent (dev); if (uevent != NULL) - { - const char *start = uevent; - const char *end; + add_uevent_member (builder, "uevent", uevent); + } + + sys_path = srt_input_device_get_hid_sys_path (dev); - /* uevent is conceptually a single string, but we - * present it as an array of lines here, because that's - * a lot easier to read! */ - json_builder_set_member_name (builder, "uevent"); - json_builder_begin_array (builder); + if (sys_path != NULL) + { + json_builder_set_member_name (builder, "hid_ancestor"); + json_builder_begin_object (builder); + { + json_builder_set_member_name (builder, "sys_path"); + json_builder_add_string_value (builder, sys_path); - while (*start != '\0') + if (opt_verbose) { - g_autofree gchar *valid = NULL; + g_autofree gchar *uevent = srt_input_device_dup_hid_uevent (dev); + + if (uevent != NULL) + add_uevent_member (builder, "uevent", uevent); + } + + /* TODO: vendor, product etc. */ + } + json_builder_end_object (builder); + } + + sys_path = srt_input_device_get_input_sys_path (dev); + + if (sys_path != NULL) + { + json_builder_set_member_name (builder, "input_ancestor"); + json_builder_begin_object (builder); + { + json_builder_set_member_name (builder, "sys_path"); + json_builder_add_string_value (builder, sys_path); - while (*start == '\n') - start++; + if (opt_verbose) + { + g_autofree gchar *uevent = srt_input_device_dup_input_uevent (dev); - if (*start == '\0') - break; + if (uevent != NULL) + add_uevent_member (builder, "uevent", uevent); + } + } + json_builder_end_object (builder); + } - end = strchrnul (start, '\n'); + sys_path = srt_input_device_get_usb_device_sys_path (dev); - valid = g_utf8_make_valid (start, (end - start) - 1); - json_builder_add_string_value (builder, valid); + if (sys_path != NULL) + { + json_builder_set_member_name (builder, "usb_device_ancestor"); + json_builder_begin_object (builder); + { + json_builder_set_member_name (builder, "sys_path"); + json_builder_add_string_value (builder, sys_path); - if (*end == '\0') - break; + if (opt_verbose) + { + g_autofree gchar *uevent = srt_input_device_dup_usb_device_uevent (dev); - start = end + 1; + if (uevent != NULL) + add_uevent_member (builder, "uevent", uevent); } - json_builder_end_array (builder); + /* TODO: vendor, product etc. */ } + json_builder_end_object (builder); } /* TODO: Print more details here */ diff --git a/steam-runtime-tools/direct-input-device.c b/steam-runtime-tools/direct-input-device.c index a1431bc727527ea576634ac4dd3d5e468dd67578..7cd31f930ecb81090827e5e98b00368afd97654e 100644 --- a/steam-runtime-tools/direct-input-device.c +++ b/steam-runtime-tools/direct-input-device.c @@ -43,6 +43,108 @@ static void srt_direct_input_device_iface_init (SrtInputDeviceInterface *iface); static void srt_direct_input_device_monitor_iface_init (SrtInputDeviceMonitorInterface *iface); +static void +remove_last_component_in_place (char *path) +{ + char *slash = strrchr (path, '/'); + + if (slash == NULL) + *path = '\0'; + else + *slash = '\0'; +} + +/* + * @path: The path to a device directory in /sys + * + * Returns: (nullable) (transfer full): The closest ancestor of @path + * that is an input device with evdev capabilities, or %NULL if not found + */ +static gchar * +find_input_ancestor (const char *path) +{ + g_autofree char *ancestor = NULL; + + for (ancestor = g_strdup (path); + g_str_has_prefix (ancestor, "/sys/"); + remove_last_component_in_place (ancestor)) + { + g_autofree char *subsys = g_build_filename (ancestor, "subsystem", NULL); + g_autofree char *caps = g_build_filename (ancestor, "capabilities", "ev", NULL); + g_autofree char *target = glnx_readlinkat_malloc (AT_FDCWD, subsys, + NULL, NULL); + const char *slash = NULL; + + if (!g_file_test (caps, G_FILE_TEST_IS_REGULAR)) + continue; + + if (target != NULL) + slash = strrchr (target, '/'); + + if (slash == NULL || strcmp (slash + 1, "input") != 0) + continue; + + return g_steal_pointer (&ancestor); + } + + return NULL; +} + +/* + * @path: The path to a device directory in /sys + * @subsystem: A desired subsystem, such as "hid" or "usb", or %NULL to accept any + * @devtype: A desired device type, such as "usb_device", or %NULL to accept any + * @uevent_out: (out) (optional): Optionally return the text of /sys/.../uevent + * + * Returns: (nullable) (transfer full): The closest ancestor of @path + * that has a subsystem of @subsystem, or %NULL if not found. + */ +static gchar * +get_ancestor_with_subsystem_devtype (const char *path, + const char *subsystem, + const char *devtype, + gchar **uevent_out) +{ + g_autofree char *ancestor = NULL; + + for (ancestor = g_strdup (path); + g_str_has_prefix (ancestor, "/sys/"); + remove_last_component_in_place (ancestor)) + { + g_autofree char *uevent = g_build_filename (ancestor, "uevent", NULL); + g_autofree char *text = NULL; + + /* If it doesn't have a uevent file, it isn't a real device */ + if (!g_file_get_contents (uevent, &text, NULL, NULL)) + continue; + + if (subsystem != NULL) + { + g_autofree char *subsys = g_build_filename (ancestor, "subsystem", NULL); + g_autofree char *target = glnx_readlinkat_malloc (AT_FDCWD, subsys, + NULL, NULL); + const char *slash = NULL; + + if (target != NULL) + slash = strrchr (target, '/'); + + if (slash == NULL || strcmp (slash + 1, subsystem) != 0) + continue; + } + + if (devtype != NULL + && !_srt_input_device_uevent_field_equals (text, "DEVTYPE", devtype)) + continue; + + if (uevent_out != NULL) + *uevent_out = g_steal_pointer (&text); + + return g_steal_pointer (&ancestor); + } + + return NULL; +} + static GQuark quark_hidraw = 0; static GQuark quark_input = 0; @@ -53,6 +155,21 @@ struct _SrtDirectInputDevice char *sys_path; gchar *dev_node; GQuark subsystem; + + struct + { + gchar *sys_path; + } hid_ancestor; + + struct + { + gchar *sys_path; + } input_ancestor; + + struct + { + gchar *sys_path; + } usb_device_ancestor; }; struct _SrtDirectInputDeviceClass @@ -109,6 +226,9 @@ srt_direct_input_device_finalize (GObject *object) g_clear_pointer (&self->sys_path, free); g_clear_pointer (&self->dev_node, g_free); + g_clear_pointer (&self->hid_ancestor.sys_path, g_free); + g_clear_pointer (&self->input_ancestor.sys_path, g_free); + g_clear_pointer (&self->usb_device_ancestor.sys_path, g_free); G_OBJECT_CLASS (_srt_direct_input_device_parent_class)->finalize (object); } @@ -145,6 +265,30 @@ srt_direct_input_device_get_sys_path (SrtInputDevice *device) return self->sys_path; } +static const char * +srt_direct_input_device_get_hid_sys_path (SrtInputDevice *device) +{ + SrtDirectInputDevice *self = SRT_DIRECT_INPUT_DEVICE (device); + + return self->hid_ancestor.sys_path; +} + +static const char * +srt_direct_input_device_get_input_sys_path (SrtInputDevice *device) +{ + SrtDirectInputDevice *self = SRT_DIRECT_INPUT_DEVICE (device); + + return self->input_ancestor.sys_path; +} + +static const char * +srt_direct_input_device_get_usb_device_sys_path (SrtInputDevice *device) +{ + SrtDirectInputDevice *self = SRT_DIRECT_INPUT_DEVICE (device); + + return self->usb_device_ancestor.sys_path; +} + static void srt_direct_input_device_iface_init (SrtInputDeviceInterface *iface) { @@ -154,6 +298,10 @@ srt_direct_input_device_iface_init (SrtInputDeviceInterface *iface) IMPLEMENT (get_sys_path); IMPLEMENT (get_subsystem); + IMPLEMENT (get_hid_sys_path); + IMPLEMENT (get_input_sys_path); + IMPLEMENT (get_usb_device_sys_path); + #undef IMPLEMENT } @@ -311,6 +459,15 @@ add_device (SrtDirectInputDeviceMonitor *self, return; } + device->hid_ancestor.sys_path = get_ancestor_with_subsystem_devtype (device->sys_path, + "hid", NULL, + NULL); + device->input_ancestor.sys_path = find_input_ancestor (device->sys_path); + device->usb_device_ancestor.sys_path = get_ancestor_with_subsystem_devtype (device->sys_path, + "usb", + "usb_device", + NULL); + g_hash_table_replace (self->devices, device->dev_node, device); _srt_input_device_monitor_emit_added (SRT_INPUT_DEVICE_MONITOR (self), SRT_INPUT_DEVICE (device)); diff --git a/steam-runtime-tools/input-device-internal.h b/steam-runtime-tools/input-device-internal.h index 02acf67cc57b2aac3e9c74be720b5e0158fdfb52..4b5589ff17fa3a952e4f619a7b7aad3e9ba401e2 100644 --- a/steam-runtime-tools/input-device-internal.h +++ b/steam-runtime-tools/input-device-internal.h @@ -37,6 +37,15 @@ struct _SrtInputDeviceInterface const char * (*get_subsystem) (SrtInputDevice *device); gchar ** (*dup_udev_properties) (SrtInputDevice *device); gchar * (*dup_uevent) (SrtInputDevice *device); + + const char * (*get_hid_sys_path) (SrtInputDevice *device); + gchar * (*dup_hid_uevent) (SrtInputDevice *device); + + const char * (*get_input_sys_path) (SrtInputDevice *device); + gchar * (*dup_input_uevent) (SrtInputDevice *device); + + const char * (*get_usb_device_sys_path) (SrtInputDevice *device); + gchar * (*dup_usb_device_uevent) (SrtInputDevice *device); }; struct _SrtInputDeviceMonitorInterface @@ -63,3 +72,9 @@ void _srt_input_device_monitor_emit_added (SrtInputDeviceMonitor *monitor, void _srt_input_device_monitor_emit_removed (SrtInputDeviceMonitor *monitor, SrtInputDevice *device); void _srt_input_device_monitor_emit_all_for_now (SrtInputDeviceMonitor *monitor); + +gchar *_srt_input_device_uevent_field (const char *text, + const char *key); +gboolean _srt_input_device_uevent_field_equals (const char *text, + const char *key, + const char *want_value); diff --git a/steam-runtime-tools/input-device.c b/steam-runtime-tools/input-device.c index 7188bbf30cfdc143baeae719ef44c3381111bede..3054cc2ffcc2435f2b24b8d6433b4fc528d849a4 100644 --- a/steam-runtime-tools/input-device.c +++ b/steam-runtime-tools/input-device.c @@ -154,18 +154,11 @@ srt_input_device_dup_uevent (SrtInputDevice *device) return iface->dup_uevent (device); } -/* - * Default implementation: read the file in /sys. The kernel provides this, - * so it should always be present, except in containers. - */ static gchar * -srt_input_device_default_dup_uevent (SrtInputDevice *device) +read_uevent (const char *sys_path) { g_autofree gchar *uevent_path = NULL; g_autofree gchar *contents = NULL; - const char *sys_path; - - sys_path = srt_input_device_get_sys_path (device); if (sys_path == NULL) return NULL; @@ -178,6 +171,34 @@ srt_input_device_default_dup_uevent (SrtInputDevice *device) return NULL; } +/* + * Default implementation: read the file in /sys. The kernel provides this, + * so it should always be present, except in containers. + */ +static gchar * +srt_input_device_default_dup_uevent (SrtInputDevice *device) +{ + return read_uevent (srt_input_device_get_sys_path (device)); +} + +static gchar * +srt_input_device_default_dup_hid_uevent (SrtInputDevice *device) +{ + return read_uevent (srt_input_device_get_hid_sys_path (device)); +} + +static gchar * +srt_input_device_default_dup_input_uevent (SrtInputDevice *device) +{ + return read_uevent (srt_input_device_get_input_sys_path (device)); +} + +static gchar * +srt_input_device_default_dup_usb_device_uevent (SrtInputDevice *device) +{ + return read_uevent (srt_input_device_get_usb_device_sys_path (device)); +} + /** * srt_input_device_dup_udev_properties: * @device: An object implementing #SrtInputDeviceInterface @@ -199,6 +220,153 @@ srt_input_device_dup_udev_properties (SrtInputDevice *device) return iface->dup_udev_properties (device); } +/** + * srt_input_device_get_hid_sys_path: + * @device: An object implementing #SrtInputDeviceInterface + * + * Return the path of the device directory in /sys that represents this + * input device's closest ancestor that is a Human Interface Device. + * Many, but not all, input devices have a HID ancestor. If there is no + * applicable HID device, return %NULL. + * + * For processes in a container, it is not guaranteed that this path will + * exist in the container's /sys. + * + * The returned string will not be freed as long as @device is referenced, + * but the directory in /sys might be deleted, or even reused for a different + * device. + * + * Returns: (nullable) (transfer none): A path in /sys that will not be + * freed as long as a reference to @device is held, or %NULL + */ +const char * +srt_input_device_get_hid_sys_path (SrtInputDevice *device) +{ + SrtInputDeviceInterface *iface = SRT_INPUT_DEVICE_GET_INTERFACE (device); + + g_return_val_if_fail (iface != NULL, NULL); + + return iface->get_hid_sys_path (device); +} + +/** + * srt_input_device_dup_hid_uevent: + * @device: An object implementing #SrtInputDeviceInterface + * + * Return the uevent data structure similar to + * srt_input_device_dup_uevent(), but for the ancestor device returned + * by srt_input_device_get_hid_sys_path(). + * + * Returns: (nullable) (transfer full): A multi-line string, or %NULL. + * Free with g_free(). + */ +gchar * +srt_input_device_dup_hid_uevent (SrtInputDevice *device) +{ + SrtInputDeviceInterface *iface = SRT_INPUT_DEVICE_GET_INTERFACE (device); + + g_return_val_if_fail (iface != NULL, NULL); + + return iface->dup_hid_uevent (device); +} + +/** + * srt_input_device_get_usb_device_sys_path: + * @device: An object implementing #SrtInputDeviceInterface + * + * If the @device is associated with a USB device, return the path in + * /sys representing the Linux `usb_device`. If not, return %NULL. + * + * For processes in a container, it is not guaranteed that this path will + * exist in the container's /sys. + * + * The returned string will not be freed as long as @device is referenced, + * but the directory in /sys might be deleted, or even reused for a different + * device. + * + * Returns: (nullable) (transfer none): A path in /sys that will not be + * freed as long as a reference to @device is held, or %NULL + */ +const char * +srt_input_device_get_usb_device_sys_path (SrtInputDevice *device) +{ + SrtInputDeviceInterface *iface = SRT_INPUT_DEVICE_GET_INTERFACE (device); + + g_return_val_if_fail (iface != NULL, NULL); + + return iface->get_usb_device_sys_path (device); +} + +/** + * srt_input_device_dup_usb_device_uevent: + * @device: An object implementing #SrtInputDeviceInterface + * + * Return the uevent data structure similar to + * srt_input_device_dup_uevent(), but for the ancestor device returned + * by srt_input_device_get_usb_device_sys_path(). + * + * Returns: (nullable) (transfer full): A multi-line string, or %NULL. + * Free with g_free(). + */ +gchar * +srt_input_device_dup_usb_device_uevent (SrtInputDevice *device) +{ + SrtInputDeviceInterface *iface = SRT_INPUT_DEVICE_GET_INTERFACE (device); + + g_return_val_if_fail (iface != NULL, NULL); + + return iface->dup_usb_device_uevent (device); +} + +/** + * srt_input_device_get_input_sys_path: + * @device: An object implementing #SrtInputDeviceInterface + * + * If the @device has an ancestor device that advertises evdev input + * capabilities, return the path in /sys for that device. Otherwise + * return %NULL. + * + * For processes in a container, it is not guaranteed that this path will + * exist in the container's /sys. + * + * The returned string will not be freed as long as @device is referenced, + * but the directory in /sys might be deleted, or even reused for a different + * device. + * + * Returns: (nullable) (transfer none): A path in /sys that will not be + * freed as long as a reference to @device is held, or %NULL + */ +const char * +srt_input_device_get_input_sys_path (SrtInputDevice *device) +{ + SrtInputDeviceInterface *iface = SRT_INPUT_DEVICE_GET_INTERFACE (device); + + g_return_val_if_fail (iface != NULL, NULL); + + return iface->get_input_sys_path (device); +} + +/** + * srt_input_device_dup_input_uevent: + * @device: An object implementing #SrtInputDeviceInterface + * + * Return the uevent data structure similar to + * srt_input_device_dup_uevent(), but for the ancestor device returned + * by srt_input_device_get_input_sys_path(). + * + * Returns: (nullable) (transfer full): A multi-line string, or %NULL. + * Free with g_free(). + */ +gchar * +srt_input_device_dup_input_uevent (SrtInputDevice *device) +{ + SrtInputDeviceInterface *iface = SRT_INPUT_DEVICE_GET_INTERFACE (device); + + g_return_val_if_fail (iface != NULL, NULL); + + return iface->dup_input_uevent (device); +} + static void srt_input_device_default_init (SrtInputDeviceInterface *iface) { @@ -208,8 +376,17 @@ srt_input_device_default_init (SrtInputDeviceInterface *iface) IMPLEMENT2 (get_dev_node, get_null_string); IMPLEMENT2 (get_sys_path, get_null_string); IMPLEMENT2 (get_subsystem, get_null_string); + + IMPLEMENT2 (get_hid_sys_path, get_null_string); + IMPLEMENT2 (get_input_sys_path, get_null_string); + IMPLEMENT2 (get_usb_device_sys_path, get_null_string); + IMPLEMENT2 (dup_udev_properties, get_null_strv); + IMPLEMENT (dup_uevent); + IMPLEMENT (dup_hid_uevent); + IMPLEMENT (dup_input_uevent); + IMPLEMENT (dup_usb_device_uevent); #undef IMPLEMENT #undef IMPLEMENT2 @@ -581,3 +758,77 @@ _srt_input_device_monitor_emit_all_for_now (SrtInputDeviceMonitor *monitor) g_debug ("All for now"); g_signal_emit (monitor, monitor_signal_all_for_now, 0); } + +/* + * Returns: The field @key from @text, or %NULL if not found. + */ +gchar * +_srt_input_device_uevent_field (const char *text, + const char *key) +{ + const char *line = text; + size_t key_len = strlen (key); + + for (line = text; + line != NULL && *line != '\0'; + line = strchr (line, '\n')) + { + while (*line == '\n') + line++; + + /* If the line starts with KEY=, we're looking at the right field */ + if (strncmp (line, key, key_len) == 0 + && line[key_len] == '=') + { + const char *value = line + key_len + 1; + const char *after = strchrnul (line, '\n'); + + g_assert (after != NULL); + g_assert (after >= value); + g_assert (*after == '\n' || *after == '\0'); + return g_strndup (value, after - value); + } + } + + return NULL; +} + +/* + * Returns: %TRUE if @text contains KEY=WANT_VALUE, preceded by + * beginning-of-string or a newline, and followed by a newline + * or end-of-string. + */ +gboolean +_srt_input_device_uevent_field_equals (const char *text, + const char *key, + const char *want_value) +{ + const char *line = text; + size_t key_len = strlen (key); + size_t val_len = strlen (want_value); + + for (line = text; + line != NULL && *line != '\0'; + line = strchr (line, '\n')) + { + while (*line == '\n') + line++; + + /* If the line starts with KEY=, we're looking at the right field */ + if (strncmp (line, key, key_len) == 0 + && line[key_len] == '=') + { + const char *real_value = line + key_len + 1; + char after; + + if (strncmp (real_value, want_value, val_len) != 0) + return FALSE; + + after = real_value[val_len]; + + return (after == '\0' || after == '\n'); + } + } + + return FALSE; +} diff --git a/steam-runtime-tools/input-device.h b/steam-runtime-tools/input-device.h index 67685b7366cbc67a6451a897fe7714a623599673..037e4c992be9a65bbf11c5dc523504733a035a79 100644 --- a/steam-runtime-tools/input-device.h +++ b/steam-runtime-tools/input-device.h @@ -55,6 +55,21 @@ gchar **srt_input_device_dup_udev_properties (SrtInputDevice *device); _SRT_PUBLIC gchar *srt_input_device_dup_uevent (SrtInputDevice *device); +_SRT_PUBLIC +const char *srt_input_device_get_hid_sys_path (SrtInputDevice *device); +_SRT_PUBLIC +gchar *srt_input_device_dup_hid_uevent (SrtInputDevice *device); + +_SRT_PUBLIC +const char *srt_input_device_get_input_sys_path (SrtInputDevice *device); +_SRT_PUBLIC +gchar *srt_input_device_dup_input_uevent (SrtInputDevice *device); + +_SRT_PUBLIC +const char *srt_input_device_get_usb_device_sys_path (SrtInputDevice *device); +_SRT_PUBLIC +gchar *srt_input_device_dup_usb_device_uevent (SrtInputDevice *device); + /** * SrtInputDeviceMonitorFlags: * @SRT_INPUT_DEVICE_MONITOR_FLAGS_ONCE: Enumerate the devices that were diff --git a/steam-runtime-tools/udev-input-device.c b/steam-runtime-tools/udev-input-device.c index 7245a7535079218fa177f2642ffe747a4b3cd9af..ca3594cb81cc88f9704a7d130f09737b675b1995 100644 --- a/steam-runtime-tools/udev-input-device.c +++ b/steam-runtime-tools/udev-input-device.c @@ -65,6 +65,7 @@ static struct struct udev_list_entry *(*udev_device_get_properties_list_entry) (struct udev_device *); const char *(*udev_device_get_property_value) (struct udev_device *, const char *); const char *(*udev_device_get_sysattr_value) (struct udev_device *, const char *); + struct udev_device *(*udev_device_get_parent_with_subsystem_devtype) (struct udev_device *, const char *, const char *); struct udev_device *(*udev_device_ref) (struct udev_device *); struct udev_device *(*udev_device_unref) (struct udev_device *); @@ -86,11 +87,42 @@ static struct struct udev_enumerate *(*udev_enumerate_unref) (struct udev_enumerate *); } symbols; +static struct udev_device * +find_input_ancestor (struct udev_device *dev) +{ + struct udev_device *ancestor; + + for (ancestor = dev; + ancestor != NULL; + ancestor = symbols.udev_device_get_parent_with_subsystem_devtype (ancestor, "input", NULL)) + { + if (symbols.udev_device_get_sysattr_value (ancestor, "capabilities/ev") != NULL) + return ancestor; + } + + return NULL; +} + struct _SrtUdevInputDevice { GObject parent; - struct udev_device *dev; + struct udev_device *dev; /* owned */ + + struct + { + struct udev_device *dev; /* borrowed from child dev */ + } hid_ancestor; + + struct + { + struct udev_device *dev; /* borrowed from child dev */ + } input_ancestor; + + struct + { + struct udev_device *dev; /* borrowed from child dev */ + } usb_device_ancestor; }; struct _SrtUdevInputDeviceClass @@ -217,6 +249,72 @@ srt_udev_input_device_dup_uevent (SrtInputDevice *device) return g_strdup (symbols.udev_device_get_sysattr_value (self->dev, "uevent")); } +static const char * +srt_udev_input_device_get_hid_sys_path (SrtInputDevice *device) +{ + SrtUdevInputDevice *self = SRT_UDEV_INPUT_DEVICE (device); + + if (self->hid_ancestor.dev == NULL) + return NULL; + + return symbols.udev_device_get_syspath (self->hid_ancestor.dev); +} + +static const char * +srt_udev_input_device_get_input_sys_path (SrtInputDevice *device) +{ + SrtUdevInputDevice *self = SRT_UDEV_INPUT_DEVICE (device); + + if (self->input_ancestor.dev == NULL) + return NULL; + + return symbols.udev_device_get_syspath (self->input_ancestor.dev); +} + +static const char * +srt_udev_input_device_get_usb_device_sys_path (SrtInputDevice *device) +{ + SrtUdevInputDevice *self = SRT_UDEV_INPUT_DEVICE (device); + + if (self->usb_device_ancestor.dev == NULL) + return NULL; + + return symbols.udev_device_get_syspath (self->usb_device_ancestor.dev); +} + +static gchar * +srt_udev_input_device_dup_hid_uevent (SrtInputDevice *device) +{ + SrtUdevInputDevice *self = SRT_UDEV_INPUT_DEVICE (device); + + if (self->hid_ancestor.dev == NULL) + return NULL; + + return g_strdup (symbols.udev_device_get_sysattr_value (self->hid_ancestor.dev, "uevent")); +} + +static gchar * +srt_udev_input_device_dup_input_uevent (SrtInputDevice *device) +{ + SrtUdevInputDevice *self = SRT_UDEV_INPUT_DEVICE (device); + + if (self->input_ancestor.dev == NULL) + return NULL; + + return g_strdup (symbols.udev_device_get_sysattr_value (self->input_ancestor.dev, "uevent")); +} + +static gchar * +srt_udev_input_device_dup_usb_device_uevent (SrtInputDevice *device) +{ + SrtUdevInputDevice *self = SRT_UDEV_INPUT_DEVICE (device); + + if (self->usb_device_ancestor.dev == NULL) + return NULL; + + return g_strdup (symbols.udev_device_get_sysattr_value (self->usb_device_ancestor.dev, "uevent")); +} + static void srt_udev_input_device_iface_init (SrtInputDeviceInterface *iface) { @@ -228,6 +326,14 @@ srt_udev_input_device_iface_init (SrtInputDeviceInterface *iface) IMPLEMENT (dup_udev_properties); IMPLEMENT (dup_uevent); + IMPLEMENT (get_hid_sys_path); + IMPLEMENT (get_input_sys_path); + IMPLEMENT (get_usb_device_sys_path); + + IMPLEMENT (dup_hid_uevent); + IMPLEMENT (dup_input_uevent); + IMPLEMENT (dup_usb_device_uevent); + #undef IMPLEMENT } @@ -391,6 +497,7 @@ srt_udev_input_device_monitor_initable_init (GInitable *initable, SYMBOL (udev_device_get_properties_list_entry); SYMBOL (udev_device_get_property_value); SYMBOL (udev_device_get_sysattr_value); + SYMBOL (udev_device_get_parent_with_subsystem_devtype); SYMBOL (udev_device_ref); SYMBOL (udev_device_unref); @@ -480,6 +587,14 @@ add_device (SrtUdevInputDeviceMonitor *self, device = g_object_new (SRT_TYPE_UDEV_INPUT_DEVICE, NULL); device->dev = symbols.udev_device_ref (dev); + device->hid_ancestor.dev = symbols.udev_device_get_parent_with_subsystem_devtype (device->dev, + "hid", + NULL); + device->input_ancestor.dev = find_input_ancestor (device->dev); + device->usb_device_ancestor.dev = symbols.udev_device_get_parent_with_subsystem_devtype (device->dev, + "usb", + "usb_device"); + g_hash_table_replace (self->devices, (char *) syspath, device); _srt_input_device_monitor_emit_added (SRT_INPUT_DEVICE_MONITOR (self), SRT_INPUT_DEVICE (device)); diff --git a/tests/input-device.c b/tests/input-device.c index 74335137a54d4e20527e1e6273f993c8161eb983..af84938582f1ece392a01560b2086486af465c64 100644 --- a/tests/input-device.c +++ b/tests/input-device.c @@ -104,6 +104,9 @@ test_input_device_usb (Fixture *f, SrtInputDevice *device = SRT_INPUT_DEVICE (mock_device); g_auto(GStrv) udev_properties = NULL; g_autofree gchar *uevent = NULL; + g_autofree gchar *hid_uevent = NULL; + g_autofree gchar *input_uevent = NULL; + g_autofree gchar *usb_uevent = NULL; mock_device->dev_node = g_strdup ("/dev/input/event0"); mock_device->sys_path = g_strdup ("/sys/devices/mock/usb/hid/input/input0/event0"); @@ -113,6 +116,15 @@ test_input_device_usb (Fixture *f, mock_device->udev_properties[1] = NULL; mock_device->uevent = g_strdup ("A=a\nB=b\n"); + mock_device->hid_ancestor.sys_path = g_strdup ("/sys/devices/mock/usb/hid"); + mock_device->hid_ancestor.uevent = g_strdup ("HID=yes\n"); + + mock_device->input_ancestor.sys_path = g_strdup ("/sys/devices/mock/usb/hid/input"); + mock_device->input_ancestor.uevent = g_strdup ("INPUT=yes\n"); + + mock_device->usb_device_ancestor.sys_path = g_strdup ("/sys/devices/mock/usb"); + mock_device->usb_device_ancestor.uevent = g_strdup ("USB=usb_device\n"); + /* TODO: Fill in somewhat realistic details for a USB-attached * Steam Controller */ @@ -125,6 +137,21 @@ test_input_device_usb (Fixture *f, uevent = srt_input_device_dup_uevent (device); g_assert_cmpstr (uevent, ==, "A=a\nB=b\n"); + g_assert_cmpstr (srt_input_device_get_hid_sys_path (device), ==, + "/sys/devices/mock/usb/hid"); + hid_uevent = srt_input_device_dup_hid_uevent (device); + g_assert_cmpstr (hid_uevent, ==, "HID=yes\n"); + + g_assert_cmpstr (srt_input_device_get_input_sys_path (device), ==, + "/sys/devices/mock/usb/hid/input"); + input_uevent = srt_input_device_dup_input_uevent (device); + g_assert_cmpstr (input_uevent, ==, "INPUT=yes\n"); + + g_assert_cmpstr (srt_input_device_get_usb_device_sys_path (device), ==, + "/sys/devices/mock/usb"); + usb_uevent = srt_input_device_dup_usb_device_uevent (device); + g_assert_cmpstr (usb_uevent, ==, "USB=usb_device\n"); + udev_properties = srt_input_device_dup_udev_properties (device); g_assert_nonnull (udev_properties); g_assert_cmpstr (udev_properties[0], ==, "ID_INPUT_JOYSTICK=1"); @@ -164,6 +191,9 @@ device_added_cb (SrtInputDeviceMonitor *monitor, if (f->config->type == MOCK) { g_autofree gchar *uevent = NULL; + g_autofree gchar *hid_uevent = NULL; + g_autofree gchar *input_uevent = NULL; + g_autofree gchar *usb_uevent = NULL; g_auto(GStrv) udev_properties = NULL; uevent = srt_input_device_dup_uevent (device); @@ -174,6 +204,21 @@ device_added_cb (SrtInputDeviceMonitor *monitor, g_assert_cmpstr (udev_properties[0], ==, "ID_INPUT_JOYSTICK=1"); g_assert_cmpstr (udev_properties[1], ==, NULL); + g_assert_cmpstr (srt_input_device_get_hid_sys_path (device), ==, + "/sys/devices/mock/usb/hid"); + hid_uevent = srt_input_device_dup_hid_uevent (device); + g_assert_cmpstr (hid_uevent, ==, "HID=yes\n"); + + g_assert_cmpstr (srt_input_device_get_input_sys_path (device), ==, + "/sys/devices/mock/usb/hid/input"); + input_uevent = srt_input_device_dup_input_uevent (device); + g_assert_cmpstr (input_uevent, ==, "INPUT=yes\n"); + + g_assert_cmpstr (srt_input_device_get_usb_device_sys_path (device), ==, + "/sys/devices/mock/usb"); + usb_uevent = srt_input_device_dup_usb_device_uevent (device); + g_assert_cmpstr (usb_uevent, ==, "USB=usb_device\n"); + g_ptr_array_add (f->log, g_steal_pointer (&message)); } diff --git a/tests/mock-input-device.c b/tests/mock-input-device.c index cbbc05824e3f0fd8bfb639512d010c450f973ae3..3a2bda45d618db18b1c6b561787dc50c67a9591d 100644 --- a/tests/mock-input-device.c +++ b/tests/mock-input-device.c @@ -66,6 +66,15 @@ mock_input_device_finalize (GObject *object) g_clear_pointer (&self->udev_properties, g_strfreev); g_clear_pointer (&self->uevent, g_free); + g_clear_pointer (&self->hid_ancestor.sys_path, g_free); + g_clear_pointer (&self->hid_ancestor.uevent, g_free); + + g_clear_pointer (&self->input_ancestor.sys_path, g_free); + g_clear_pointer (&self->input_ancestor.uevent, g_free); + + g_clear_pointer (&self->usb_device_ancestor.sys_path, g_free); + g_clear_pointer (&self->usb_device_ancestor.uevent, g_free); + G_OBJECT_CLASS (mock_input_device_parent_class)->finalize (object); } @@ -117,6 +126,54 @@ mock_input_device_dup_uevent (SrtInputDevice *device) return g_strdup (self->uevent); } +static const char * +mock_input_device_get_hid_sys_path (SrtInputDevice *device) +{ + MockInputDevice *self = MOCK_INPUT_DEVICE (device); + + return self->hid_ancestor.sys_path; +} + +static gchar * +mock_input_device_dup_hid_uevent (SrtInputDevice *device) +{ + MockInputDevice *self = MOCK_INPUT_DEVICE (device); + + return g_strdup (self->hid_ancestor.uevent); +} + +static const char * +mock_input_device_get_input_sys_path (SrtInputDevice *device) +{ + MockInputDevice *self = MOCK_INPUT_DEVICE (device); + + return self->input_ancestor.sys_path; +} + +static gchar * +mock_input_device_dup_input_uevent (SrtInputDevice *device) +{ + MockInputDevice *self = MOCK_INPUT_DEVICE (device); + + return g_strdup (self->input_ancestor.uevent); +} + +static const char * +mock_input_device_get_usb_device_sys_path (SrtInputDevice *device) +{ + MockInputDevice *self = MOCK_INPUT_DEVICE (device); + + return self->usb_device_ancestor.sys_path; +} + +static gchar * +mock_input_device_dup_usb_device_uevent (SrtInputDevice *device) +{ + MockInputDevice *self = MOCK_INPUT_DEVICE (device); + + return g_strdup (self->usb_device_ancestor.uevent); +} + static void mock_input_device_iface_init (SrtInputDeviceInterface *iface) { @@ -128,6 +185,13 @@ mock_input_device_iface_init (SrtInputDeviceInterface *iface) IMPLEMENT (dup_udev_properties); IMPLEMENT (dup_uevent); + IMPLEMENT (get_hid_sys_path); + IMPLEMENT (dup_hid_uevent); + IMPLEMENT (get_input_sys_path); + IMPLEMENT (dup_input_uevent); + IMPLEMENT (get_usb_device_sys_path); + IMPLEMENT (dup_usb_device_uevent); + #undef IMPLEMENT } @@ -283,6 +347,15 @@ add_steam_controller (MockInputDeviceMonitor *self, device->subsystem = g_strdup ("input"); device->uevent = g_strdup ("ONE=1\nTWO=2\n"); + device->hid_ancestor.sys_path = g_strdup ("/sys/devices/mock/usb/hid"); + device->hid_ancestor.uevent = g_strdup ("HID=yes\n"); + + device->input_ancestor.sys_path = g_strdup ("/sys/devices/mock/usb/hid/input"); + device->input_ancestor.uevent = g_strdup ("INPUT=yes\n"); + + device->usb_device_ancestor.sys_path = g_strdup ("/sys/devices/mock/usb"); + device->usb_device_ancestor.uevent = g_strdup ("USB=usb_device\n"); + g_ptr_array_add (arr, g_strdup ("ID_INPUT_JOYSTICK=1")); g_ptr_array_add (arr, NULL); device->udev_properties = (gchar **) g_ptr_array_free (g_steal_pointer (&arr), FALSE); diff --git a/tests/mock-input-device.h b/tests/mock-input-device.h index 1f73593d79ab1ee1970f233b15344112ec532265..b3255af3760896ac2b407ae916e9f5af6538940b 100644 --- a/tests/mock-input-device.h +++ b/tests/mock-input-device.h @@ -54,6 +54,24 @@ struct _MockInputDevice gchar *subsystem; gchar **udev_properties; gchar *uevent; + + struct + { + gchar *sys_path; + gchar *uevent; + } hid_ancestor; + + struct + { + gchar *sys_path; + gchar *uevent; + } input_ancestor; + + struct + { + gchar *sys_path; + gchar *uevent; + } usb_device_ancestor; }; struct _MockInputDeviceClass diff --git a/tests/utils.c b/tests/utils.c index e4e22b5bdba507894a1136d626e058693d6468de..569d274d48730835f44fe5e38fb0ce99987f0a97 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -29,6 +29,7 @@ #include <glib/gstdio.h> #include <glib-object.h> +#include "steam-runtime-tools/input-device-internal.h" #include "steam-runtime-tools/utils-internal.h" #include "test-utils.h" @@ -229,6 +230,64 @@ test_str_is_integer (Fixture *f, g_assert_false (_srt_str_is_integer ("23a")); } +static const char uevent[] = +"DRIVER=lenovo\n" +"HID_ID=0003:000017EF:00006009\n" +"HID_NAME=Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint\n" +"HID_PHYS=usb-0000:00:14.0-2/input0\n" +"HID_UNIQ=\n" +"MODALIAS=hid:b0003g0000v000017EFp00006009\n"; + +static struct +{ + const char *key; + const char *value; +} uevent_parsed[] = +{ + { "DRIVER", "lenovo" }, + { "HID_ID", "0003:000017EF:00006009" }, + { "HID_NAME", "Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint" }, + { "HID_PHYS", "usb-0000:00:14.0-2/input0" }, + { "HID_UNIQ", "" }, + { "MODALIAS", "hid:b0003g0000v000017EFp00006009" } +}; + +static const char no_newline[] = "DRIVER=lenovo"; + +static void +test_uevent_field (Fixture *f, + gconstpointer context) +{ + gsize i; + + g_assert_false (_srt_input_device_uevent_field_equals (no_newline, "DRIVER", "")); + g_assert_false (_srt_input_device_uevent_field_equals (no_newline, "DRIVER", "lenov")); + g_assert_true (_srt_input_device_uevent_field_equals (no_newline, "DRIVER", "lenovo")); + g_assert_false (_srt_input_device_uevent_field_equals (no_newline, "DRIVER", "lenovoo")); + + g_assert_false (_srt_input_device_uevent_field_equals (uevent, "DRIVER", "lenov")); + g_assert_false (_srt_input_device_uevent_field_equals (uevent, "DRIVER", "lenovoo")); + g_assert_false (_srt_input_device_uevent_field_equals (uevent, "HID_ID", "0003:000017EF:0000600")); + g_assert_false (_srt_input_device_uevent_field_equals (uevent, "HID_ID", "0003:000017EF:000060099")); + g_assert_false (_srt_input_device_uevent_field_equals (uevent, "HID_UNIQ", "x")); + g_assert_false (_srt_input_device_uevent_field_equals (uevent, "MODALIAS", "nope")); + g_assert_false (_srt_input_device_uevent_field_equals (uevent, "NOPE", "")); + g_assert_false (_srt_input_device_uevent_field_equals (uevent, "NOPE", "nope")); + + for (i = 0; i < G_N_ELEMENTS (uevent_parsed); i++) + { + g_autofree gchar *v = NULL; + + v = _srt_input_device_uevent_field (uevent, uevent_parsed[i].key); + g_assert_cmpstr (v, ==, uevent_parsed[i].value); + g_assert_true (_srt_input_device_uevent_field_equals (uevent, + uevent_parsed[i].key, + uevent_parsed[i].value)); + } + + g_assert_null (_srt_input_device_uevent_field (uevent, "NOPE")); +} + int main (int argc, char **argv) @@ -243,6 +302,8 @@ main (int argc, filter_gameoverlayrenderer, teardown); g_test_add ("/utils/str_is_integer", Fixture, NULL, setup, test_str_is_integer, teardown); + g_test_add ("/utils/uevent-field", Fixture, NULL, + setup, test_uevent_field, teardown); return g_test_run (); }