/* * Copyright © 2020 Collabora Ltd. * * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include <libglnx.h> #include <steam-runtime-tools/steam-runtime-tools.h> #include <linux/input.h> #include <glib.h> #include <glib/gstdio.h> #include <glib-object.h> #include "steam-runtime-tools/json-glib-backports-internal.h" #include "steam-runtime-tools/utils-internal.h" #include "mock-input-device.h" #include "test-utils.h" static const char *argv0; typedef struct { enum { MOCK, DIRECT, UDEV } type; } Config; static const Config defconfig = { .type = MOCK, }; static const Config direct_config = { .type = DIRECT, }; static const Config udev_config = { .type = UDEV, }; typedef struct { const Config *config; gchar *srcdir; gchar *builddir; GMainContext *monitor_context; GPtrArray *log; gboolean skipped; } Fixture; static void setup (Fixture *f, gconstpointer context) { const Config *config = context; GStatBuf sb; f->srcdir = g_strdup (g_getenv ("G_TEST_SRCDIR")); f->builddir = g_strdup (g_getenv ("G_TEST_BUILDDIR")); if (f->srcdir == NULL) f->srcdir = g_path_get_dirname (argv0); if (f->builddir == NULL) f->builddir = g_path_get_dirname (argv0); if (config == NULL) f->config = &defconfig; else f->config = config; if (f->config->type == DIRECT && g_stat ("/dev/input", &sb) != 0) { g_test_skip ("/dev/input not available"); f->skipped = TRUE; } f->log = g_ptr_array_new_with_free_func (g_free); } static void teardown (Fixture *f, gconstpointer context) { G_GNUC_UNUSED const Config *config = context; g_free (f->srcdir); g_free (f->builddir); g_clear_pointer (&f->log, g_ptr_array_unref); g_clear_pointer (&f->monitor_context, g_main_context_unref); } #define VENDOR_VALVE 0x28de #define PRODUCT_VALVE_STEAM_CONTROLLER 0x1142 static SrtSimpleInputDevice * load_json (Fixture *f, const char *filename) { g_autoptr(SrtSimpleInputDevice) simple = NULL; g_autoptr(JsonParser) parser = NULL; g_autoptr(GError) error = NULL; JsonNode *node; JsonObject *object; JsonObject *added; g_autofree gchar *path = NULL; parser = json_parser_new (); path = g_build_filename (f->srcdir, "input-monitor-outputs", filename, NULL); json_parser_load_from_file (parser, path, &error); g_assert_no_error (error); node = json_parser_get_root (parser); g_assert_nonnull (node); object = json_node_get_object (node); g_assert_nonnull (object); added = json_object_get_object_member (object, "added"); g_assert_nonnull (added); simple = _srt_simple_input_device_new_from_json (added); g_assert_nonnull (simple); return g_steal_pointer (&simple); } static void test_input_device_from_json_no_details (Fixture *f) { g_autoptr(SrtSimpleInputDevice) simple = load_json (f, "no-details.json"); SrtInputDevice *dev = SRT_INPUT_DEVICE (simple); struct { unsigned int bus_type; unsigned int vendor_id; unsigned int product_id; unsigned int version; } identity; unsigned long bits[LONGS_FOR_BITS (HIGHEST_EVENT_CODE)]; g_autofree gchar *uevent = NULL; g_assert_cmphex (srt_input_device_get_interface_flags (dev), ==, SRT_INPUT_DEVICE_INTERFACE_FLAGS_NONE); g_assert_cmphex (srt_input_device_get_type_flags (dev), ==, SRT_INPUT_DEVICE_TYPE_FLAGS_NONE); g_assert_cmpstr (srt_input_device_get_dev_node (dev), ==, NULL); g_assert_cmpstr (srt_input_device_get_subsystem (dev), ==, NULL); g_assert_cmpstr (srt_input_device_get_sys_path (dev), ==, NULL); g_assert_false (srt_input_device_get_identity (dev, NULL, NULL, NULL, NULL)); g_assert_false (srt_input_device_get_identity (dev, &identity.bus_type, &identity.vendor_id, &identity.product_id, &identity.version)); g_assert_cmpuint (srt_input_device_get_event_capabilities (dev, 0, bits, G_N_ELEMENTS (bits)), ==, 1); g_assert_cmphex (bits[0], ==, 0); g_assert_cmphex (bits[1], ==, 0); g_assert_cmpuint (srt_input_device_get_event_capabilities (dev, EV_ABS, bits, G_N_ELEMENTS (bits)), >=, 1); g_assert_cmphex (bits[0], ==, 0); g_assert_cmpuint (srt_input_device_get_event_capabilities (dev, EV_REL, bits, G_N_ELEMENTS (bits)), >=, 1); g_assert_cmphex (bits[0], ==, 0); g_assert_cmpuint (srt_input_device_get_event_capabilities (dev, EV_KEY, bits, G_N_ELEMENTS (bits)), >=, 1); g_assert_cmphex (bits[0], ==, 0); g_assert_cmpuint (srt_input_device_get_input_properties (dev, bits, G_N_ELEMENTS (bits)), ==, 1); g_assert_cmphex (bits[0], ==, 0); uevent = srt_input_device_dup_uevent (dev); g_assert_cmpstr (uevent, ==, NULL); g_assert_cmpstr (srt_input_device_get_hid_sys_path (dev), ==, NULL); g_assert_false (srt_input_device_get_hid_identity (dev, NULL, NULL, NULL, NULL, NULL, NULL)); uevent = srt_input_device_dup_hid_uevent (dev); g_assert_cmpstr (uevent, ==, NULL); g_assert_cmpstr (srt_input_device_get_input_sys_path (dev), ==, NULL); g_assert_false (srt_input_device_get_input_identity (dev, NULL, NULL, NULL, NULL, NULL, NULL, NULL)); uevent = srt_input_device_dup_input_uevent (dev); g_assert_cmpstr (uevent, ==, NULL); g_assert_cmpstr (srt_input_device_get_usb_device_sys_path (dev), ==, NULL); g_assert_false (srt_input_device_get_usb_device_identity (dev, NULL, NULL, NULL, NULL, NULL, NULL)); uevent = srt_input_device_dup_usb_device_uevent (dev); g_assert_cmpstr (uevent, ==, NULL); } static void test_input_device_from_json_odd (Fixture *f) { g_autoptr(SrtSimpleInputDevice) simple = load_json (f, "odd.json"); SrtInputDevice *dev = SRT_INPUT_DEVICE (simple); struct { unsigned int bus_type; unsigned int vendor_id; unsigned int product_id; const char *name; const char *phys; const char *uniq; } hid_identity; struct { unsigned int bus_type; unsigned int vendor_id; unsigned int product_id; unsigned int version; const char *name; const char *phys; const char *uniq; } input_identity; struct { unsigned int vendor_id; unsigned int product_id; unsigned int version; const char *manufacturer; const char *product; const char *serial; } usb_identity; unsigned long bits[LONGS_FOR_BITS (HIGHEST_EVENT_CODE)]; g_assert_cmphex (srt_input_device_get_interface_flags (dev), ==, SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID); g_assert_cmphex (srt_input_device_get_type_flags (dev), ==, SRT_INPUT_DEVICE_TYPE_FLAGS_NONE); g_assert_cmpstr (srt_input_device_get_dev_node (dev), ==, NULL); g_assert_cmpstr (srt_input_device_get_subsystem (dev), ==, NULL); g_assert_cmpstr (srt_input_device_get_sys_path (dev), ==, NULL); g_assert_false (srt_input_device_get_identity (dev, NULL, NULL, NULL, NULL)); g_assert_cmpuint (srt_input_device_get_event_capabilities (dev, 0, bits, G_N_ELEMENTS (bits)), ==, 1); g_assert_cmphex (bits[0], ==, 0); g_assert_cmpuint (srt_input_device_get_event_capabilities (dev, EV_ABS, bits, G_N_ELEMENTS (bits)), >=, 1); #if defined(__LP64__) g_assert_cmphex (bits[0], ==, 0x0807060504030201UL); #elif defined(__i386__) g_assert_cmphex (bits[0], ==, 0x04030201UL); g_assert_cmphex (bits[1], ==, 0x08070605UL); #endif g_assert_cmpuint (srt_input_device_get_event_capabilities (dev, EV_REL, bits, G_N_ELEMENTS (bits)), >=, 1); g_assert_cmphex (bits[0], ==, 0); g_assert_cmpuint (srt_input_device_get_event_capabilities (dev, EV_KEY, bits, G_N_ELEMENTS (bits)), >=, 1); g_assert_cmphex (bits[0], ==, 0); g_assert_cmpuint (srt_input_device_get_input_properties (dev, bits, G_N_ELEMENTS (bits)), ==, 1); #if defined(__LP64__) g_assert_cmphex (bits[0], ==, 0x2143658778563412UL); #elif defined(__i386__) g_assert_cmphex (bits[0], ==, 0x78563412UL); #endif g_assert_cmphex (bits[1], ==, 0); g_assert_true (srt_input_device_get_hid_identity (dev, NULL, NULL, NULL, NULL, NULL, NULL)); g_assert_true (srt_input_device_get_hid_identity (dev, &hid_identity.bus_type, &hid_identity.vendor_id, &hid_identity.product_id, &hid_identity.name, &hid_identity.phys, &hid_identity.uniq)); g_assert_cmphex (hid_identity.bus_type, ==, 0xfff1); g_assert_cmphex (hid_identity.vendor_id, ==, 0xfff1); g_assert_cmphex (hid_identity.product_id, ==, 0xfff1); g_assert_cmpstr (hid_identity.name, ==, "Acme Weird Device"); g_assert_cmpstr (hid_identity.phys, ==, NULL); g_assert_cmpstr (hid_identity.uniq, ==, "12345678"); g_assert_true (srt_input_device_get_input_identity (dev, NULL, NULL, NULL, NULL, NULL, NULL, NULL)); g_assert_true (srt_input_device_get_input_identity (dev, &input_identity.bus_type, &input_identity.vendor_id, &input_identity.product_id, &input_identity.version, &input_identity.name, &input_identity.phys, &input_identity.uniq)); g_assert_cmphex (input_identity.bus_type, ==, 0xfff2); g_assert_cmphex (input_identity.vendor_id, ==, 0xfff2); g_assert_cmphex (input_identity.product_id, ==, 0xfff2); g_assert_cmphex (input_identity.version, ==, 0); g_assert_cmpstr (input_identity.name, ==, NULL); g_assert_cmpstr (input_identity.phys, ==, NULL); g_assert_cmpstr (input_identity.uniq, ==, "1234-5678"); g_assert_cmpstr (srt_input_device_get_usb_device_sys_path (dev), ==, "/..."); g_assert_true (srt_input_device_get_usb_device_identity (dev, NULL, NULL, NULL, NULL, NULL, NULL)); g_assert_true (srt_input_device_get_usb_device_identity (dev, &usb_identity.vendor_id, &usb_identity.product_id, &usb_identity.version, &usb_identity.manufacturer, &usb_identity.product, &usb_identity.serial)); g_assert_cmphex (usb_identity.vendor_id, ==, 0xfff3); g_assert_cmphex (usb_identity.product_id, ==, 0xfff3); g_assert_cmphex (usb_identity.version, ==, 0); g_assert_cmpstr (usb_identity.manufacturer, ==, NULL); g_assert_cmpstr (usb_identity.product, ==, NULL); g_assert_cmpstr (usb_identity.serial, ==, "12:34:56:78"); } static void test_input_device_from_json_steam_controller (Fixture *f) { g_autoptr(SrtSimpleInputDevice) simple = load_json (f, "steam-controller.json"); SrtInputDevice *dev = SRT_INPUT_DEVICE (simple); struct { unsigned int bus_type; unsigned int vendor_id; unsigned int product_id; unsigned int version; } identity; struct { unsigned int bus_type; unsigned int vendor_id; unsigned int product_id; const char *name; const char *phys; const char *uniq; } hid_identity; struct { unsigned int bus_type; unsigned int vendor_id; unsigned int product_id; unsigned int version; const char *name; const char *phys; const char *uniq; } input_identity; struct { unsigned int vendor_id; unsigned int product_id; unsigned int version; const char *manufacturer; const char *product; const char *serial; } usb_identity; unsigned long bits[LONGS_FOR_BITS (HIGHEST_EVENT_CODE)]; g_autofree gchar *uevent = NULL; g_assert_cmphex (srt_input_device_get_interface_flags (dev), ==, SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT); g_assert_cmphex (srt_input_device_get_type_flags (dev), ==, (SRT_INPUT_DEVICE_TYPE_FLAGS_KEYBOARD | SRT_INPUT_DEVICE_TYPE_FLAGS_HAS_KEYS | SRT_INPUT_DEVICE_TYPE_FLAGS_MOUSE)); g_assert_cmpstr (srt_input_device_get_dev_node (dev), ==, "/dev/input/event20"); g_assert_cmpstr (srt_input_device_get_subsystem (dev), ==, "input"); g_assert_cmpstr (srt_input_device_get_sys_path (dev), ==, "/sys/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.1/1-1.1:1.0/0003:28DE:1142.00DD/input/input308/event20"); g_assert_true (srt_input_device_get_identity (dev, NULL, NULL, NULL, NULL)); g_assert_true (srt_input_device_get_identity (dev, &identity.bus_type, &identity.vendor_id, &identity.product_id, &identity.version)); /* Using magic numbers rather than a #define here so that it's easier * to validate against the JSON */ g_assert_cmphex (identity.bus_type, ==, 0x0003); g_assert_cmphex (identity.vendor_id, ==, 0x28de); g_assert_cmphex (identity.product_id, ==, 0x1142); g_assert_cmphex (identity.version, ==, 0x0111); g_assert_cmpuint (srt_input_device_get_event_capabilities (dev, 0, bits, G_N_ELEMENTS (bits)), ==, 1); g_assert_cmphex (bits[0], ==, 0x120017); g_assert_cmphex (bits[1], ==, 0); g_assert_cmpuint (srt_input_device_get_event_capabilities (dev, EV_ABS, bits, G_N_ELEMENTS (bits)), >=, 1); g_assert_cmphex (bits[0], ==, 0); g_assert_cmpuint (srt_input_device_get_event_capabilities (dev, EV_REL, bits, G_N_ELEMENTS (bits)), >=, 1); g_assert_cmphex (bits[0], ==, 0x0903); g_assert_cmpuint (srt_input_device_get_event_capabilities (dev, EV_KEY, bits, G_N_ELEMENTS (bits)), >=, 1); #if defined(__LP64__) g_assert_cmphex (bits[0], ==, 0xfffffffffffffffeUL); g_assert_cmphex (bits[1], ==, 0xe080ffdf01cfffffUL); g_assert_cmphex (bits[2], ==, 0); g_assert_cmphex (bits[3], ==, 0); g_assert_cmphex (bits[4], ==, 0x1f0000); g_assert_cmphex (bits[5], ==, 0); #elif defined(__i386__) g_assert_cmphex (bits[0], ==, 0xfffffffeUL); g_assert_cmphex (bits[1], ==, 0xffffffffUL); g_assert_cmphex (bits[2], ==, 0x01cfffffUL); g_assert_cmphex (bits[3], ==, 0xe080ffdfUL); g_assert_cmphex (bits[4], ==, 0); g_assert_cmphex (bits[5], ==, 0); g_assert_cmphex (bits[6], ==, 0); g_assert_cmphex (bits[7], ==, 0); g_assert_cmphex (bits[8], ==, 0x1f0000); g_assert_cmphex (bits[9], ==, 0); g_assert_cmphex (bits[10], ==, 0); g_assert_cmphex (bits[11], ==, 0); #endif g_assert_cmpuint (srt_input_device_get_input_properties (dev, bits, G_N_ELEMENTS (bits)), ==, 1); g_assert_cmphex (bits[0], ==, 0); uevent = srt_input_device_dup_uevent (dev); g_assert_cmpstr (uevent, ==, "MAJOR=13\n" "MINOR=84\n" "DEVNAME=input/event20\n"); g_clear_pointer (&uevent, g_free); g_assert_cmpstr (srt_input_device_get_hid_sys_path (dev), ==, "/sys/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.1/1-1.1:1.0/0003:28DE:1142.00DD"); g_assert_true (srt_input_device_get_hid_identity (dev, NULL, NULL, NULL, NULL, NULL, NULL)); g_assert_true (srt_input_device_get_hid_identity (dev, &hid_identity.bus_type, &hid_identity.vendor_id, &hid_identity.product_id, &hid_identity.name, &hid_identity.phys, &hid_identity.uniq)); g_assert_cmphex (hid_identity.bus_type, ==, 0x0003); g_assert_cmphex (hid_identity.vendor_id, ==, 0x28de); g_assert_cmphex (hid_identity.product_id, ==, 0x1142); g_assert_cmpstr (hid_identity.name, ==, "Valve Software Steam Controller"); g_assert_cmpstr (hid_identity.phys, ==, "usb-0000:00:14.0-1.1/input0"); g_assert_cmpstr (hid_identity.uniq, ==, ""); uevent = srt_input_device_dup_hid_uevent (dev); g_assert_cmpstr (uevent, ==, "DRIVER=hid-steam\n" "HID_ID=0003:000028DE:00001142\n" "HID_NAME=Valve Software Steam Controller\n" "HID_PHYS=usb-0000:00:14.0-1.1/input0\n" "HID_UNIQ=\n" "MODALIAS=hid:b0003g0001v000028DEp00001142\n"); g_clear_pointer (&uevent, g_free); g_assert_cmpstr (srt_input_device_get_input_sys_path (dev), ==, "/sys/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.1/1-1.1:1.0/0003:28DE:1142.00DD/input/input308"); g_assert_true (srt_input_device_get_input_identity (dev, NULL, NULL, NULL, NULL, NULL, NULL, NULL)); g_assert_true (srt_input_device_get_input_identity (dev, &input_identity.bus_type, &input_identity.vendor_id, &input_identity.product_id, &input_identity.version, &input_identity.name, &input_identity.phys, &input_identity.uniq)); g_assert_cmphex (input_identity.bus_type, ==, 0x0003); g_assert_cmphex (input_identity.vendor_id, ==, 0x28de); g_assert_cmphex (input_identity.product_id, ==, 0x1142); g_assert_cmphex (input_identity.version, ==, 0x0111); g_assert_cmpstr (input_identity.name, ==, "Valve Software Steam Controller"); g_assert_cmpstr (input_identity.phys, ==, "usb-0000:00:14.0-1.1/input0"); g_assert_cmpstr (input_identity.uniq, ==, NULL); uevent = srt_input_device_dup_input_uevent (dev); g_assert_cmpstr (uevent, ==, "PRODUCT=3/28de/1142/111\n" "NAME=\"Valve Software Steam Controller\"\n" "PHYS=\"usb-0000:00:14.0-1.1/input0\"\n" "UNIQ=\"\"\n" "PROP=0\n" "EV=120017\n" "KEY=1f0000 0 0 e080ffdf01cfffff fffffffffffffffe\n" "REL=903\n" "MSC=10\n" "LED=1f\n" "MODALIAS=input:b0003v28DEp1142e0111-e0,1,2,4,11,14,k77,7D,7E,7F,110,111,112,113,114,r0,1,8,B,am4,l0,1,2,3,4,sfw\n"); g_clear_pointer (&uevent, g_free); g_assert_cmpstr (srt_input_device_get_usb_device_sys_path (dev), ==, "/sys/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.1"); g_assert_true (srt_input_device_get_usb_device_identity (dev, NULL, NULL, NULL, NULL, NULL, NULL)); g_assert_true (srt_input_device_get_usb_device_identity (dev, &usb_identity.vendor_id, &usb_identity.product_id, &usb_identity.version, &usb_identity.manufacturer, &usb_identity.product, &usb_identity.serial)); g_assert_cmphex (usb_identity.vendor_id, ==, 0x28de); g_assert_cmphex (usb_identity.product_id, ==, 0x1142); g_assert_cmphex (usb_identity.version, ==, 0x0001); g_assert_cmpstr (usb_identity.manufacturer, ==, "Valve Software"); g_assert_cmpstr (usb_identity.product, ==, "Steam Controller"); g_assert_cmpstr (usb_identity.serial, ==, NULL); uevent = srt_input_device_dup_usb_device_uevent (dev); g_assert_cmpstr (uevent, ==, "MAJOR=189\n" "MINOR=66\n" "DEVNAME=bus/usb/001/067\n" "DEVTYPE=usb_device\n" "DRIVER=usb\n" "PRODUCT=28de/1142/1\n" "TYPE=0/0/0\n" "BUSNUM=001\n" "DEVNUM=067\n"); g_clear_pointer (&uevent, g_free); } static void test_input_device_from_json (Fixture *f, gconstpointer context) { test_input_device_from_json_no_details (f); test_input_device_from_json_odd (f); test_input_device_from_json_steam_controller (f); } typedef struct { const char *name; guint16 bus_type; guint16 vendor_id; guint16 product_id; guint16 version; guint8 ev[(EV_MAX + 1) / 8]; guint8 keys[(KEY_MAX + 1) / 8]; guint8 abs[(ABS_MAX + 1) / 8]; guint8 rel[(REL_MAX + 1) / 8]; guint8 ff[(FF_MAX + 1) / 8]; guint8 props[INPUT_PROP_MAX / 8]; SrtInputDeviceTypeFlags expected; } GuessTest; /* * Test-cases for guessing a device type from its capabilities. * * The bytes in ev, etc. are in little-endian byte order, the same as * the JSON output from input-monitor. Trailing zeroes can be omitted. */ #define ZEROx4 0, 0, 0, 0 #define ZEROx8 ZEROx4, ZEROx4 #define FFx4 0xff, 0xff, 0xff, 0xff #define FFx8 FFx4, FFx4 static const GuessTest guess_tests[] = { { .name = "Xbox 360 wired USB controller", .bus_type = 0x0003, .vendor_id = 0x045e, .product_id = 0x028e, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS, FF */ .ev = { 0x0b, 0x00, 0x20 }, /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */ .abs = { 0x3f, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* A, B, X, Y, TL, TR, SELECT, START, MODE, THUMBL, THUMBR */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7c, }, }, { .name = "X-Box One Elite", .bus_type = 0x0003, .vendor_id = 0x045e, .product_id = 0x02e3, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */ .abs = { 0x3f, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* A, B, X, Y, TL, TR, SELECT, START, MODE, THUMBL, THUMBR */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7c, }, }, { .name = "X-Box One S via Bluetooth", .bus_type = 0x0005, .vendor_id = 0x045e, .product_id = 0x02e0, .version = 0x1130, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */ .abs = { 0x3f, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* A, B, X, Y, TL, TR, SELECT, START, MODE, THUMBL, THUMBR */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7c, }, }, { .name = "X-Box One S wired", .bus_type = 0x0003, .vendor_id = 0x045e, .product_id = 0x02ea, .version = 0x0301, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */ .abs = { 0x3f, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* A, B, X, Y, TL, TR, SELECT, START, MODE, THUMBL, THUMBR */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7c, }, }, { .name = "DualSense (PS5) - gamepad", .bus_type = 0x0003, .vendor_id = 0x054c, .product_id = 0x0ce6, .version = 0x111, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */ .abs = { 0x3f, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* ABC, XYZ, TL, TR, TL2, TR2, select, start, mode, thumbl, * thumbr */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xff, 0x7f, }, }, { .name = "DualShock 4 - gamepad", .bus_type = 0x0003, .vendor_id = 0x054c, .product_id = 0x09cc, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS, MSC, FF */ /* Some versions only have 0x0b, SYN, KEY, ABS, like the * Bluetooth example below */ .ev = { 0x1b, 0x00, 0x20 }, /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */ .abs = { 0x3f, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* A, B, X, Y, TL, TR, TL2, TR2, SELECT, START, MODE, * THUMBL, THUMBR */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7f, }, }, { .name = "DualShock 4 - gamepad via Bluetooth", .bus_type = 0x0005, .vendor_id = 0x054c, .product_id = 0x09cc, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */ .abs = { 0x3f, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* A, B, X, Y, TL, TR, TL2, TR2, SELECT, START, MODE, * THUMBL, THUMBR */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7f, }, }, { .name = "DualShock 4 - touchpad", .bus_type = 0x0003, .vendor_id = 0x054c, .product_id = 0x09cc, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_TOUCHPAD, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, multitouch */ .abs = { 0x03, 0x00, 0x00, 0x00, 0x00, 0x80, 0x60, 0x02 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* Left mouse button */ /* 0x100 */ 0x00, 0x00, 0x01, 0x00, ZEROx4, /* BTN_TOOL_FINGER and some multitouch stuff */ /* 0x140 */ 0x20, 0x24, 0x00, 0x00 }, /* POINTER, BUTTONPAD */ .props = { 0x05 }, }, { .name = "DualShock 4 - accelerometer", .bus_type = 0x0003, .vendor_id = 0x054c, .product_id = 0x09cc, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_ACCELEROMETER, /* SYN, ABS, MSC */ .ev = { 0x19 }, /* X, Y, Z, RX, RY, RZ */ .abs = { 0x3f }, /* ACCELEROMETER */ .props = { 0x40 }, }, { .name = "DualShock 4 via USB dongle", .bus_type = 0x0003, .vendor_id = 0x054c, .product_id = 0x0ba0, .version = 0x8111, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, ABS, KEY */ .ev = { 0x0b }, /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */ .abs = { 0x3f, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* A, B, X, Y, TL, TR, TL2, TR2, SELECT, START, MODE, * THUMBL, THUMBR */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7f, }, }, { .name = "DualShock 3 - gamepad", .bus_type = 0x0003, .vendor_id = 0x054c, .product_id = 0x0268, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS, MSC, FF */ .ev = { 0x1b, 0x00, 0x20 }, /* X, Y, Z, RX, RY, RZ */ .abs = { 0x3f }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* A, B, X, Y, TL, TR, TL2, TR2, SELECT, START, MODE, * THUMBL, THUMBR */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7f, /* 0x140 */ ZEROx8, /* 0x180 */ ZEROx8, /* 0x1c0 */ ZEROx8, /* Digital dpad */ /* 0x200 */ ZEROx4, 0x0f, 0x00, 0x00, 0x00, }, }, { .name = "DualShock 3 - accelerometer", .bus_type = 0x0003, .vendor_id = 0x054c, .product_id = 0x0268, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_ACCELEROMETER, /* SYN, ABS */ .ev = { 0x09 }, /* X, Y, Z */ .abs = { 0x07 }, /* ACCELEROMETER */ .props = { 0x40 }, }, { .name = "Steam Controller - gamepad", .bus_type = 0x0003, .vendor_id = 0x28de, .product_id = 0x1142, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, RX, RY, HAT0X, HAT0Y, HAT2X, HAT2Y */ .abs = { 0x1b, 0x00, 0x33 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* A, B, X, Y, TL, TR, TL2, TR2, SELECT, START, MODE, * THUMBL, THUMBR, joystick THUMB, joystick THUMB2 */ /* 0x100 */ ZEROx4, 0x06, 0x00, 0xdb, 0x7f, /* GEAR_DOWN, GEAR_UP */ /* 0x140 */ 0x00, 0x00, 0x03, 0x00, ZEROx4, /* 0x180 */ ZEROx8, /* 0x1c0 */ ZEROx8, /* Digital dpad */ /* 0x200 */ ZEROx4, 0x0f, 0x00, 0x00, 0x00, }, }, { /* Present to support lizard mode, even if no Steam Controller * is connected */ .name = "Steam Controller - dongle", .bus_type = 0x0003, .vendor_id = 0x28de, .product_id = 0x1142, .expected = (SRT_INPUT_DEVICE_TYPE_FLAGS_KEYBOARD | SRT_INPUT_DEVICE_TYPE_FLAGS_HAS_KEYS | SRT_INPUT_DEVICE_TYPE_FLAGS_MOUSE), /* SYN, KEY, REL, MSC, LED, REP */ .ev = { 0x17, 0x00, 0x12 }, /* X, Y, mouse wheel, high-res mouse wheel */ .rel = { 0x03, 0x09 }, .keys = { /* 0x00 */ 0xfe, 0xff, 0xff, 0xff, FFx4, /* 0x40 */ 0xff, 0xff, 0xcf, 0x01, 0xdf, 0xff, 0x80, 0xe0, /* 0x80 */ ZEROx8, /* 0xc0 */ ZEROx8, /* 0x100 */ 0x00, 0x00, 0x1f, 0x00, ZEROx4, }, }, { .name = "Guitar Hero for PS3", .bus_type = 0x0003, .vendor_id = 0x12ba, .product_id = 0x0100, .version = 0x0110, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, Z, RZ, HAT0X, HAT0Y */ .abs = { 0x27, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* ABC, XYZ, TL, TR, TL2, TR2, SELECT, START, MODE */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xff, 0x1f, }, }, { .name = "G27 Racing Wheel, 0003:046d:c29b v0111", .bus_type = 0x0003, .vendor_id = 0x046d, .product_id = 0xc29b, .version = 0x0111, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, Z, RZ, HAT0X, HAT0Y */ .abs = { 0x27, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* 16 buttons: TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE, BASE, * BASE2..BASE6, unregistered event codes 0x12c-0x12e, DEAD */ /* 0x100 */ ZEROx4, 0xff, 0xff, 0x00, 0x00, /* 0x140 */ ZEROx8, /* 0x180 */ ZEROx8, /* 0x1c0 */ ZEROx8, /* 0x200 */ ZEROx8, /* 0x240 */ ZEROx8, /* 0x280 */ ZEROx8, /* TRIGGER_HAPPY1..TRIGGER_HAPPY7 */ /* 0x2c0 */ 0x7f, }, }, { .name = "Logitech Driving Force, 0003:046d:c294 v0100", .bus_type = 0x0003, .vendor_id = 0x046d, .product_id = 0xc294, .version = 0x0100, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, RZ, HAT0X, HAT0Y */ .abs = { 0x23, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* 12 buttons: TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE, BASE, * BASE2..BASE6 */ /* 0x100 */ ZEROx4, 0xff, 0x0f, 0x00, 0x00, }, }, { .name = "Logitech Dual Action", .bus_type = 0x0003, .vendor_id = 0x046d, .product_id = 0xc216, .version = 0x0110, /* Logitech RumblePad 2 USB, 0003:046d:c218 v0110, is the same * except for having force feedback, which we don't use in our * heuristic */ /* Jess Tech GGE909 PC Recoil Pad, 0003:0f30:010b v0110, is the same */ /* 8BitDo SNES30, 0003:2dc8:ab20 v0110, is the same */ .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, Z, RZ, HAT0X, HAT0Y */ .abs = { 0x27, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* 12 buttons: TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE, BASE, * BASE2..BASE6 */ /* 0x100 */ ZEROx4, 0xff, 0x0f, 0x00, 0x00, }, }, { .name = "Saitek ST290 Pro flight stick", .bus_type = 0x0003, .vendor_id = 0x06a3, .product_id = 0x0160, /* 0x0460 seems to be the same */ .version = 0x0100, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS, MSC */ .ev = { 0x1b }, /* X, Y, Z, RZ, HAT0X, HAT0Y */ .abs = { 0x27, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE */ /* 0x100 */ ZEROx4, 0x3f, 0x00, 0x00, 0x00, }, }, { .name = "Saitek X52 Pro Flight Control System", .bus_type = 0x0003, .vendor_id = 0x06a3, .product_id = 0x0762, .version = 0x0111, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, .ev = { 0x0b }, /* XYZ, RXYZ, throttle, hat0, MISC, unregistered event code 0x29 */ .abs = { 0x7f, 0x00, 0x03, 0x00, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* 16 buttons: TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE, BASE, * BASE2..BASE6, unregistered event codes 0x12c-0x12e, DEAD */ /* 0x100 */ ZEROx4, 0xff, 0xff, 0x00, 0x00, /* 0x140 */ ZEROx8, /* 0x180 */ ZEROx8, /* 0x1c0 */ ZEROx8, /* 0x200 */ ZEROx8, /* 0x240 */ ZEROx8, /* 0x280 */ ZEROx8, /* TRIGGER_HAPPY1..TRIGGER_HAPPY23 */ /* 0x2c0 */ 0xff, 0xff, 0x7f, }, }, { .name = "Logitech Extreme 3D", .bus_type = 0x0003, .vendor_id = 0x046d, .product_id = 0xc215, .version = 0x0110, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS, MSC */ .ev = { 0x0b }, /* X, Y, RZ, throttle, hat 0 */ .abs = { 0x63, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* 12 buttons: TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE, BASE, * BASE2..BASE6 */ /* 0x100 */ ZEROx4, 0xff, 0x0f, 0x00, 0x00, }, }, { .name = "Hori Real Arcade Pro VX-SA", .bus_type = 0x0003, .vendor_id = 0x24c6, .product_id = 0x5501, .version = 0x0533, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, Z, RX, RY, RZ, hat 0 */ .abs = { 0x3f, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* A, B, X, Y, TL, TR, SELECT, START, MODE, THUMBL, THUMBR */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7c, }, }, { .name = "Switch Pro Controller via Bluetooth", .bus_type = 0x0005, .vendor_id = 0x057e, .product_id = 0x2009, .version = 0x0001, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, RX, RY, hat 0 */ .abs = { 0x1b, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* 16 buttons: TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE, BASE, * BASE2..BASE6, unregistered event codes 0x12c-0x12e, DEAD */ /* 0x100 */ ZEROx4, 0xff, 0xff, 0x00, 0x00, /* 0x140 */ ZEROx8, /* 0x180 */ ZEROx8, /* 0x1c0 */ ZEROx8, /* 0x200 */ ZEROx8, /* 0x240 */ ZEROx8, /* 0x280 */ ZEROx8, /* TRIGGER_HAPPY1..TRIGGER_HAPPY2 */ /* 0x2c0 */ 0x03, }, }, { .name = "Switch Pro Controller via USB", .bus_type = 0x0003, .vendor_id = 0x057e, .product_id = 0x2009, .version = 0x0111, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, Z, RZ, HAT0X, HAT0Y */ .abs = { 0x27, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, }, }, { .name = "Thrustmaster Racing Wheel FFB", /* Mad Catz FightStick TE S+ PS4, 0003:0738:8384:0111 v0111 * (aka Street Fighter V Arcade FightStick TES+) * is functionally the same */ .bus_type = 0x0003, .vendor_id = 0x044f, .product_id = 0xb66d, .version = 0x0110, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, .ev = { 0x0b }, /* XYZ, RXYZ, hat 0 */ .abs = { 0x3f, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* ABC, XYZ, TL, TR, TL2, TR2, SELECT, START, MODE, * THUMBL */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xff, 0x3f, }, }, { .name = "Thrustmaster T.Flight Hotas X", .bus_type = 0x0003, .vendor_id = 0x044f, .product_id = 0xb108, .version = 0x0100, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, .ev = { 0x0b }, /* XYZ, RZ, throttle, hat 0 */ .abs = { 0x67, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* trigger, thumb, thumb2, top, top2, pinkie, base, * base2..base6 */ /* 0x100 */ ZEROx4, 0xff, 0x0f }, }, { .name = "8BitDo N30 Pro 2", .bus_type = 0x0003, .vendor_id = 0x2dc8, .product_id = 0x9015, .version = 0x0111, /* 8BitDo NES30 Pro, 0003:2dc8:9001 v0111, is the same */ .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, .ev = { 0x0b }, /* XYZ, RZ, gas, brake, hat0 */ .abs = { 0x27, 0x06, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* ABC, XYZ, TL, TR, TL2, TR2, select, start, mode, thumbl, * thumbr */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xff, 0x7f, }, }, { .name = "Retro Power SNES-style controller, 0003:0079:0011 v0110", .bus_type = 0x0003, .vendor_id = 0x0079, .product_id = 0x0011, .version = 0x0110, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, .ev = { 0x0b }, /* X, Y */ .abs = { 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* trigger, thumb, thumb2, top, top2, pinkie, base, * base2..base4 */ /* 0x100 */ ZEROx4, 0xff, 0x03, 0x00, 0x00, }, }, { .name = "Wiimote - buttons", .bus_type = 0x0005, .vendor_id = 0x057e, .product_id = 0x0306, .version = 0x8600, /* This one is a bit weird because some of the buttons are mapped * to the arrow, page up and page down keys, so it's a joystick * with a subset of a keyboard attached */ .expected = (SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK | SRT_INPUT_DEVICE_TYPE_FLAGS_HAS_KEYS), /* SYN, KEY */ .ev = { 0x03 }, .keys = { /* 0x00 */ ZEROx8, /* left, right, up down */ /* 0x40 */ ZEROx4, 0x80, 0x16, 0x00, 0x00, /* 0x80 */ ZEROx8, /* 0xc0 */ ZEROx8, /* BTN_1, BTN_2, BTN_A, BTN_B, BTN_MODE */ /* 0x100 */ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, /* 0x140 */ ZEROx8, /* next (page down), previous (page up) */ /* 0x180 */ 0x00, 0x00, 0x80, 0x10, ZEROx4, }, }, { .name = "Wiimote - Motion Plus or accelerometer", .bus_type = 0x0005, .vendor_id = 0x057e, .product_id = 0x0306, .version = 0x8600, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_ACCELEROMETER, /* SYN, ABS */ .ev = { 0x09 }, /* RX, RY, RZ */ .abs = { 0x38 }, }, { .name = "Wiimote - IR positioning", .bus_type = 0x0005, .vendor_id = 0x057e, .product_id = 0x0306, .version = 0x8600, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, ABS */ .ev = { 0x09 }, /* HAT0 to HAT3 */ .abs = { 0x00, 0x1f }, }, { .name = "Wiimote - Nunchuck", .bus_type = 0x0005, .vendor_id = 0x057e, .product_id = 0x0306, .version = 0x8600, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* RX, RY, RZ, hat 0 */ .abs = { 0x38, 0x00, 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* C and Z buttons */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0x24, 0x00, }, }, { /* Flags guessed from kernel source code */ .name = "Wiimote - Classic Controller", .expected = (SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK | SRT_INPUT_DEVICE_TYPE_FLAGS_HAS_KEYS), /* SYN, KEY, ABS */ .ev = { 0x0b }, /* Hat 1-3 */ .abs = { 0x00, 0x1c }, .keys = { /* 0x00 */ ZEROx8, /* left, right, up down */ /* 0x40 */ ZEROx4, 0x80, 0x16, 0x00, 0x00, /* 0x80 */ ZEROx8, /* 0xc0 */ ZEROx8, /* A, B, X, Y, MODE, TL, TL2, TR, TR2 */ /* 0x100 */ ZEROx4, 0x00, 0x13, 0xdb, 0x10, /* 0x140 */ ZEROx8, /* next, previous */ /* 0x180 */ 0x00, 0x00, 0x80, 0x10, ZEROx4, }, }, { /* Flags guessed from kernel source code */ .name = "Wiimote - Balance Board", .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* Hat 0-1 */ .abs = { 0x00, 0x0f }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* A */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0x01, 0x00, }, }, { /* Flags guessed from kernel source code */ .name = "Wiimote - Wii U Pro Controller", .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, RX, RY */ .abs = { 0x1b }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* A, B, X, Y, TL, TR, TL2, TR2, SELECT, START, MODE, * THUMBL, THUMBR */ /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7f, /* 0x140 */ ZEROx8, /* 0x180 */ ZEROx8, /* 0x1c0 */ ZEROx8, /* Digital dpad */ /* 0x200 */ ZEROx4, 0x0f, 0x00, 0x00, 0x00, }, }, { .name = "Synaptics TM3381-002 (Thinkpad X280 trackpad)", .bus_type = 0x001d, /* BUS_RMI */ .vendor_id = 0x06cb, .product_id = 0x0000, .version = 0x0000, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_TOUCHPAD, /* SYN, KEY, ABS */ .ev = { 0x0b }, /* X, Y, pressure, multitouch */ .abs = { 0x03, 0x00, 0x00, 0x01, 0x00, 0x80, 0xf3, 0x06 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* Left mouse button */ /* 0x100 */ 0x00, 0x00, 0x01, 0x00, ZEROx4, /* BTN_TOOL_FINGER and some multitouch gestures */ /* 0x140 */ 0x20, 0xe5 }, /* POINTER, BUTTONPAD */ .props = { 0x05 }, }, { .name = "DELL08AF:00 (Dell XPS laptop touchpad)", .bus_type = 0x18, .vendor_id = 0x6cb, .product_id = 0x76af, .version = 0x100, .ev = { 0x0b }, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_TOUCHPAD, /* X, Y, multitouch */ .abs = { 0x03, 0x00, 0x00, 0x00, 0x00, 0x80, 0xe0, 0x02 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* Left mouse button */ /* 0x100 */ 0x00, 0x00, 0x01, 0x00, ZEROx4, /* BTN_TOOL_FINGER and some multitouch gestures */ /* 0x140 */ 0x20, 0xe5 }, /* POINTER, BUTTONPAD */ .props = { 0x05 }, }, { .name = "TPPS/2 Elan TrackPoint (Thinkpad X280)", .bus_type = 0x0011, /* BUS_I8042 */ .vendor_id = 0x0002, .product_id = 0x000a, .version = 0x0000, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_POINTING_STICK, /* SYN, KEY, REL */ .ev = { 0x07 }, /* X, Y */ .rel = { 0x03 }, .keys = { /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8, /* Left, middle, right mouse buttons */ /* 0x100 */ 0x00, 0x00, 0x07, }, /* POINTER, POINTING_STICK */ .props = { 0x21 }, }, { .name = "Thinkpad ACPI buttons", .expected = (SRT_INPUT_DEVICE_TYPE_FLAGS_HAS_KEYS | SRT_INPUT_DEVICE_TYPE_FLAGS_SWITCH), /* SYN, KEY, MSC, SW */ .ev = { 0x33 }, .keys = { /* 0x00 */ ZEROx8, /* 0x40 */ ZEROx4, 0x00, 0x00, 0x0e, 0x01, /* 0x80 */ 0x00, 0x50, 0x11, 0x51, 0x00, 0x28, 0x00, 0xc0, /* 0xc0 */ 0x04, 0x20, 0x10, 0x02, 0x1b, 0x70, 0x01, 0x00, /* 0x100 */ ZEROx8, /* 0x140 */ ZEROx4, 0x00, 0x00, 0x50, 0x00, /* 0x180 */ ZEROx8, /* 0x1c0 */ 0x00, 0x00, 0x04, 0x18, ZEROx4, /* 0x200 */ ZEROx8, /* 0x240 */ 0x40, 0x00, 0x01, 0x00, ZEROx4, }, }, { .name = "PC speaker", .bus_type = 0x0010, /* BUS_ISA */ .vendor_id = 0x001f, .product_id = 0x0001, .version = 0x0100, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_NONE, /* SYN, SND */ .ev = { 0x01, 0x00, 0x04 }, }, { .name = "ALSA headphone detection, etc.", .bus_type = 0x0000, .vendor_id = 0x0000, .product_id = 0x0000, .version = 0x0000, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_SWITCH, /* SYN, SW */ .ev = { 0x21 }, }, { /* Assumed to be a reasonably typical i8042 (PC AT) keyboard */ .name = "Thinkpad T520 and X280 keyboards", .bus_type = 0x0011, /* BUS_I8042 */ .vendor_id = 0x0001, .product_id = 0x0001, .version = 0xab54, .expected = (SRT_INPUT_DEVICE_TYPE_FLAGS_KEYBOARD | SRT_INPUT_DEVICE_TYPE_FLAGS_HAS_KEYS), /* SYN, KEY, MSC, LED, REP */ .ev = { 0x13, 0x00, 0x12 }, .keys = { /* 0x00 */ 0xfe, 0xff, 0xff, 0xff, FFx4, /* 0x40 */ 0xff, 0xff, 0xef, 0xff, 0xdf, 0xff, 0xff, 0xfe, /* 0x80 */ 0x01, 0xd0, 0x00, 0xf8, 0x78, 0x30, 0x80, 0x03, /* 0xc0 */ 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, }, }, { .name = "Thinkpad X280 sleep button", .bus_type = 0x0019, /* BUS_HOST */ .vendor_id = 0x0000, .product_id = 0x0003, .version = 0x0000, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_HAS_KEYS, /* SYN, KEY */ .ev = { 0x03 }, .keys = { /* 0x00 */ ZEROx8, /* 0x40 */ ZEROx8, /* KEY_SLEEP */ /* 0x80 */ 0x00, 0x40, }, }, { .name = "Thinkpad X280 lid switch", .bus_type = 0x0019, /* BUS_HOST */ .vendor_id = 0x0000, .product_id = 0x0005, .version = 0x0000, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_SWITCH, /* SYN, SW */ .ev = { 0x21 }, }, { .name = "Thinkpad X280 power button", .bus_type = 0x0019, /* BUS_HOST */ .vendor_id = 0x0000, .product_id = 0x0001, .version = 0x0000, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_HAS_KEYS, /* SYN, KEY */ .ev = { 0x03 }, .keys = { /* 0x00 */ ZEROx8, /* KEY_POWER */ /* 0x40 */ ZEROx4, 0x00, 0x00, 0x10, 0x00, }, }, { .name = "Thinkpad X280 video bus", .bus_type = 0x0019, /* BUS_HOST */ .vendor_id = 0x0000, .product_id = 0x0006, .version = 0x0000, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_HAS_KEYS, /* SYN, KEY */ .ev = { 0x03 }, .keys = { /* 0x00 */ ZEROx8, /* 0x40 */ ZEROx8, /* 0x80 */ ZEROx8, /* brightness control, video mode, display off */ /* 0xc0 */ ZEROx4, 0x0b, 0x00, 0x3e, 0x00, }, }, { .name = "Thinkpad X280 extra buttons", .bus_type = 0x0019, /* BUS_HOST */ .vendor_id = 0x17aa, .product_id = 0x5054, .version = 0x4101, .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_HAS_KEYS, /* SYN, KEY */ .ev = { 0x03 }, .keys = { /* 0x00 */ ZEROx8, /* 0x40 */ ZEROx4, 0x00, 0x00, 0x0e, 0x01, /* 0x80 */ 0x00, 0x50, 0x11, 0x51, 0x00, 0x28, 0x00, 0xc0, /* 0xc0 */ 0x04, 0x20, 0x10, 0x02, 0x1b, 0x70, 0x01, 0x00, /* 0x100 */ ZEROx8, /* 0x140 */ ZEROx4, 0x00, 0x00, 0x50, 0x00, /* 0x180 */ ZEROx8, /* 0x1c0 */ 0x00, 0x00, 0x04, 0x18, ZEROx4, /* 0x200 */ ZEROx8, /* 0x240 */ 0x40, 0x00, 0x01, 0x00, ZEROx4, }, }, { .name = "Thinkpad USB keyboard with Trackpoint - keyboard", .bus_type = 0x0003, .vendor_id = 0x17ef, .product_id = 0x6009, .expected = (SRT_INPUT_DEVICE_TYPE_FLAGS_KEYBOARD | SRT_INPUT_DEVICE_TYPE_FLAGS_HAS_KEYS), /* SYN, KEY, MSC, LED, REP */ .ev = { 0x13, 0x00, 0x12 }, .keys = { /* 0x00 */ 0xfe, 0xff, 0xff, 0xff, FFx4, /* 0x40 */ 0xff, 0xff, 0xef, 0xff, 0xdf, 0xff, 0xbe, 0xfe, /* 0x80 */ 0xff, 0x57, 0x40, 0xc1, 0x7a, 0x20, 0x9f, 0xff, /* 0xc0 */ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, }, }, { .name = "Thinkpad USB keyboard with Trackpoint - Trackpoint", .bus_type = 0x0003, .vendor_id = 0x17ef, .product_id = 0x6009, /* For some reason the special keys like mute and wlan toggle * show up here instead of, or in addition to, as part of * the keyboard - so we report this as having keys too. */ .expected = (SRT_INPUT_DEVICE_TYPE_FLAGS_MOUSE | SRT_INPUT_DEVICE_TYPE_FLAGS_HAS_KEYS), /* SYN, KEY, REL, MSC, LED */ .ev = { 0x17, 0x00, 0x02 }, /* X, Y */ .rel = { 0x03 }, .keys = { /* 0x00 */ ZEROx8, /* 0x40 */ ZEROx4, 0x00, 0x00, 0x1e, 0x00, /* 0x80 */ 0x00, 0xcc, 0x11, 0x01, 0x78, 0x40, 0x00, 0xc0, /* 0xc0 */ 0x00, 0x20, 0x10, 0x00, 0x0b, 0x50, 0x00, 0x00, /* Mouse buttons: left, right, middle, "task" */ /* 0x100 */ 0x00, 0x00, 0x87, 0x68, ZEROx4, /* 0x140 */ ZEROx4, 0x00, 0x00, 0x10, 0x00, /* 0x180 */ ZEROx4, 0x00, 0x00, 0x40, 0x00, }, }, { .name = "No information", .expected = SRT_INPUT_DEVICE_TYPE_FLAGS_NONE, } }; static void test_input_device_guess (Fixture *f, gconstpointer context) { size_t i; for (i = 0; i < G_N_ELEMENTS (guess_tests); i++) { const GuessTest *t = &guess_tests[i]; SrtEvdevCapabilities caps; size_t j; SrtInputDeviceTypeFlags actual; g_test_message ("%s", t->name); /* The real SrtEvdevCapabilities rounds the sizes up to the next * 32- or 64-bit boundary, and GuessTest rounds them up to the next * 8-bit boundary, so GuessTest is the same size or smaller. */ g_assert_cmpuint (sizeof (caps.ev), >=, sizeof (t->ev)); g_assert_cmpuint (sizeof (caps.keys), >=, sizeof (t->keys)); g_assert_cmpuint (sizeof (caps.abs), >=, sizeof (t->abs)); g_assert_cmpuint (sizeof (caps.rel), >=, sizeof (t->rel)); g_assert_cmpuint (sizeof (caps.ff), >=, sizeof (t->ff)); g_assert_cmpuint (sizeof (caps.props), >=, sizeof (t->props)); memset (&caps, '\0', sizeof (caps)); memcpy (caps.ev, t->ev, sizeof (t->ev)); memcpy (caps.keys, t->keys, sizeof (t->keys)); memcpy (caps.abs, t->abs, sizeof (t->abs)); memcpy (caps.rel, t->rel, sizeof (t->rel)); memcpy (caps.ff, t->ff, sizeof (t->ff)); memcpy (caps.props, t->props, sizeof (t->props)); for (j = 0; j < G_N_ELEMENTS (caps.ev); j++) caps.ev[j] = GULONG_FROM_LE (caps.ev[j]); for (j = 0; j < G_N_ELEMENTS (caps.keys); j++) caps.keys[j] = GULONG_FROM_LE (caps.keys[j]); for (j = 0; j < G_N_ELEMENTS (caps.abs); j++) caps.abs[j] = GULONG_FROM_LE (caps.abs[j]); for (j = 0; j < G_N_ELEMENTS (caps.rel); j++) caps.rel[j] = GULONG_FROM_LE (caps.rel[j]); for (j = 0; j < G_N_ELEMENTS (caps.ff); j++) caps.ff[j] = GULONG_FROM_LE (caps.ff[j]); for (j = 0; j < G_N_ELEMENTS (caps.props); j++) caps.props[j] = GULONG_FROM_LE (caps.props[j]); _srt_evdev_capabilities_dump (&caps); /* Now we can check whether our guess works */ actual = _srt_evdev_capabilities_guess_type (&caps); g_assert_cmphex (actual, ==, t->expected); } } static void test_input_device_identity_from_hid_uevent (Fixture *f, gconstpointer context) { static const char text[] = "DRIVER=hid-steam\n" "HID_ID=0003:000028DE:00001142\n" "HID_NAME=Valve Software Steam Controller\n" "HID_PHYS=usb-0000:00:14.0-1.1/input0\n" "HID_UNIQ=serialnumber\n" "MODALIAS=hid:b0003g0001v000028DEp00001142\n"; guint32 bus_type, vendor_id, product_id; g_autofree gchar *name = NULL; g_autofree gchar *phys = NULL; g_autofree gchar *uniq = NULL; g_assert_true (_srt_get_identity_from_hid_uevent (text, &bus_type, &vendor_id, &product_id, &name, &phys, &uniq)); g_assert_cmphex (bus_type, ==, 0x0003); g_assert_cmphex (vendor_id, ==, 0x28de); g_assert_cmphex (product_id, ==, 0x1142); g_assert_cmpstr (name, ==, "Valve Software Steam Controller"); g_assert_cmpstr (phys, ==, "usb-0000:00:14.0-1.1/input0"); /* Real Steam Controllers don't expose a serial number here, but it's * a better test if we include one */ g_assert_cmpstr (uniq, ==, "serialnumber"); } #define VENDOR_SONY 0x0268 #define PRODUCT_SONY_PS3 0x054c /* These aren't in the real vendor/product IDs, but we add them here * to make the test able to distinguish. They look a bit like HID, * EVDE(v) and USB, if you squint. */ #define HID_MARKER 0x41D00000 #define EVDEV_MARKER 0xE7DE0000 #define USB_MARKER 0x05B00000 /* The test below assumes EV_MAX doesn't increase its value */ G_STATIC_ASSERT (EV_MAX <= 31); /* Same for INPUT_PROP_MAX */ G_STATIC_ASSERT (INPUT_PROP_MAX <= 31); static void test_input_device_usb (Fixture *f, gconstpointer context) { g_autoptr(MockInputDevice) mock_device = mock_input_device_new (); SrtInputDevice *device = SRT_INPUT_DEVICE (mock_device); SrtSimpleInputDevice *simple = SRT_SIMPLE_INPUT_DEVICE (mock_device); SrtInputDeviceInterface *iface = SRT_INPUT_DEVICE_GET_INTERFACE (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; struct { unsigned int bus_type; unsigned int vendor_id; unsigned int product_id; unsigned int version; } identity = { 1, 1, 1, 1 }; struct { unsigned int bus_type; unsigned int vendor_id; unsigned int product_id; const char *name; const char *phys; const char *uniq; } hid_identity = { 1, 1, 1, "x", "x", "x" }; struct { unsigned int bus_type; unsigned int vendor_id; unsigned int product_id; unsigned int version; const char *name; const char *phys; const char *uniq; } input_identity = { 1, 1, 1, 1, "x", "x", "x" }; struct { unsigned int vendor_id; unsigned int product_id; unsigned int version; const char *manufacturer; const char *product; const char *serial; } usb_identity = { 1, 1, 1, "x", "x", "x" }; unsigned long evbits; /* Initialize the first two to nonzero to check that they get zeroed */ unsigned long bits[LONGS_FOR_BITS (HIGHEST_EVENT_CODE)] = { 0xa, 0xb }; gsize i; simple->iface_flags = (SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE); simple->dev_node = g_strdup ("/dev/input/event0"); simple->sys_path = g_strdup ("/sys/devices/mock/usb/hid/input/input0/event0"); simple->subsystem = g_strdup ("input"); simple->udev_properties = g_new0 (gchar *, 2); simple->udev_properties[0] = g_strdup ("ID_INPUT_JOYSTICK=1"); simple->udev_properties[1] = NULL; simple->uevent = g_strdup ("A=a\nB=b\n"); /* This is a semi-realistic PS3 controller. */ simple->type_flags = SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK; simple->bus_type = BUS_USB; simple->vendor_id = VENDOR_SONY; simple->product_id = PRODUCT_SONY_PS3; simple->version = 0x8111; /* We don't set all the bits, just enough to be vaguely realistic */ set_bit (EV_KEY, simple->evdev_caps.ev); set_bit (EV_ABS, simple->evdev_caps.ev); set_bit (BTN_A, simple->evdev_caps.keys); set_bit (BTN_B, simple->evdev_caps.keys); set_bit (BTN_TL, simple->evdev_caps.keys); set_bit (BTN_TR, simple->evdev_caps.keys); set_bit (ABS_X, simple->evdev_caps.abs); set_bit (ABS_Y, simple->evdev_caps.abs); set_bit (ABS_RX, simple->evdev_caps.abs); set_bit (ABS_RY, simple->evdev_caps.abs); g_debug ("Mock device capabilities:"); _srt_evdev_capabilities_dump (&simple->evdev_caps); simple->hid_ancestor.sys_path = g_strdup ("/sys/devices/mock/usb/hid"); simple->hid_ancestor.uevent = g_strdup ("HID=yes\n"); /* The part in square brackets isn't present on the real device, but * makes this test more thorough by letting us distinguish. */ simple->hid_ancestor.name = g_strdup ("Sony PLAYSTATION(R)3 Controller [hid]"); simple->hid_ancestor.phys = g_strdup ("usb-0000:00:14.0-1/input0"); simple->hid_ancestor.uniq = g_strdup ("12:34:56:78:9a:bc"); simple->hid_ancestor.bus_type = HID_MARKER | BUS_USB; simple->hid_ancestor.vendor_id = HID_MARKER | VENDOR_SONY; simple->hid_ancestor.product_id = HID_MARKER | PRODUCT_SONY_PS3; simple->input_ancestor.sys_path = g_strdup ("/sys/devices/mock/usb/hid/input"); simple->input_ancestor.uevent = g_strdup ("INPUT=yes\n"); simple->input_ancestor.name = g_strdup ("Sony PLAYSTATION(R)3 Controller [input]"); simple->input_ancestor.phys = NULL; simple->input_ancestor.uniq = NULL; simple->input_ancestor.bus_type = EVDEV_MARKER | BUS_USB; simple->input_ancestor.vendor_id = EVDEV_MARKER | VENDOR_SONY; simple->input_ancestor.product_id = EVDEV_MARKER | PRODUCT_SONY_PS3; simple->input_ancestor.version = EVDEV_MARKER | 0x8111; simple->usb_device_ancestor.sys_path = g_strdup ("/sys/devices/mock/usb"); simple->usb_device_ancestor.uevent = g_strdup ("USB=usb_device\n"); simple->usb_device_ancestor.vendor_id = USB_MARKER | VENDOR_SONY; simple->usb_device_ancestor.product_id = USB_MARKER | PRODUCT_SONY_PS3; simple->usb_device_ancestor.device_version = USB_MARKER | 0x0100; simple->usb_device_ancestor.manufacturer = g_strdup ("Sony"); simple->usb_device_ancestor.product = g_strdup ("PLAYSTATION(R)3 Controller"); simple->usb_device_ancestor.serial = NULL; g_assert_cmphex (srt_input_device_get_type_flags (device), ==, SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK); 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), ==, "/sys/devices/mock/usb/hid/input/input0/event0"); g_assert_cmpstr (srt_input_device_get_subsystem (device), ==, "input"); 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"); g_assert_cmpstr (udev_properties[1], ==, NULL); g_assert_true (srt_input_device_get_identity (device, NULL, NULL, NULL, NULL)); g_assert_true (srt_input_device_get_identity (device, &identity.bus_type, &identity.vendor_id, &identity.product_id, &identity.version)); g_assert_cmphex (identity.bus_type, ==, BUS_USB); g_assert_cmphex (identity.vendor_id, ==, VENDOR_SONY); g_assert_cmphex (identity.product_id, ==, PRODUCT_SONY_PS3); g_assert_cmphex (identity.version, ==, 0x8111); g_assert_true (srt_input_device_get_hid_identity (device, NULL, NULL, NULL, NULL, NULL, NULL)); g_assert_true (srt_input_device_get_hid_identity (device, &hid_identity.bus_type, &hid_identity.vendor_id, &hid_identity.product_id, &hid_identity.name, &hid_identity.phys, &hid_identity.uniq)); g_assert_cmphex (hid_identity.bus_type, ==, HID_MARKER | BUS_USB); g_assert_cmphex (hid_identity.vendor_id, ==, HID_MARKER | VENDOR_SONY); g_assert_cmphex (hid_identity.product_id, ==, HID_MARKER | PRODUCT_SONY_PS3); g_assert_cmpstr (hid_identity.name, ==, "Sony PLAYSTATION(R)3 Controller [hid]"); g_assert_cmpstr (hid_identity.phys, ==, "usb-0000:00:14.0-1/input0"); g_assert_cmpstr (hid_identity.uniq, ==, "12:34:56:78:9a:bc"); g_assert_true (srt_input_device_get_input_identity (device, NULL, NULL, NULL, NULL, NULL, NULL, NULL)); g_assert_true (srt_input_device_get_input_identity (device, &input_identity.bus_type, &input_identity.vendor_id, &input_identity.product_id, &input_identity.version, &input_identity.name, &input_identity.phys, &input_identity.uniq)); g_assert_cmphex (input_identity.bus_type, ==, EVDEV_MARKER | BUS_USB); g_assert_cmphex (input_identity.vendor_id, ==, EVDEV_MARKER | VENDOR_SONY); g_assert_cmphex (input_identity.product_id, ==, EVDEV_MARKER | PRODUCT_SONY_PS3); g_assert_cmphex (input_identity.version, ==, EVDEV_MARKER | 0x8111); g_assert_cmpstr (input_identity.name, ==, "Sony PLAYSTATION(R)3 Controller [input]"); g_assert_cmpstr (input_identity.phys, ==, NULL); g_assert_cmpstr (input_identity.uniq, ==, NULL); g_assert_true (srt_input_device_get_usb_device_identity (device, NULL, NULL, NULL, NULL, NULL, NULL)); g_assert_true (srt_input_device_get_usb_device_identity (device, &usb_identity.vendor_id, &usb_identity.product_id, &usb_identity.version, &usb_identity.manufacturer, &usb_identity.product, &usb_identity.serial)); g_assert_cmphex (usb_identity.vendor_id, ==, USB_MARKER | VENDOR_SONY); g_assert_cmphex (usb_identity.product_id, ==, USB_MARKER | PRODUCT_SONY_PS3); g_assert_cmpstr (usb_identity.manufacturer, ==, "Sony"); g_assert_cmpstr (usb_identity.product, ==, "PLAYSTATION(R)3 Controller"); g_assert_cmpstr (usb_identity.serial, ==, NULL); g_debug ("Capabilities internally:"); _srt_evdev_capabilities_dump (iface->peek_event_capabilities (device)); /* This assumes EV_MAX doesn't increase its value */ g_assert_cmpuint (srt_input_device_get_event_types (device, NULL, 0), ==, 1); g_assert_cmpuint (srt_input_device_get_event_types (device, &evbits, 1), ==, 1); g_assert_cmphex (evbits, ==, simple->evdev_caps.ev[0]); g_assert_cmphex (evbits & (1 << EV_KEY), ==, 1 << EV_KEY); g_assert_cmphex (evbits & (1 << EV_ABS), ==, 1 << EV_ABS); g_assert_cmphex (evbits & (1 << EV_SW), ==, 0); g_assert_cmphex (evbits & (1 << EV_MSC), ==, 0); g_assert_cmpint (srt_input_device_has_event_type (device, EV_KEY), ==, TRUE); g_assert_cmpint (srt_input_device_has_event_type (device, EV_SW), ==, FALSE); g_assert_cmpint (srt_input_device_has_event_capability (device, 0, EV_KEY), ==, TRUE); g_assert_cmpint (srt_input_device_has_event_capability (device, 0, EV_SW), ==, FALSE); g_assert_cmpuint (srt_input_device_get_event_capabilities (device, 0, bits, G_N_ELEMENTS (bits)), ==, 1); g_assert_cmphex (bits[0], ==, evbits); for (i = 1; i < G_N_ELEMENTS (bits); i++) g_assert_cmphex (bits[i], ==, 0); g_assert_cmpuint (srt_input_device_get_event_capabilities (device, EV_KEY, bits, G_N_ELEMENTS (bits)), >, 1); /* Low KEY_ codes are keyboard keys, which we don't have */ g_assert_cmphex (bits[0], ==, 0); g_assert_cmpint (test_bit (BTN_A, bits), ==, 1); g_assert_cmpint (test_bit (BTN_STYLUS, bits), ==, 0); g_assert_cmpint (test_bit (KEY_SEMICOLON, bits), ==, 0); g_assert_cmpmem (bits, MIN (sizeof (bits), sizeof (simple->evdev_caps.keys)), simple->evdev_caps.keys, MIN (sizeof (bits), sizeof (simple->evdev_caps.keys))); /* ABS axes also match */ g_assert_cmpuint (srt_input_device_get_event_capabilities (device, EV_ABS, bits, G_N_ELEMENTS (bits)), >=, 1); g_assert_cmpint (test_bit (ABS_X, bits), ==, 1); g_assert_cmpint (test_bit (ABS_Z, bits), ==, 0); g_assert_cmpmem (bits, MIN (sizeof (bits), sizeof (simple->evdev_caps.abs)), simple->evdev_caps.abs, MIN (sizeof (bits), sizeof (simple->evdev_caps.abs))); /* REL axes also match (in fact we don't have any, but we still store * the bitfield) */ g_assert_cmpuint (srt_input_device_get_event_capabilities (device, EV_REL, bits, G_N_ELEMENTS (bits)), >=, 1); g_assert_cmpmem (bits, MIN (sizeof (bits), sizeof (simple->evdev_caps.rel)), simple->evdev_caps.rel, MIN (sizeof (bits), sizeof (simple->evdev_caps.rel))); /* We don't support EV_SW */ g_assert_cmpuint (srt_input_device_get_event_capabilities (device, EV_SW, bits, G_N_ELEMENTS (bits)), ==, 0); for (i = 1; i < G_N_ELEMENTS (bits); i++) g_assert_cmphex (bits[i], ==, 0); g_assert_cmpuint (srt_input_device_get_input_properties (device, bits, G_N_ELEMENTS (bits)), ==, 1); g_assert_cmphex (bits[0], ==, 0); g_assert_cmpint (srt_input_device_has_input_property (device, INPUT_PROP_SEMI_MT), ==, FALSE); for (i = 1; i < G_N_ELEMENTS (bits); i++) g_assert_cmphex (bits[i], ==, 0); } static gboolean in_monitor_main_context (Fixture *f) { if (f->monitor_context == NULL) return g_main_context_is_owner (g_main_context_default ()); return g_main_context_is_owner (f->monitor_context); } static void device_added_cb (SrtInputDeviceMonitor *monitor, SrtInputDevice *device, gpointer user_data) { Fixture *f = user_data; g_autoptr(GError) error = NULL; g_autofree gchar *message = NULL; struct { unsigned int bus_type; unsigned int vendor_id; unsigned int product_id; unsigned int version; } identity = { 1, 1, 1, 1 }; struct { unsigned int bus_type; unsigned int vendor_id; unsigned int product_id; const char *name; const char *phys; const char *uniq; } hid_identity = { 1, 1, 1, "x", "x", "x" }; struct { unsigned int bus_type; unsigned int vendor_id; unsigned int product_id; unsigned int version; const char *name; const char *phys; const char *uniq; } input_identity = { 1, 1, 1, 1, "x", "x", "x" }; struct { unsigned int vendor_id; unsigned int product_id; unsigned int version; const char *manufacturer; const char *product; const char *serial; } usb_identity = { 1, 1, 1, "x", "x", "x" }; SrtInputDeviceInterfaceFlags iface_flags; int fd; g_assert_true (SRT_IS_INPUT_DEVICE_MONITOR (monitor)); g_assert_true (SRT_IS_INPUT_DEVICE (device)); message = g_strdup_printf ("added device: %s", srt_input_device_get_dev_node (device)); g_debug ("%s: %s", G_OBJECT_TYPE_NAME (monitor), message); iface_flags = srt_input_device_get_interface_flags (device); if (srt_input_device_get_identity (device, &identity.bus_type, &identity.vendor_id, &identity.product_id, &identity.version)) { g_assert_true (srt_input_device_get_identity (device, NULL, NULL, NULL, NULL)); } else { g_assert_false (srt_input_device_get_identity (device, NULL, NULL, NULL, NULL)); /* previous contents are untouched */ g_assert_cmphex (identity.bus_type, ==, 1); g_assert_cmphex (identity.vendor_id, ==, 1); g_assert_cmphex (identity.product_id, ==, 1); g_assert_cmphex (identity.version, ==, 1); } if (srt_input_device_get_hid_identity (device, &hid_identity.bus_type, &hid_identity.vendor_id, &hid_identity.product_id, &hid_identity.name, &hid_identity.phys, &hid_identity.uniq)) { g_assert_true (srt_input_device_get_hid_identity (device, NULL, NULL, NULL, NULL, NULL, NULL)); } else { g_assert_false (srt_input_device_get_hid_identity (device, NULL, NULL, NULL, NULL, NULL, NULL)); /* previous contents are untouched */ g_assert_cmphex (hid_identity.bus_type, ==, 1); g_assert_cmphex (hid_identity.vendor_id, ==, 1); g_assert_cmphex (hid_identity.product_id, ==, 1); g_assert_cmpstr (hid_identity.name, ==, "x"); g_assert_cmpstr (hid_identity.phys, ==, "x"); g_assert_cmpstr (hid_identity.uniq, ==, "x"); } if (srt_input_device_get_input_identity (device, &input_identity.bus_type, &input_identity.vendor_id, &input_identity.product_id, &input_identity.version, &input_identity.name, &input_identity.phys, &input_identity.uniq)) { g_assert_true (srt_input_device_get_input_identity (device, NULL, NULL, NULL, NULL, NULL, NULL, NULL)); } else { g_assert_false (srt_input_device_get_input_identity (device, NULL, NULL, NULL, NULL, NULL, NULL, NULL)); /* previous contents are untouched */ g_assert_cmphex (input_identity.bus_type, ==, 1); g_assert_cmphex (input_identity.vendor_id, ==, 1); g_assert_cmphex (input_identity.product_id, ==, 1); g_assert_cmphex (input_identity.version, ==, 1); g_assert_cmpstr (input_identity.name, ==, "x"); g_assert_cmpstr (input_identity.phys, ==, "x"); g_assert_cmpstr (input_identity.uniq, ==, "x"); } if (srt_input_device_get_usb_device_identity (device, &usb_identity.vendor_id, &usb_identity.product_id, &usb_identity.version, &usb_identity.manufacturer, &usb_identity.product, &usb_identity.serial)) { g_assert_true (srt_input_device_get_usb_device_identity (device, NULL, NULL, NULL, NULL, NULL, NULL)); } else { g_assert_false (srt_input_device_get_usb_device_identity (device, NULL, NULL, NULL, NULL, NULL, NULL)); /* previous contents are untouched */ g_assert_cmphex (usb_identity.vendor_id, ==, 1); g_assert_cmphex (usb_identity.product_id, ==, 1); g_assert_cmphex (usb_identity.version, ==, 1); g_assert_cmpstr (usb_identity.manufacturer, ==, "x"); g_assert_cmpstr (usb_identity.product, ==, "x"); g_assert_cmpstr (usb_identity.serial, ==, "x"); } fd = srt_input_device_open (device, O_RDONLY | O_NONBLOCK, &error); if (iface_flags & SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE) { g_assert_no_error (error); g_assert_cmpint (fd, >=, 0); } else { g_assert_nonnull (error); g_assert_cmpint (fd, <, 0); } glnx_close_fd (&fd); g_clear_error (&error); fd = srt_input_device_open (device, O_RDWR | O_NONBLOCK, &error); if (iface_flags & SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE) { g_assert_no_error (error); g_assert_cmpint (fd, >=, 0); } else { g_assert_nonnull (error); g_assert_cmpint (fd, <, 0); } glnx_close_fd (&fd); g_clear_error (&error); /* Unsupported flags (currently everything except O_NONBLOCK) are * not allowed */ fd = srt_input_device_open (device, O_RDONLY | O_SYNC, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); glnx_close_fd (&fd); g_clear_error (&error); /* For the mock device monitor, we know exactly what to expect, so * we can compare the expected log with what actually happened. For * real device monitors, we don't know what's physically present, * so we have to just emit debug messages. */ 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; unsigned long evbits; unsigned long bits[LONGS_FOR_BITS (HIGHEST_EVENT_CODE)]; gsize i; g_assert_cmphex (srt_input_device_get_type_flags (device), ==, SRT_INPUT_DEVICE_TYPE_FLAGS_JOYSTICK); g_assert_cmphex (identity.bus_type, ==, BUS_USB); g_assert_cmphex (identity.vendor_id, ==, VENDOR_VALVE); g_assert_cmphex (identity.product_id, ==, PRODUCT_VALVE_STEAM_CONTROLLER); g_assert_cmphex (identity.version, ==, 0x0111); g_assert_cmphex (hid_identity.bus_type, ==, HID_MARKER | BUS_USB); g_assert_cmphex (hid_identity.vendor_id, ==, HID_MARKER | VENDOR_VALVE); g_assert_cmphex (hid_identity.product_id, ==, HID_MARKER | PRODUCT_VALVE_STEAM_CONTROLLER); g_assert_cmpstr (hid_identity.name, ==, "Valve Software Steam Controller"); g_assert_cmpstr (hid_identity.phys, ==, "[hid]usb-0000:00:14.0-1.2/input1"); g_assert_cmpstr (hid_identity.uniq, ==, ""); g_assert_cmphex (input_identity.bus_type, ==, EVDEV_MARKER | BUS_USB); g_assert_cmphex (input_identity.vendor_id, ==, EVDEV_MARKER | VENDOR_VALVE); g_assert_cmphex (input_identity.product_id, ==, EVDEV_MARKER | PRODUCT_VALVE_STEAM_CONTROLLER); g_assert_cmphex (input_identity.version, ==, EVDEV_MARKER | 0x0111); g_assert_cmpstr (input_identity.name, ==, "Wireless Steam Controller"); g_assert_cmpstr (input_identity.phys, ==, "[input]usb-0000:00:14.0-1.2/input1"); g_assert_cmpstr (input_identity.uniq, ==, "12345678"); g_assert_cmphex (usb_identity.vendor_id, ==, USB_MARKER | VENDOR_VALVE); g_assert_cmphex (usb_identity.product_id, ==, USB_MARKER | PRODUCT_VALVE_STEAM_CONTROLLER); g_assert_cmphex (usb_identity.version, ==, USB_MARKER | 0x0001); g_assert_cmpstr (usb_identity.manufacturer, ==, "Valve Software"); g_assert_cmpstr (usb_identity.product, ==, "Steam Controller"); g_assert_cmpstr (usb_identity.serial, ==, NULL); uevent = srt_input_device_dup_uevent (device); g_assert_cmpstr (uevent, ==, "ONE=1\nTWO=2\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"); 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"); /* This assumes EV_MAX doesn't increase its value */ g_assert_cmpuint (srt_input_device_get_event_types (device, NULL, 0), ==, 1); g_assert_cmpuint (srt_input_device_get_event_types (device, &evbits, 1), ==, 1); g_assert_cmphex (evbits & (1 << EV_KEY), ==, 1 << EV_KEY); g_assert_cmphex (evbits & (1 << EV_ABS), ==, 1 << EV_ABS); g_assert_cmphex (evbits & (1 << EV_SW), ==, 0); g_assert_cmphex (evbits & (1 << EV_MSC), ==, 0); g_assert_cmpint (srt_input_device_has_event_type (device, EV_KEY), ==, TRUE); g_assert_cmpint (srt_input_device_has_event_type (device, EV_SW), ==, FALSE); g_assert_cmpint (srt_input_device_has_event_capability (device, 0, EV_KEY), ==, TRUE); g_assert_cmpint (srt_input_device_has_event_capability (device, 0, EV_SW), ==, FALSE); g_assert_cmpuint (srt_input_device_get_event_capabilities (device, 0, bits, G_N_ELEMENTS (bits)), ==, 1); g_assert_cmphex (bits[0], ==, evbits); for (i = 1; i < G_N_ELEMENTS (bits); i++) g_assert_cmphex (bits[i], ==, 0); g_assert_cmpuint (srt_input_device_get_event_capabilities (device, EV_KEY, bits, G_N_ELEMENTS (bits)), >, 1); /* Low KEY_ codes are keyboard keys, which we don't have */ g_assert_cmphex (bits[0], ==, 0); g_assert_cmpint (test_bit (BTN_A, bits), ==, 1); g_assert_cmpint (test_bit (BTN_STYLUS, bits), ==, 0); g_assert_cmpint (test_bit (KEY_SEMICOLON, bits), ==, 0); /* ABS axes also match */ g_assert_cmpuint (srt_input_device_get_event_capabilities (device, EV_ABS, bits, G_N_ELEMENTS (bits)), >=, 1); g_assert_cmpint (test_bit (ABS_X, bits), ==, 1); g_assert_cmpint (test_bit (ABS_Z, bits), ==, 0); /* REL axes also match (in fact we don't have any, but we still store * the bitfield) */ g_assert_cmpuint (srt_input_device_get_event_capabilities (device, EV_REL, bits, G_N_ELEMENTS (bits)), >=, 1); for (i = 1; i < G_N_ELEMENTS (bits); i++) g_assert_cmphex (bits[i], ==, 0); /* We don't support EV_SW */ g_assert_cmpuint (srt_input_device_get_event_capabilities (device, EV_SW, bits, G_N_ELEMENTS (bits)), ==, 0); for (i = 1; i < G_N_ELEMENTS (bits); i++) g_assert_cmphex (bits[i], ==, 0); g_assert_cmpuint (srt_input_device_get_input_properties (device, bits, G_N_ELEMENTS (bits)), ==, 1); /* The mock implementation unrealistically sets INPUT_PROP_POINTER, * just so we have something nonzero to test against */ g_assert_cmphex (bits[0], ==, (1 << INPUT_PROP_POINTER)); g_assert_cmpint (srt_input_device_has_input_property (device, INPUT_PROP_POINTER), ==, TRUE); g_assert_cmpint (srt_input_device_has_input_property (device, INPUT_PROP_SEMI_MT), ==, FALSE); for (i = 1; i < G_N_ELEMENTS (bits); i++) g_assert_cmphex (bits[i], ==, 0); g_ptr_array_add (f->log, g_steal_pointer (&message)); } g_assert_true (in_monitor_main_context (f)); } static void device_removed_cb (SrtInputDeviceMonitor *monitor, SrtInputDevice *device, gpointer user_data) { Fixture *f = user_data; g_autofree gchar *message = NULL; g_assert_true (SRT_IS_INPUT_DEVICE_MONITOR (monitor)); g_assert_true (SRT_IS_INPUT_DEVICE (device)); message = g_strdup_printf ("removed device: %s", srt_input_device_get_dev_node (device)); g_debug ("%s: %s", G_OBJECT_TYPE_NAME (monitor), message); if (f->config->type == MOCK) g_ptr_array_add (f->log, g_steal_pointer (&message)); g_assert_true (in_monitor_main_context (f)); } static void all_for_now_cb (SrtInputDeviceMonitor *monitor, gpointer user_data) { Fixture *f = user_data; g_assert_true (SRT_IS_INPUT_DEVICE_MONITOR (monitor)); g_ptr_array_add (f->log, g_strdup ("all for now")); g_debug ("%s: %s", G_OBJECT_TYPE_NAME (monitor), (const char *) g_ptr_array_index (f->log, f->log->len - 1)); g_assert_true (in_monitor_main_context (f)); } static gboolean done_cb (gpointer user_data) { gboolean *done = user_data; *done = TRUE; return G_SOURCE_REMOVE; } /* This is the equivalent of g_idle_add() for a non-default main context */ static guint idle_add_in_context (GSourceFunc function, gpointer data, GMainContext *context) { g_autoptr(GSource) idler = g_idle_source_new (); g_source_set_callback (idler, function, data, NULL); return g_source_attach (idler, context); } static SrtInputDeviceMonitor * input_device_monitor_new (Fixture *f, SrtInputDeviceMonitorFlags flags) { 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: default: return SRT_INPUT_DEVICE_MONITOR (mock_input_device_monitor_new (flags)); } } /* * Test the basic behaviour of an input device monitor: * - start * - do initial enumeration * - watch for new devices * - emit signals in the correct main context * - stop */ static void test_input_device_monitor (Fixture *f, gconstpointer context) { g_autoptr(SrtInputDeviceMonitor) monitor = NULL; g_autoptr(GError) error = NULL; gboolean ok; gboolean did_default_idle = FALSE; gboolean did_context_idle = FALSE; guint i; if (f->skipped) return; f->monitor_context = g_main_context_new (); /* To check that the signals get emitted in the correct main-context, * temporarily set a new thread-default main-context while we create * the monitor. */ g_main_context_push_thread_default (f->monitor_context); { monitor = input_device_monitor_new (f, SRT_INPUT_DEVICE_MONITOR_FLAGS_NONE); } g_main_context_pop_thread_default (f->monitor_context); g_assert_nonnull (monitor); srt_input_device_monitor_request_evdev (monitor); srt_input_device_monitor_request_raw_hid (monitor); g_signal_connect (monitor, "added", G_CALLBACK (device_added_cb), f); g_signal_connect (monitor, "removed", G_CALLBACK (device_removed_cb), f); g_signal_connect (monitor, "all-for-now", G_CALLBACK (all_for_now_cb), f); /* Note that the signals are emitted in the main-context that was * thread-default at the time we created the object, not the * main-context that called start(). */ ok = srt_input_device_monitor_start (monitor, &error); g_debug ("start() returned"); g_assert_no_error (error); g_assert_true (ok); g_ptr_array_add (f->log, g_strdup ("start() returned")); g_idle_add (done_cb, &did_default_idle); idle_add_in_context (done_cb, &did_context_idle, f->monitor_context); i = 0; g_assert_cmpuint (f->log->len, >, i); g_assert_cmpstr (g_ptr_array_index (f->log, i++), ==, "start() returned"); /* There's nothing else in the log yet */ g_assert_cmpuint (f->log->len, ==, i); /* Iterating the default main context does not deliver signals */ while (!did_default_idle) g_main_context_iteration (NULL, TRUE); g_assert_cmpuint (f->log->len, ==, i); /* Iterating the main context that was thread-default at the time we * constructed the monitor *does* deliver signals */ while (!did_context_idle) g_main_context_iteration (f->monitor_context, TRUE); /* For the mock device monitor, we can predict which devices will be added, * so we log them and assert about them. For real device monitors we * can't reliably do this. */ if (f->config->type == MOCK) { g_assert_cmpuint (f->log->len, >, i); g_assert_cmpstr (g_ptr_array_index (f->log, i++), ==, "added device: /dev/input/event0"); } g_assert_cmpuint (f->log->len, >, i); g_assert_cmpstr (g_ptr_array_index (f->log, i++), ==, "all for now"); if (f->config->type == MOCK) { g_assert_cmpuint (f->log->len, >, i); g_assert_cmpstr (g_ptr_array_index (f->log, i++), ==, "added device: /dev/input/event-connected-briefly"); g_assert_cmpuint (f->log->len, >, i); g_assert_cmpstr (g_ptr_array_index (f->log, i++), ==, "removed device: /dev/input/event-connected-briefly"); } g_assert_cmpuint (f->log->len, ==, i); /* Explicitly stop it here. We test not explicitly stopping in the * other test-case */ srt_input_device_monitor_stop (monitor); /* It's possible that not all the memory used is freed until we have * iterated the main-context one last time */ did_context_idle = FALSE; idle_add_in_context (done_cb, &did_context_idle, f->monitor_context); while (!did_context_idle) g_main_context_iteration (f->monitor_context, TRUE); } /* * Test things we couldn't test in the previous test-case: * - the ONCE flag, which disables monitoring * - using our thread-default main-context throughout */ static void test_input_device_monitor_once (Fixture *f, gconstpointer context) { g_autoptr(SrtInputDeviceMonitor) monitor = NULL; g_autoptr(GError) error = NULL; gboolean ok; gboolean done = FALSE; guint i; if (f->skipped) return; monitor = input_device_monitor_new (f, SRT_INPUT_DEVICE_MONITOR_FLAGS_ONCE); g_assert_nonnull (monitor); srt_input_device_monitor_request_evdev (monitor); srt_input_device_monitor_request_raw_hid (monitor); g_signal_connect (monitor, "added", G_CALLBACK (device_added_cb), f); g_signal_connect (monitor, "removed", G_CALLBACK (device_removed_cb), f); g_signal_connect (monitor, "all-for-now", G_CALLBACK (all_for_now_cb), f); ok = srt_input_device_monitor_start (monitor, &error); g_debug ("start() returned"); g_assert_no_error (error); g_assert_true (ok); g_ptr_array_add (f->log, g_strdup ("start() returned")); g_idle_add (done_cb, &done); while (!done) g_main_context_iteration (NULL, TRUE); i = 0; /* Because the same main context was the thread-default at the * time we created the object and at the time we called start(), * the first batch of signals arrive even before start() has returned. */ if (f->config->type == MOCK) { g_assert_cmpuint (f->log->len, >, i); g_assert_cmpstr (g_ptr_array_index (f->log, i++), ==, "added device: /dev/input/event0"); } g_assert_cmpuint (f->log->len, >, i); g_assert_cmpstr (g_ptr_array_index (f->log, i++), ==, "all for now"); g_assert_cmpuint (f->log->len, >, i); g_assert_cmpstr (g_ptr_array_index (f->log, i++), ==, "start() returned"); g_assert_cmpuint (f->log->len, ==, i); /* Don't explicitly stop it here. We test explicitly stopping in the * other test-case */ g_clear_object (&monitor); /* It's possible that not all the memory used is freed until we have * iterate the main-context one last time */ done = FALSE; g_idle_add (done_cb, &done); while (!done) g_main_context_iteration (NULL, TRUE); } int main (int argc, char **argv) { argv0 = argv[0]; g_test_init (&argc, &argv, NULL); g_test_add ("/input-device/from-json", Fixture, NULL, setup, test_input_device_from_json, teardown); g_test_add ("/input-device/guess", Fixture, NULL, setup, test_input_device_guess, teardown); g_test_add ("/input-device/identity-from-hid-uevent", Fixture, NULL, setup, test_input_device_identity_from_hid_uevent, teardown); g_test_add ("/input-device/usb", Fixture, NULL, setup, test_input_device_usb, teardown); g_test_add ("/input-device/monitor/mock", Fixture, NULL, setup, test_input_device_monitor, teardown); g_test_add ("/input-device/monitor-once/mock", Fixture, NULL, setup, test_input_device_monitor_once, teardown); g_test_add ("/input-device/monitor/direct", Fixture, &direct_config, 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 (); }