Merge tag 'for-linus-2023042601' of git://git.kernel.org/pub/scm/linux/kernel/git...
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 27 Apr 2023 18:23:36 +0000 (11:23 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 27 Apr 2023 18:23:36 +0000 (11:23 -0700)
Pull HID updates from Jiri Kosina:

 - import a bunch of HID selftests from out-of-tree hid-tools project
   (Benjamin Tissoires)

 - drastically reducing Bluetooth disconnects on hid-nintendo driven
   devices (Daniel J. Ogorchock)

 - lazy initialization of battery interfaces in wacom driver (Jason
   Gerecke)

 - generic support for all Kye tablets (David Yang)

 - proper rumble queue overrun handling in hid-nintendo (Daniel J.
   Ogorchock)

 - support for ADC measurement in logitech-hidpp driver (Bastien Nocera)

 - reset GPIO support in i2c-hid (Hans de Goede)

 - improved handling of generic "Digitizer" usage (Jason Gerecke)

 - support for KEY_CAMERA_FOCUS (Feng Qi)

 - quirks for Apple Geyser 3 and Apple Geyser 4 (Alex Henrie)

 - assorted functional fixes and device ID additions

* tag 'for-linus-2023042601' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (54 commits)
  HID: amd_sfh: Fix max supported HID devices
  HID: wacom: generic: Set battery quirk only when we see battery data
  HID: wacom: Lazy-init batteries
  HID: Ignore battery for ELAN touchscreen on ROG Flow X13 GV301RA
  HID: asus: explicitly include linux/leds.h
  HID: lg-g15: explicitly include linux/leds.h
  HID: steelseries: explicitly include linux/leds.h
  HID: apple: Set the tilde quirk flag on the Geyser 3
  HID: apple: explicitly include linux/leds.h
  HID: mcp2221: fix get and get_direction for gpio
  HID: mcp2221: fix report layout for gpio get
  HID: wacom: Set a default resolution for older tablets
  HID: i2c-hid-of: Add reset GPIO support to i2c-hid-of
  HID: i2c-hid-of: Allow using i2c-hid-of on non OF platforms
  HID: i2c-hid-of: Consistenly use dev local variable in probe()
  HID: kye: Fix rdesc for kye tablets
  HID: amd_sfh: Support for additional light sensor
  HID: amd_sfh: Handle "no sensors" enabled for SFH1.1
  HID: amd_sfh: Increase sensor command timeout for SFH1.1
  HID: amd_sfh: Correct the stop all command
  ...

62 files changed:
Documentation/ABI/testing/sysfs-bus-usb
drivers/hid/amd-sfh-hid/amd_sfh_client.c
drivers/hid/amd-sfh-hid/amd_sfh_hid.h
drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
drivers/hid/amd-sfh-hid/amd_sfh_pcie.h
drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c
drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_desc.c
drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c
drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c
drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h
drivers/hid/hid-apple.c
drivers/hid/hid-asus.c
drivers/hid/hid-ids.h
drivers/hid/hid-input.c
drivers/hid/hid-kye.c
drivers/hid/hid-lg-g15.c
drivers/hid/hid-logitech-hidpp.c
drivers/hid/hid-mcp2221.c
drivers/hid/hid-nintendo.c
drivers/hid/hid-quirks.c
drivers/hid/hid-steelseries.c
drivers/hid/i2c-hid/Kconfig
drivers/hid/i2c-hid/i2c-hid-of.c
drivers/hid/wacom_sys.c
drivers/hid/wacom_wac.c
drivers/hid/wacom_wac.h
drivers/usb/core/message.c
drivers/usb/core/sysfs.c
drivers/usb/core/usb.h
include/linux/hid.h
include/linux/usb.h
tools/testing/selftests/hid/Makefile
tools/testing/selftests/hid/config
tools/testing/selftests/hid/hid-apple.sh [new file with mode: 0755]
tools/testing/selftests/hid/hid-core.sh [new file with mode: 0755]
tools/testing/selftests/hid/hid-gamepad.sh [new file with mode: 0755]
tools/testing/selftests/hid/hid-ite.sh [new file with mode: 0755]
tools/testing/selftests/hid/hid-keyboard.sh [new file with mode: 0755]
tools/testing/selftests/hid/hid-mouse.sh [new file with mode: 0755]
tools/testing/selftests/hid/hid-multitouch.sh [new file with mode: 0755]
tools/testing/selftests/hid/hid-sony.sh [new file with mode: 0755]
tools/testing/selftests/hid/hid-tablet.sh [new file with mode: 0755]
tools/testing/selftests/hid/hid-usb_crash.sh [new file with mode: 0755]
tools/testing/selftests/hid/hid-wacom.sh [new file with mode: 0755]
tools/testing/selftests/hid/run-hid-tools-tests.sh [new file with mode: 0755]
tools/testing/selftests/hid/settings [new file with mode: 0644]
tools/testing/selftests/hid/tests/__init__.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/base.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/conftest.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/descriptors_wacom.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/test_apple_keyboard.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/test_gamepad.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/test_hid_core.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/test_ite_keyboard.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/test_keyboard.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/test_mouse.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/test_multitouch.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/test_sony.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/test_tablet.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/test_usb_crash.py [new file with mode: 0644]
tools/testing/selftests/hid/tests/test_wacom_generic.py [new file with mode: 0644]
tools/testing/selftests/hid/vmtest.sh

index 545c2dd..cb172db 100644 (file)
@@ -166,6 +166,23 @@ Description:
                The file will be present for all speeds of USB devices, and will
                always read "no" for USB 1.1 and USB 2.0 devices.
 
+What:          /sys/bus/usb/devices/<INTERFACE>/wireless_status
+Date:          February 2023
+Contact:       Bastien Nocera <hadess@hadess.net>
+Description:
+               Some USB devices use a USB receiver dongle to communicate
+               wirelessly with their device using proprietary protocols. This
+               attribute allows user-space to know whether the device is
+               connected to its receiver dongle, and, for example, consider
+               the device to be absent when choosing whether to show the
+               device's battery, show a headset in a list of outputs, or show
+               an on-screen keyboard if the only wireless keyboard is
+               turned off.
+               This attribute is not to be used to replace protocol specific
+               statuses available in WWAN, WLAN/Wi-Fi, Bluetooth, etc.
+               If the device does not use a receiver dongle with a wireless
+               device, then this attribute will not exist.
+
 What:          /sys/bus/usb/devices/.../<hub_interface>/port<X>
 Date:          August 2012
 Contact:       Lan Tianyu <tianyu.lan@intel.com>
index c751d12..d9b7b01 100644 (file)
@@ -147,6 +147,7 @@ static const char *get_sensor_name(int idx)
        case mag_idx:
                return "magnetometer";
        case als_idx:
+       case ACS_IDX: /* ambient color sensor */
                return "ALS";
        case HPD_IDX:
                return "HPD";
index 5280368..97296f5 100644 (file)
@@ -11,7 +11,7 @@
 #ifndef AMDSFH_HID_H
 #define AMDSFH_HID_H
 
-#define MAX_HID_DEVICES                5
+#define MAX_HID_DEVICES                6
 #define AMD_SFH_HID_VENDOR     0x1022
 #define AMD_SFH_HID_PRODUCT    0x0001
 
index 47774b9..2530fa9 100644 (file)
@@ -29,6 +29,7 @@
 #define MAGNO_EN       BIT(2)
 #define HPD_EN         BIT(16)
 #define ALS_EN         BIT(19)
+#define ACS_EN         BIT(22)
 
 static int sensor_mask_override = -1;
 module_param_named(sensor_mask, sensor_mask_override, int, 0444);
@@ -233,6 +234,9 @@ int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id)
        if (HPD_EN & activestatus)
                sensor_id[num_of_sensors++] = HPD_IDX;
 
+       if (ACS_EN & activestatus)
+               sensor_id[num_of_sensors++] = ACS_IDX;
+
        return num_of_sensors;
 }
 
@@ -367,6 +371,14 @@ init_done:
        return devm_add_action_or_reset(&pdev->dev, privdata->mp2_ops->remove, privdata);
 }
 
+static void amd_sfh_shutdown(struct pci_dev *pdev)
+{
+       struct amd_mp2_dev *mp2 = pci_get_drvdata(pdev);
+
+       if (mp2 && mp2->mp2_ops)
+               mp2->mp2_ops->stop_all(mp2);
+}
+
 static int __maybe_unused amd_mp2_pci_resume(struct device *dev)
 {
        struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
@@ -401,6 +413,7 @@ static struct pci_driver amd_mp2_pci_driver = {
        .id_table       = amd_mp2_pci_tbl,
        .probe          = amd_mp2_pci_probe,
        .driver.pm      = &amd_mp2_pm_ops,
+       .shutdown       = amd_sfh_shutdown,
 };
 module_pci_driver(amd_mp2_pci_driver);
 
index dfb7cab..70add75 100644 (file)
@@ -23,6 +23,7 @@
 #define V2_STATUS      0x2
 
 #define HPD_IDX                16
+#define ACS_IDX                22
 
 #define SENSOR_DISCOVERY_STATUS_MASK           GENMASK(5, 3)
 #define SENSOR_DISCOVERY_STATUS_SHIFT          3
index f9a8c02..8716a05 100644 (file)
@@ -48,6 +48,7 @@ static int get_report_descriptor(int sensor_idx, u8 *rep_desc)
                       sizeof(comp3_report_descriptor));
                break;
        case als_idx: /* ambient light sensor */
+       case ACS_IDX: /* ambient color sensor */
                memset(rep_desc, 0, sizeof(als_report_descriptor));
                memcpy(rep_desc, als_report_descriptor,
                       sizeof(als_report_descriptor));
@@ -97,6 +98,7 @@ static u32 get_descr_sz(int sensor_idx, int descriptor_name)
                }
                break;
        case als_idx:
+       case ACS_IDX: /* ambient color sensor */
                switch (descriptor_name) {
                case descr_size:
                        return sizeof(als_report_descriptor);
@@ -174,6 +176,7 @@ static u8 get_feature_report(int sensor_idx, int report_id, u8 *feature_report)
                report_size = sizeof(magno_feature);
                break;
        case als_idx:  /* ambient light sensor */
+       case ACS_IDX: /* ambient color sensor */
                get_common_features(&als_feature.common_property, report_id);
                als_feature.als_change_sesnitivity = HID_DEFAULT_SENSITIVITY;
                als_feature.als_sensitivity_min = HID_DEFAULT_MIN_VALUE;
@@ -245,6 +248,7 @@ static u8 get_input_report(u8 current_index, int sensor_idx, int report_id,
                report_size = sizeof(magno_input);
                break;
        case als_idx: /* Als */
+       case ACS_IDX: /* ambient color sensor */
                get_common_inputs(&als_input.common_property, report_id);
                /* For ALS ,V2 Platforms uses C2P_MSG5 register instead of DRAM access method */
                if (supported_input == V2_STATUS)
index 0609fea..6f0d332 100644 (file)
@@ -218,7 +218,7 @@ static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
                             OFFSET_SENSOR_DATA_DEFAULT;
                memcpy_fromio(&als_data, sensoraddr, sizeof(struct sfh_als_data));
                get_common_inputs(&als_input.common_property, report_id);
-               als_input.illuminance_value = als_data.lux;
+               als_input.illuminance_value = float_to_int(als_data.lux);
                report_size = sizeof(als_input);
                memcpy(input_report, &als_input, sizeof(als_input));
                break;
index a1d6e08..bb8bd78 100644 (file)
@@ -112,6 +112,7 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
        cl_data->num_hid_devices = amd_sfh_get_sensor_num(privdata, &cl_data->sensor_idx[0]);
        if (cl_data->num_hid_devices == 0)
                return -ENODEV;
+       cl_data->is_any_sensor_enabled = false;
 
        INIT_DELAYED_WORK(&cl_data->work, amd_sfh_work);
        INIT_DELAYED_WORK(&cl_data->work_buffer, amd_sfh_work_buffer);
@@ -170,6 +171,7 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
                status = (status == 0) ? SENSOR_ENABLED : SENSOR_DISABLED;
 
                if (status == SENSOR_ENABLED) {
+                       cl_data->is_any_sensor_enabled = true;
                        cl_data->sensor_sts[i] = SENSOR_ENABLED;
                        rc = amdtp_hid_probe(i, cl_data);
                        if (rc) {
@@ -186,12 +188,21 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
                                        cl_data->sensor_sts[i]);
                                goto cleanup;
                        }
+               } else {
+                       cl_data->sensor_sts[i] = SENSOR_DISABLED;
                }
                dev_dbg(dev, "sid 0x%x (%s) status 0x%x\n",
                        cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
                        cl_data->sensor_sts[i]);
        }
 
+       if (!cl_data->is_any_sensor_enabled) {
+               dev_warn(dev, "Failed to discover, sensors not enabled is %d\n",
+                        cl_data->is_any_sensor_enabled);
+               rc = -EOPNOTSUPP;
+               goto cleanup;
+       }
+
        schedule_delayed_work(&cl_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP));
        return 0;
 
index c6df959..4f81ef2 100644 (file)
@@ -16,11 +16,11 @@ static int amd_sfh_wait_response(struct amd_mp2_dev *mp2, u8 sid, u32 cmd_id)
 {
        struct sfh_cmd_response cmd_resp;
 
-       /* Get response with status within a max of 1600 ms timeout */
+       /* Get response with status within a max of 10000 ms timeout */
        if (!readl_poll_timeout(mp2->mmio + AMD_P2C_MSG(0), cmd_resp.resp,
                                (cmd_resp.response.response == 0 &&
                                cmd_resp.response.cmd_id == cmd_id && (sid == 0xff ||
-                               cmd_resp.response.sensor_id == sid)), 500, 1600000))
+                               cmd_resp.response.sensor_id == sid)), 500, 10000000))
                return cmd_resp.response.response;
 
        return -1;
@@ -33,6 +33,7 @@ static void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor
        cmd_base.ul = 0;
        cmd_base.cmd.cmd_id = ENABLE_SENSOR;
        cmd_base.cmd.intr_disable = 0;
+       cmd_base.cmd.sub_cmd_value = 1;
        cmd_base.cmd.sensor_id = info.sensor_idx;
 
        writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG(0));
@@ -45,6 +46,7 @@ static void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx)
        cmd_base.ul = 0;
        cmd_base.cmd.cmd_id = DISABLE_SENSOR;
        cmd_base.cmd.intr_disable = 0;
+       cmd_base.cmd.sub_cmd_value = 1;
        cmd_base.cmd.sensor_id = sensor_idx;
 
        writeq(0x0, privdata->mmio + AMD_C2P_MSG(1));
@@ -56,8 +58,10 @@ static void amd_stop_all_sensor(struct amd_mp2_dev *privdata)
        struct sfh_cmd_base cmd_base;
 
        cmd_base.ul = 0;
-       cmd_base.cmd.cmd_id = STOP_ALL_SENSORS;
+       cmd_base.cmd.cmd_id = DISABLE_SENSOR;
        cmd_base.cmd.intr_disable = 0;
+       /* 0xf indicates all sensors */
+       cmd_base.cmd.sensor_id = 0xf;
 
        writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG(0));
 }
index ae47a36..9d31d5b 100644 (file)
@@ -33,9 +33,9 @@ struct sfh_cmd_base {
                struct {
                        u32 sensor_id           : 4;
                        u32 cmd_id              : 4;
-                       u32 sub_cmd_id          : 6;
-                       u32 length              : 12;
-                       u32 rsvd                : 5;
+                       u32 sub_cmd_id          : 8;
+                       u32 sub_cmd_value       : 12;
+                       u32 rsvd                : 3;
                        u32 intr_disable        : 1;
                } cmd;
        };
@@ -133,7 +133,7 @@ struct sfh_mag_data {
 
 struct sfh_als_data {
        struct sfh_common_data commondata;
-       u16 lux;
+       u32 lux;
 };
 
 struct hpd_status {
index 1ccab8a..cc535d2 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/slab.h>
 #include <linux/timer.h>
 #include <linux/string.h>
+#include <linux/leds.h>
 
 #include "hid-ids.h"
 
@@ -875,14 +876,16 @@ static const struct hid_device_id apple_devices[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ANSI),
                .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ISO),
-               .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+               .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+                       APPLE_ISO_TILDE_QUIRK },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_JIS),
                .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
                        APPLE_RDESC_JIS },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ANSI),
                .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ISO),
-               .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+               .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+                       APPLE_ISO_TILDE_QUIRK },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_JIS),
                .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
                        APPLE_RDESC_JIS },
@@ -901,7 +904,8 @@ static const struct hid_device_id apple_devices[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ANSI),
                .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ISO),
-               .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+               .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+                       APPLE_ISO_TILDE_QUIRK },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_JIS),
                .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
                        APPLE_RDESC_JIS },
@@ -942,31 +946,31 @@ static const struct hid_device_id apple_devices[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ANSI),
                .driver_data = APPLE_HAS_FN },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ISO),
-               .driver_data = APPLE_HAS_FN },
+               .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_JIS),
                .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI),
                .driver_data = APPLE_HAS_FN },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ISO),
-               .driver_data = APPLE_HAS_FN },
+               .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_JIS),
                .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI),
                .driver_data = APPLE_HAS_FN },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ISO),
-               .driver_data = APPLE_HAS_FN },
+               .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_JIS),
                .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI),
                .driver_data = APPLE_HAS_FN },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_ISO),
-               .driver_data = APPLE_HAS_FN },
+               .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_JIS),
                .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI),
                .driver_data = APPLE_HAS_FN },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO),
-               .driver_data = APPLE_HAS_FN },
+               .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS),
                .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
        { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI),
index d1094bb..01a2757 100644 (file)
@@ -30,6 +30,7 @@
 #include <linux/input/mt.h>
 #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
 #include <linux/power_supply.h>
+#include <linux/leds.h>
 
 #include "hid-ids.h"
 
index c2e9b6d..d79e946 100644 (file)
 #define I2C_DEVICE_ID_HP_SPECTRE_X360_15       0x2817
 #define I2C_DEVICE_ID_HP_SPECTRE_X360_13_AW0020NG  0x29DF
 #define I2C_DEVICE_ID_ASUS_TP420IA_TOUCHSCREEN 0x2BC8
+#define I2C_DEVICE_ID_ASUS_GV301RA_TOUCHSCREEN 0x2C82
 #define USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN 0x2544
 #define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN   0x2706
 #define I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN   0x261A
 #define USB_DEVICE_ID_GENIUS_MANTICORE 0x0153
 #define USB_DEVICE_ID_GENIUS_GX_IMPERATOR      0x4018
 #define USB_DEVICE_ID_KYE_GPEN_560     0x5003
+#define USB_DEVICE_ID_KYE_EASYPEN_M406 0x5005
+#define USB_DEVICE_ID_KYE_EASYPEN_M506 0x500F
 #define USB_DEVICE_ID_KYE_EASYPEN_I405X        0x5010
 #define USB_DEVICE_ID_KYE_MOUSEPEN_I608X       0x5011
-#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2    0x501a
+#define USB_DEVICE_ID_KYE_EASYPEN_M406W        0x5012
 #define USB_DEVICE_ID_KYE_EASYPEN_M610X        0x5013
+#define USB_DEVICE_ID_KYE_EASYPEN_340  0x5014
 #define USB_DEVICE_ID_KYE_PENSKETCH_M912       0x5015
+#define USB_DEVICE_ID_KYE_MOUSEPEN_M508WX      0x5016
+#define USB_DEVICE_ID_KYE_MOUSEPEN_M508X       0x5017
 #define USB_DEVICE_ID_KYE_EASYPEN_M406XE       0x5019
+#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2    0x501A
+#define USB_DEVICE_ID_KYE_PENSKETCH_T609A      0x501B
 
 #define USB_VENDOR_ID_LABTEC           0x1020
 #define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD 0x0006
index 5c65a58..a1d2690 100644 (file)
@@ -372,6 +372,8 @@ static const struct hid_device_id hid_battery_quirks[] = {
          HID_BATTERY_QUIRK_IGNORE },
        { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_ASUS_TP420IA_TOUCHSCREEN),
          HID_BATTERY_QUIRK_IGNORE },
+       { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_ASUS_GV301RA_TOUCHSCREEN),
+         HID_BATTERY_QUIRK_IGNORE },
        { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN),
          HID_BATTERY_QUIRK_IGNORE },
        { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN),
@@ -1267,6 +1269,16 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
                        return;
                }
                goto unknown;
+       case HID_UP_CAMERA:
+               switch (usage->hid & HID_USAGE) {
+               case 0x020:
+                       map_key_clear(KEY_CAMERA_FOCUS);        break;
+               case 0x021:
+                       map_key_clear(KEY_CAMERA);              break;
+               default:
+                       goto ignore;
+               }
+               break;
 
        case HID_UP_HPVENDOR:   /* Reported on a Dutch layout HP5308 */
                set_bit(EV_REP, input->evbit);
index da90313..eb9bf28 100644 (file)
  *  Copyright (c) 2009 Jiri Kosina
  *  Copyright (c) 2009 Tomas Hanak
  *  Copyright (c) 2012 Nikolai Kondrashov
+ *  Copyright (c) 2023 David Yang
  */
 
-/*
- */
-
+#include <asm-generic/unaligned.h>
 #include <linux/device.h>
 #include <linux/hid.h>
 #include <linux/module.h>
 
 #include "hid-ids.h"
 
-/* Original EasyPen i405X report descriptor size */
-#define EASYPEN_I405X_RDESC_ORIG_SIZE  476
-
-/* Fixed EasyPen i405X report descriptor */
-static __u8 easypen_i405x_rdesc_fixed[] = {
-       0x06, 0x00, 0xFF, /*  Usage Page (FF00h),             */
-       0x09, 0x01,       /*  Usage (01h),                    */
-       0xA1, 0x01,       /*  Collection (Application),       */
-       0x85, 0x05,       /*    Report ID (5),                */
-       0x09, 0x01,       /*    Usage (01h),                  */
-       0x15, 0x80,       /*    Logical Minimum (-128),       */
-       0x25, 0x7F,       /*    Logical Maximum (127),        */
-       0x75, 0x08,       /*    Report Size (8),              */
-       0x95, 0x07,       /*    Report Count (7),             */
-       0xB1, 0x02,       /*    Feature (Variable),           */
-       0xC0,             /*  End Collection,                 */
-       0x05, 0x0D,       /*  Usage Page (Digitizer),         */
-       0x09, 0x01,       /*  Usage (Digitizer),              */
-       0xA1, 0x01,       /*  Collection (Application),       */
-       0x85, 0x10,       /*    Report ID (16),               */
-       0x09, 0x20,       /*    Usage (Stylus),               */
-       0xA0,             /*    Collection (Physical),        */
-       0x14,             /*      Logical Minimum (0),        */
-       0x25, 0x01,       /*      Logical Maximum (1),        */
-       0x75, 0x01,       /*      Report Size (1),            */
-       0x09, 0x42,       /*      Usage (Tip Switch),         */
-       0x09, 0x44,       /*      Usage (Barrel Switch),      */
-       0x09, 0x46,       /*      Usage (Tablet Pick),        */
-       0x95, 0x03,       /*      Report Count (3),           */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0x95, 0x04,       /*      Report Count (4),           */
-       0x81, 0x03,       /*      Input (Constant, Variable), */
-       0x09, 0x32,       /*      Usage (In Range),           */
-       0x95, 0x01,       /*      Report Count (1),           */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0x75, 0x10,       /*      Report Size (16),           */
-       0x95, 0x01,       /*      Report Count (1),           */
-       0xA4,             /*      Push,                       */
-       0x05, 0x01,       /*      Usage Page (Desktop),       */
-       0x55, 0xFD,       /*      Unit Exponent (-3),         */
-       0x65, 0x13,       /*      Unit (Inch),                */
-       0x34,             /*      Physical Minimum (0),       */
-       0x09, 0x30,       /*      Usage (X),                  */
-       0x46, 0x7C, 0x15, /*      Physical Maximum (5500),    */
-       0x26, 0x00, 0x37, /*      Logical Maximum (14080),    */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0x09, 0x31,       /*      Usage (Y),                  */
-       0x46, 0xA0, 0x0F, /*      Physical Maximum (4000),    */
-       0x26, 0x00, 0x28, /*      Logical Maximum (10240),    */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0xB4,             /*      Pop,                        */
-       0x09, 0x30,       /*      Usage (Tip Pressure),       */
-       0x26, 0xFF, 0x03, /*      Logical Maximum (1023),     */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0xC0,             /*    End Collection,               */
-       0xC0              /*  End Collection                  */
+/* Data gathered from Database/VID0458_PID????/Vista/TBoard/default.xml in ioTablet driver
+ *
+ * TODO:
+ *   - Add battery and sleep support for EasyPen M406W and MousePen M508WX
+ *   - Investigate ScrollZ.MiceFMT buttons of EasyPen M406
+ */
+
+static const __u8 easypen_m406_control_rdesc[] = {
+       0x05, 0x0C,        /*  Usage Page (Consumer),    */
+       0x09, 0x01,        /*  Usage (Consumer Control), */
+       0xA1, 0x01,        /*  Collection (Application), */
+       0x85, 0x12,        /*    Report ID (18),         */
+       0x0A, 0x45, 0x02,  /*    Usage (AC Rotate),      */
+       0x09, 0x40,        /*    Usage (Menu),           */
+       0x0A, 0x2F, 0x02,  /*    Usage (AC Zoom),        */
+       0x0A, 0x46, 0x02,  /*    Usage (AC Resize),      */
+       0x0A, 0x1A, 0x02,  /*    Usage (AC Undo),        */
+       0x0A, 0x6A, 0x02,  /*    Usage (AC Delete),      */
+       0x0A, 0x24, 0x02,  /*    Usage (AC Back),        */
+       0x0A, 0x25, 0x02,  /*    Usage (AC Forward),     */
+       0x14,              /*    Logical Minimum (0),    */
+       0x25, 0x01,        /*    Logical Maximum (1),    */
+       0x75, 0x01,        /*    Report Size (1),        */
+       0x95, 0x08,        /*    Report Count (8),       */
+       0x81, 0x02,        /*    Input (Variable),       */
+       0x95, 0x30,        /*    Report Count (48),      */
+       0x81, 0x01,        /*    Input (Constant),       */
+       0xC0               /*  End Collection            */
 };
 
-/* Original MousePen i608X report descriptor size */
-#define MOUSEPEN_I608X_RDESC_ORIG_SIZE 476
-
-/* Fixed MousePen i608X report descriptor */
-static __u8 mousepen_i608x_rdesc_fixed[] = {
-       0x06, 0x00, 0xFF, /*  Usage Page (FF00h),             */
-       0x09, 0x01,       /*  Usage (01h),                    */
-       0xA1, 0x01,       /*  Collection (Application),       */
-       0x85, 0x05,       /*    Report ID (5),                */
-       0x09, 0x01,       /*    Usage (01h),                  */
-       0x15, 0x80,       /*    Logical Minimum (-128),       */
-       0x25, 0x7F,       /*    Logical Maximum (127),        */
-       0x75, 0x08,       /*    Report Size (8),              */
-       0x95, 0x07,       /*    Report Count (7),             */
-       0xB1, 0x02,       /*    Feature (Variable),           */
-       0xC0,             /*  End Collection,                 */
-       0x05, 0x0D,       /*  Usage Page (Digitizer),         */
-       0x09, 0x01,       /*  Usage (Digitizer),              */
-       0xA1, 0x01,       /*  Collection (Application),       */
-       0x85, 0x10,       /*    Report ID (16),               */
-       0x09, 0x20,       /*    Usage (Stylus),               */
-       0xA0,             /*    Collection (Physical),        */
-       0x14,             /*      Logical Minimum (0),        */
-       0x25, 0x01,       /*      Logical Maximum (1),        */
-       0x75, 0x01,       /*      Report Size (1),            */
-       0x09, 0x42,       /*      Usage (Tip Switch),         */
-       0x09, 0x44,       /*      Usage (Barrel Switch),      */
-       0x09, 0x46,       /*      Usage (Tablet Pick),        */
-       0x95, 0x03,       /*      Report Count (3),           */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0x95, 0x04,       /*      Report Count (4),           */
-       0x81, 0x03,       /*      Input (Constant, Variable), */
-       0x09, 0x32,       /*      Usage (In Range),           */
-       0x95, 0x01,       /*      Report Count (1),           */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0x75, 0x10,       /*      Report Size (16),           */
-       0x95, 0x01,       /*      Report Count (1),           */
-       0xA4,             /*      Push,                       */
-       0x05, 0x01,       /*      Usage Page (Desktop),       */
-       0x55, 0xFD,       /*      Unit Exponent (-3),         */
-       0x65, 0x13,       /*      Unit (Inch),                */
-       0x34,             /*      Physical Minimum (0),       */
-       0x09, 0x30,       /*      Usage (X),                  */
-       0x46, 0x40, 0x1F, /*      Physical Maximum (8000),    */
-       0x26, 0x00, 0x50, /*      Logical Maximum (20480),    */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0x09, 0x31,       /*      Usage (Y),                  */
-       0x46, 0x70, 0x17, /*      Physical Maximum (6000),    */
-       0x26, 0x00, 0x3C, /*      Logical Maximum (15360),    */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0xB4,             /*      Pop,                        */
-       0x09, 0x30,       /*      Usage (Tip Pressure),       */
-       0x26, 0xFF, 0x03, /*      Logical Maximum (1023),     */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0xC0,             /*    End Collection,               */
-       0xC0,             /*  End Collection,                 */
-       0x05, 0x01,       /*  Usage Page (Desktop),           */
-       0x09, 0x02,       /*  Usage (Mouse),                  */
-       0xA1, 0x01,       /*  Collection (Application),       */
-       0x85, 0x11,       /*    Report ID (17),               */
-       0x09, 0x01,       /*    Usage (Pointer),              */
-       0xA0,             /*    Collection (Physical),        */
-       0x14,             /*      Logical Minimum (0),        */
-       0xA4,             /*      Push,                       */
-       0x05, 0x09,       /*      Usage Page (Button),        */
-       0x75, 0x01,       /*      Report Size (1),            */
-       0x19, 0x01,       /*      Usage Minimum (01h),        */
-       0x29, 0x03,       /*      Usage Maximum (03h),        */
-       0x25, 0x01,       /*      Logical Maximum (1),        */
-       0x95, 0x03,       /*      Report Count (3),           */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0x95, 0x05,       /*      Report Count (5),           */
-       0x81, 0x01,       /*      Input (Constant),           */
-       0xB4,             /*      Pop,                        */
-       0x95, 0x01,       /*      Report Count (1),           */
-       0xA4,             /*      Push,                       */
-       0x55, 0xFD,       /*      Unit Exponent (-3),         */
-       0x65, 0x13,       /*      Unit (Inch),                */
-       0x34,             /*      Physical Minimum (0),       */
-       0x75, 0x10,       /*      Report Size (16),           */
-       0x09, 0x30,       /*      Usage (X),                  */
-       0x46, 0x40, 0x1F, /*      Physical Maximum (8000),    */
-       0x26, 0x00, 0x50, /*      Logical Maximum (20480),    */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0x09, 0x31,       /*      Usage (Y),                  */
-       0x46, 0x70, 0x17, /*      Physical Maximum (6000),    */
-       0x26, 0x00, 0x3C, /*      Logical Maximum (15360),    */
-       0x81, 0x02,       /*      Input (Variable),           */
-       0xB4,             /*      Pop,                        */
-       0x75, 0x08,       /*      Report Size (8),            */
-       0x09, 0x38,       /*      Usage (Wheel),              */
-       0x15, 0xFF,       /*      Logical Minimum (-1),       */
-       0x25, 0x01,       /*      Logical Maximum (1),        */
-       0x81, 0x06,       /*      Input (Variable, Relative), */
-       0x81, 0x01,       /*      Input (Constant),           */
-       0xC0,             /*    End Collection,               */
-       0xC0              /*  End Collection                  */
+static const __u8 easypen_m506_control_rdesc[] = {
+       0x05, 0x0C,        /*  Usage Page (Consumer),    */
+       0x09, 0x01,        /*  Usage (Consumer Control), */
+       0xA1, 0x01,        /*  Collection (Application), */
+       0x85, 0x12,        /*    Report ID (18),         */
+       0x0A, 0x6A, 0x02,  /*    Usage (AC Delete),      */
+       0x0A, 0x1A, 0x02,  /*    Usage (AC Undo),        */
+       0x0A, 0x2D, 0x02,  /*    Usage (AC Zoom In),     */
+       0x0A, 0x2E, 0x02,  /*    Usage (AC Zoom Out),    */
+       0x14,              /*    Logical Minimum (0),    */
+       0x25, 0x01,        /*    Logical Maximum (1),    */
+       0x75, 0x01,        /*    Report Size (1),        */
+       0x95, 0x04,        /*    Report Count (4),       */
+       0x81, 0x02,        /*    Input (Variable),       */
+       0x95, 0x34,        /*    Report Count (52),      */
+       0x81, 0x01,        /*    Input (Constant),       */
+       0xC0               /*  End Collection            */
 };
 
-/* Original MousePen i608X v2 report descriptor size */
-#define MOUSEPEN_I608X_V2_RDESC_ORIG_SIZE      482
+static const __u8 easypen_m406w_control_rdesc[] = {
+       0x05, 0x0C,        /*  Usage Page (Consumer),    */
+       0x09, 0x01,        /*  Usage (Consumer Control), */
+       0xA1, 0x01,        /*  Collection (Application), */
+       0x85, 0x12,        /*    Report ID (18),         */
+       0x0A, 0x6A, 0x02,  /*    Usage (AC Delete),      */
+       0x0A, 0x1A, 0x02,  /*    Usage (AC Undo),        */
+       0x0A, 0x01, 0x02,  /*    Usage (AC New),         */
+       0x09, 0x40,        /*    Usage (Menu),           */
+       0x14,              /*    Logical Minimum (0),    */
+       0x25, 0x01,        /*    Logical Maximum (1),    */
+       0x75, 0x01,        /*    Report Size (1),        */
+       0x95, 0x04,        /*    Report Count (4),       */
+       0x81, 0x02,        /*    Input (Variable),       */
+       0x95, 0x34,        /*    Report Count (52),      */
+       0x81, 0x01,        /*    Input (Constant),       */
+       0xC0               /*  End Collection            */
+};
 
-/* Fixed MousePen i608X v2 report descriptor */
-static __u8 mousepen_i608x_v2_rdesc_fixed[] = {
-       0x06, 0x00, 0xFF,             /*  Usage Page (FF00h),             */
-       0x09, 0x01,                   /*  Usage (01h),                    */
-       0xA1, 0x01,                   /*  Collection (Application),       */
-       0x85, 0x05,                   /*    Report ID (5),                */
-       0x09, 0x01,                   /*    Usage (01h),                  */
-       0x15, 0x80,                   /*    Logical Minimum (-128),       */
-       0x25, 0x7F,                   /*    Logical Maximum (127),        */
-       0x75, 0x08,                   /*    Report Size (8),              */
-       0x95, 0x07,                   /*    Report Count (7),             */
-       0xB1, 0x02,                   /*    Feature (Variable),           */
-       0xC0,                         /*  End Collection,                 */
-       0x05, 0x0D,                   /*  Usage Page (Digitizer),         */
-       0x09, 0x01,                   /*  Usage (Digitizer),              */
-       0xA1, 0x01,                   /*  Collection (Application),       */
-       0x85, 0x10,                   /*    Report ID (16),               */
-       0x09, 0x20,                   /*    Usage (Stylus),               */
-       0xA0,                         /*    Collection (Physical),        */
-       0x14,                         /*      Logical Minimum (0),        */
-       0x25, 0x01,                   /*      Logical Maximum (1),        */
-       0x75, 0x01,                   /*      Report Size (1),            */
-       0x09, 0x42,                   /*      Usage (Tip Switch),         */
-       0x09, 0x44,                   /*      Usage (Barrel Switch),      */
-       0x09, 0x46,                   /*      Usage (Tablet Pick),        */
-       0x95, 0x03,                   /*      Report Count (3),           */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0x95, 0x04,                   /*      Report Count (4),           */
-       0x81, 0x03,                   /*      Input (Constant, Variable), */
-       0x09, 0x32,                   /*      Usage (In Range),           */
-       0x95, 0x01,                   /*      Report Count (1),           */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0x75, 0x10,                   /*      Report Size (16),           */
-       0x95, 0x01,                   /*      Report Count (1),           */
-       0xA4,                         /*      Push,                       */
-       0x05, 0x01,                   /*      Usage Page (Desktop),       */
-       0x55, 0xFD,                   /*      Unit Exponent (-3),         */
-       0x65, 0x13,                   /*      Unit (Inch),                */
-       0x34,                         /*      Physical Minimum (0),       */
-       0x09, 0x30,                   /*      Usage (X),                  */
-       0x46, 0x40, 0x1F,             /*      Physical Maximum (8000),    */
-       0x27, 0x00, 0xA0, 0x00, 0x00, /*      Logical Maximum (40960),    */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0x09, 0x31,                   /*      Usage (Y),                  */
-       0x46, 0x70, 0x17,             /*      Physical Maximum (6000),    */
-       0x26, 0x00, 0x78,             /*      Logical Maximum (30720),    */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0xB4,                         /*      Pop,                        */
-       0x09, 0x30,                   /*      Usage (Tip Pressure),       */
-       0x26, 0xFF, 0x07,             /*      Logical Maximum (2047),     */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0xC0,                         /*    End Collection,               */
-       0xC0,                         /*  End Collection,                 */
-       0x05, 0x01,                   /*  Usage Page (Desktop),           */
-       0x09, 0x02,                   /*  Usage (Mouse),                  */
-       0xA1, 0x01,                   /*  Collection (Application),       */
-       0x85, 0x11,                   /*    Report ID (17),               */
-       0x09, 0x01,                   /*    Usage (Pointer),              */
-       0xA0,                         /*    Collection (Physical),        */
-       0x14,                         /*      Logical Minimum (0),        */
-       0xA4,                         /*      Push,                       */
-       0x05, 0x09,                   /*      Usage Page (Button),        */
-       0x75, 0x01,                   /*      Report Size (1),            */
-       0x19, 0x01,                   /*      Usage Minimum (01h),        */
-       0x29, 0x03,                   /*      Usage Maximum (03h),        */
-       0x25, 0x01,                   /*      Logical Maximum (1),        */
-       0x95, 0x03,                   /*      Report Count (3),           */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0x95, 0x05,                   /*      Report Count (5),           */
-       0x81, 0x01,                   /*      Input (Constant),           */
-       0xB4,                         /*      Pop,                        */
-       0x95, 0x01,                   /*      Report Count (1),           */
-       0xA4,                         /*      Push,                       */
-       0x55, 0xFD,                   /*      Unit Exponent (-3),         */
-       0x65, 0x13,                   /*      Unit (Inch),                */
-       0x34,                         /*      Physical Minimum (0),       */
-       0x75, 0x10,                   /*      Report Size (16),           */
-       0x09, 0x30,                   /*      Usage (X),                  */
-       0x46, 0x40, 0x1F,             /*      Physical Maximum (8000),    */
-       0x27, 0x00, 0xA0, 0x00, 0x00, /*      Logical Maximum (40960),    */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0x09, 0x31,                   /*      Usage (Y),                  */
-       0x46, 0x70, 0x17,             /*      Physical Maximum (6000),    */
-       0x26, 0x00, 0x78,             /*      Logical Maximum (30720),    */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0xB4,                         /*      Pop,                        */
-       0x75, 0x08,                   /*      Report Size (8),            */
-       0x09, 0x38,                   /*      Usage (Wheel),              */
-       0x15, 0xFF,                   /*      Logical Minimum (-1),       */
-       0x25, 0x01,                   /*      Logical Maximum (1),        */
-       0x81, 0x06,                   /*      Input (Variable, Relative), */
-       0x81, 0x01,                   /*      Input (Constant),           */
-       0xC0,                         /*    End Collection,               */
-       0xC0                          /*  End Collection                  */
+static const __u8 easypen_m610x_control_rdesc[] = {
+       0x05, 0x0C,        /*  Usage Page (Consumer),       */
+       0x09, 0x01,        /*  Usage (Consumer Control),    */
+       0xA1, 0x01,        /*  Collection (Application),    */
+       0x85, 0x12,        /*    Report ID (18),            */
+       0x0A, 0x1A, 0x02,  /*    Usage (AC Undo),           */
+       0x0A, 0x79, 0x02,  /*    Usage (AC Redo Or Repeat), */
+       0x0A, 0x2D, 0x02,  /*    Usage (AC Zoom In),        */
+       0x0A, 0x2E, 0x02,  /*    Usage (AC Zoom Out),       */
+       0x14,              /*    Logical Minimum (0),       */
+       0x25, 0x01,        /*    Logical Maximum (1),       */
+       0x75, 0x01,        /*    Report Size (1),           */
+       0x95, 0x04,        /*    Report Count (4),          */
+       0x81, 0x02,        /*    Input (Variable),          */
+       0x95, 0x34,        /*    Report Count (52),         */
+       0x81, 0x01,        /*    Input (Constant),          */
+       0xC0               /*  End Collection               */
 };
 
-/* Original EasyPen M610X report descriptor size */
-#define EASYPEN_M610X_RDESC_ORIG_SIZE  476
+static const __u8 pensketch_m912_control_rdesc[] = {
+       0x05, 0x0C,        /*  Usage Page (Consumer),        */
+       0x09, 0x01,        /*  Usage (Consumer Control),     */
+       0xA1, 0x01,        /*  Collection (Application),     */
+       0x85, 0x12,        /*    Report ID (18),             */
+       0x14,              /*    Logical Minimum (0),        */
+       0x25, 0x01,        /*    Logical Maximum (1),        */
+       0x75, 0x01,        /*    Report Size (1),            */
+       0x95, 0x08,        /*    Report Count (8),           */
+       0x05, 0x0C,        /*    Usage Page (Consumer),      */
+       0x0A, 0x6A, 0x02,  /*    Usage (AC Delete),          */
+       0x0A, 0x1A, 0x02,  /*    Usage (AC Undo),            */
+       0x0A, 0x01, 0x02,  /*    Usage (AC New),             */
+       0x0A, 0x2F, 0x02,  /*    Usage (AC Zoom),            */
+       0x0A, 0x25, 0x02,  /*    Usage (AC Forward),         */
+       0x0A, 0x24, 0x02,  /*    Usage (AC Back),            */
+       0x0A, 0x2D, 0x02,  /*    Usage (AC Zoom In),         */
+       0x0A, 0x2E, 0x02,  /*    Usage (AC Zoom Out),        */
+       0x81, 0x02,        /*    Input (Variable),           */
+       0x95, 0x30,        /*    Report Count (48),          */
+       0x81, 0x03,        /*    Input (Constant, Variable), */
+       0xC0               /*  End Collection                */
+};
 
-/* Fixed EasyPen M610X report descriptor */
-static __u8 easypen_m610x_rdesc_fixed[] = {
-       0x06, 0x00, 0xFF,             /*  Usage Page (FF00h),             */
-       0x09, 0x01,                   /*  Usage (01h),                    */
-       0xA1, 0x01,                   /*  Collection (Application),       */
-       0x85, 0x05,                   /*    Report ID (5),                */
-       0x09, 0x01,                   /*    Usage (01h),                  */
-       0x15, 0x80,                   /*    Logical Minimum (-128),       */
-       0x25, 0x7F,                   /*    Logical Maximum (127),        */
-       0x75, 0x08,                   /*    Report Size (8),              */
-       0x95, 0x07,                   /*    Report Count (7),             */
-       0xB1, 0x02,                   /*    Feature (Variable),           */
-       0xC0,                         /*  End Collection,                 */
-       0x05, 0x0D,                   /*  Usage Page (Digitizer),         */
-       0x09, 0x01,                   /*  Usage (Digitizer),              */
-       0xA1, 0x01,                   /*  Collection (Application),       */
-       0x85, 0x10,                   /*    Report ID (16),               */
-       0x09, 0x20,                   /*    Usage (Stylus),               */
-       0xA0,                         /*    Collection (Physical),        */
-       0x14,                         /*      Logical Minimum (0),        */
-       0x25, 0x01,                   /*      Logical Maximum (1),        */
-       0x75, 0x01,                   /*      Report Size (1),            */
-       0x09, 0x42,                   /*      Usage (Tip Switch),         */
-       0x09, 0x44,                   /*      Usage (Barrel Switch),      */
-       0x09, 0x46,                   /*      Usage (Tablet Pick),        */
-       0x95, 0x03,                   /*      Report Count (3),           */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0x95, 0x04,                   /*      Report Count (4),           */
-       0x81, 0x03,                   /*      Input (Constant, Variable), */
-       0x09, 0x32,                   /*      Usage (In Range),           */
-       0x95, 0x01,                   /*      Report Count (1),           */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0x75, 0x10,                   /*      Report Size (16),           */
-       0x95, 0x01,                   /*      Report Count (1),           */
-       0xA4,                         /*      Push,                       */
-       0x05, 0x01,                   /*      Usage Page (Desktop),       */
-       0x55, 0xFD,                   /*      Unit Exponent (-3),         */
-       0x65, 0x13,                   /*      Unit (Inch),                */
-       0x34,                         /*      Physical Minimum (0),       */
-       0x09, 0x30,                   /*      Usage (X),                  */
-       0x46, 0x10, 0x27,             /*      Physical Maximum (10000),   */
-       0x27, 0x00, 0xA0, 0x00, 0x00, /*      Logical Maximum (40960),    */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0x09, 0x31,                   /*      Usage (Y),                  */
-       0x46, 0x6A, 0x18,             /*      Physical Maximum (6250),    */
-       0x26, 0x00, 0x64,             /*      Logical Maximum (25600),    */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0xB4,                         /*      Pop,                        */
-       0x09, 0x30,                   /*      Usage (Tip Pressure),       */
-       0x26, 0xFF, 0x03,             /*      Logical Maximum (1023),     */
-       0x81, 0x02,                   /*      Input (Variable),           */
-       0xC0,                         /*    End Collection,               */
-       0xC0,                         /*  End Collection,                 */
-       0x05, 0x0C,                   /*  Usage Page (Consumer),          */
-       0x09, 0x01,                   /*  Usage (Consumer Control),       */
-       0xA1, 0x01,                   /*  Collection (Application),       */
-       0x85, 0x12,                   /*    Report ID (18),               */
-       0x14,                         /*    Logical Minimum (0),          */
-       0x25, 0x01,                   /*    Logical Maximum (1),          */
-       0x75, 0x01,                   /*    Report Size (1),              */
-       0x95, 0x04,                   /*    Report Count (4),             */
-       0x0A, 0x1A, 0x02,             /*    Usage (AC Undo),              */
-       0x0A, 0x79, 0x02,             /*    Usage (AC Redo Or Repeat),    */
-       0x0A, 0x2D, 0x02,             /*    Usage (AC Zoom In),           */
-       0x0A, 0x2E, 0x02,             /*    Usage (AC Zoom Out),          */
-       0x81, 0x02,                   /*    Input (Variable),             */
-       0x95, 0x01,                   /*    Report Count (1),             */
-       0x75, 0x14,                   /*    Report Size (20),             */
-       0x81, 0x03,                   /*    Input (Constant, Variable),   */
-       0x75, 0x20,                   /*    Report Size (32),             */
-       0x81, 0x03,                   /*    Input (Constant, Variable),   */
-       0xC0                          /*  End Collection                  */
+static const __u8 mousepen_m508wx_control_rdesc[] = {
+       0x05, 0x0C,        /*  Usage Page (Consumer),    */
+       0x09, 0x01,        /*  Usage (Consumer Control), */
+       0xA1, 0x01,        /*  Collection (Application), */
+       0x85, 0x12,        /*    Report ID (18),         */
+       0x0A, 0x1A, 0x02,  /*    Usage (AC Undo),        */
+       0x0A, 0x6A, 0x02,  /*    Usage (AC Delete),      */
+       0x0A, 0x2D, 0x02,  /*    Usage (AC Zoom In),     */
+       0x0A, 0x2E, 0x02,  /*    Usage (AC Zoom Out),    */
+       0x14,              /*    Logical Minimum (0),    */
+       0x25, 0x01,        /*    Logical Maximum (1),    */
+       0x75, 0x01,        /*    Report Size (1),        */
+       0x95, 0x04,        /*    Report Count (4),       */
+       0x81, 0x02,        /*    Input (Variable),       */
+       0x95, 0x34,        /*    Report Count (52),      */
+       0x81, 0x01,        /*    Input (Constant),       */
+       0xC0               /*  End Collection            */
 };
 
+static const __u8 mousepen_m508x_control_rdesc[] = {
+       0x05, 0x0C,        /*  Usage Page (Consumer),        */
+       0x09, 0x01,        /*  Usage (Consumer Control),     */
+       0xA1, 0x01,        /*  Collection (Application),     */
+       0x85, 0x12,        /*    Report ID (18),             */
+       0x0A, 0x01, 0x02,  /*    Usage (AC New),             */
+       0x09, 0x40,        /*    Usage (Menu),               */
+       0x0A, 0x6A, 0x02,  /*    Usage (AC Delete),          */
+       0x0A, 0x1A, 0x02,  /*    Usage (AC Undo),            */
+       0x14,              /*    Logical Minimum (0),        */
+       0x25, 0x01,        /*    Logical Maximum (1),        */
+       0x75, 0x01,        /*    Report Size (1),            */
+       0x95, 0x04,        /*    Report Count (4),           */
+       0x81, 0x02,        /*    Input (Variable),           */
+       0x81, 0x01,        /*    Input (Constant),           */
+       0x15, 0xFF,        /*    Logical Minimum (-1),       */
+       0x95, 0x10,        /*    Report Count (16),          */
+       0x81, 0x01,        /*    Input (Constant),           */
+       0x0A, 0x35, 0x02,  /*    Usage (AC Scroll),          */
+       0x0A, 0x2F, 0x02,  /*    Usage (AC Zoom),            */
+       0x0A, 0x38, 0x02,  /*    Usage (AC Pan),             */
+       0x75, 0x08,        /*    Report Size (8),            */
+       0x95, 0x03,        /*    Report Count (3),           */
+       0x81, 0x06,        /*    Input (Variable, Relative), */
+       0x95, 0x01,        /*    Report Count (1),           */
+       0x81, 0x01,        /*    Input (Constant),           */
+       0xC0               /*  End Collection                */
+};
+
+static const __u8 easypen_m406xe_control_rdesc[] = {
+       0x05, 0x0C,        /*  Usage Page (Consumer),          */
+       0x09, 0x01,        /*  Usage (Consumer Control),       */
+       0xA1, 0x01,        /*  Collection (Application),       */
+       0x85, 0x12,        /*      Report ID (18),             */
+       0x14,              /*      Logical Minimum (0),        */
+       0x25, 0x01,        /*      Logical Maximum (1),        */
+       0x75, 0x01,        /*      Report Size (1),            */
+       0x95, 0x04,        /*      Report Count (4),           */
+       0x0A, 0x79, 0x02,  /*      Usage (AC Redo Or Repeat),  */
+       0x0A, 0x1A, 0x02,  /*      Usage (AC Undo),            */
+       0x0A, 0x2D, 0x02,  /*      Usage (AC Zoom In),         */
+       0x0A, 0x2E, 0x02,  /*      Usage (AC Zoom Out),        */
+       0x81, 0x02,        /*      Input (Variable),           */
+       0x95, 0x34,        /*      Report Count (52),          */
+       0x81, 0x03,        /*      Input (Constant, Variable), */
+       0xC0               /*  End Collection                  */
+};
 
-/* Original PenSketch M912 report descriptor size */
-#define PENSKETCH_M912_RDESC_ORIG_SIZE 482
+static const __u8 pensketch_t609a_control_rdesc[] = {
+       0x05, 0x0C,        /*  Usage Page (Consumer),    */
+       0x09, 0x01,        /*  Usage (Consumer Control), */
+       0xA1, 0x01,        /*  Collection (Application), */
+       0x85, 0x12,        /*    Report ID (18),         */
+       0x0A, 0x6A, 0x02,  /*    Usage (AC Delete),      */
+       0x14,              /*    Logical Minimum (0),    */
+       0x25, 0x01,        /*    Logical Maximum (1),    */
+       0x75, 0x01,        /*    Report Size (1),        */
+       0x95, 0x08,        /*    Report Count (8),       */
+       0x81, 0x02,        /*    Input (Variable),       */
+       0x95, 0x37,        /*    Report Count (55),      */
+       0x81, 0x01,        /*    Input (Constant),       */
+       0xC0               /*  End Collection            */
+};
 
-/* Fixed PenSketch M912 report descriptor */
-static __u8 pensketch_m912_rdesc_fixed[] = {
-       0x05, 0x01,                   /*  Usage Page (Desktop),           */
-       0x08,                         /*  Usage (00h),                    */
+/* Fix indexes in kye_tablet_fixup if you change this */
+static const __u8 kye_tablet_rdesc[] = {
+       0x06, 0x00, 0xFF,             /*  Usage Page (FF00h),             */
+       0x09, 0x01,                   /*  Usage (01h),                    */
        0xA1, 0x01,                   /*  Collection (Application),       */
        0x85, 0x05,                   /*    Report ID (5),                */
-       0x06, 0x00, 0xFF,             /*    Usage Page (FF00h),           */
        0x09, 0x01,                   /*    Usage (01h),                  */
        0x15, 0x81,                   /*    Logical Minimum (-127),       */
        0x25, 0x7F,                   /*    Logical Maximum (127),        */
@@ -382,30 +237,29 @@ static __u8 pensketch_m912_rdesc_fixed[] = {
        0x95, 0x03,                   /*      Report Count (3),           */
        0x81, 0x02,                   /*      Input (Variable),           */
        0x95, 0x04,                   /*      Report Count (4),           */
-       0x81, 0x03,                   /*      Input (Constant, Variable), */
+       0x81, 0x01,                   /*      Input (Constant),           */
        0x09, 0x32,                   /*      Usage (In Range),           */
        0x95, 0x01,                   /*      Report Count (1),           */
        0x81, 0x02,                   /*      Input (Variable),           */
        0x75, 0x10,                   /*      Report Size (16),           */
-       0x95, 0x01,                   /*      Report Count (1),           */
        0xA4,                         /*      Push,                       */
        0x05, 0x01,                   /*      Usage Page (Desktop),       */
-       0x55, 0xFD,                   /*      Unit Exponent (-3),         */
-       0x65, 0x13,                   /*      Unit (Inch),                */
-       0x14,                         /*      Logical Minimum (0),        */
-       0x34,                         /*      Physical Minimum (0),       */
        0x09, 0x30,                   /*      Usage (X),                  */
-       0x27, 0x00, 0xF0, 0x00, 0x00, /*      Logical Maximum (61440),    */
-       0x46, 0xE0, 0x2E,             /*      Physical Maximum (12000),   */
+       0x27, 0xFF, 0x7F, 0x00, 0x00, /*      Logical Maximum (32767),    */
+       0x34,                         /*      Physical Minimum (0),       */
+       0x47, 0x00, 0x00, 0x00, 0x00, /*      Physical Maximum (0),       */
+       0x65, 0x11,                   /*      Unit (Centimeter),          */
+       0x55, 0x00,                   /*      Unit Exponent (0),          */
+       0x75, 0x10,                   /*      Report Size (16),           */
        0x81, 0x02,                   /*      Input (Variable),           */
        0x09, 0x31,                   /*      Usage (Y),                  */
-       0x27, 0x00, 0xB4, 0x00, 0x00, /*      Logical Maximum (46080),    */
-       0x46, 0x28, 0x23,             /*      Physical Maximum (9000),    */
+       0x27, 0xFF, 0x7F, 0x00, 0x00, /*      Logical Maximum (32767),    */
+       0x47, 0x00, 0x00, 0x00, 0x00, /*      Physical Maximum (0),       */
        0x81, 0x02,                   /*      Input (Variable),           */
        0xB4,                         /*      Pop,                        */
+       0x05, 0x0D,                   /*      Usage Page (Digitizer),     */
        0x09, 0x30,                   /*      Usage (Tip Pressure),       */
-       0x14,                         /*      Logical Minimum (0),        */
-       0x26, 0xFF, 0x07,             /*      Logical Maximum (2047),     */
+       0x27, 0xFF, 0x07, 0x00, 0x00, /*      Logical Maximum (2047),     */
        0x81, 0x02,                   /*      Input (Variable),           */
        0xC0,                         /*    End Collection,               */
        0xC0,                         /*  End Collection,                 */
@@ -416,146 +270,98 @@ static __u8 pensketch_m912_rdesc_fixed[] = {
        0x09, 0x21,                   /*    Usage (Puck),                 */
        0xA0,                         /*    Collection (Physical),        */
        0x05, 0x09,                   /*      Usage Page (Button),        */
-       0x75, 0x01,                   /*      Report Size (1),            */
        0x19, 0x01,                   /*      Usage Minimum (01h),        */
        0x29, 0x03,                   /*      Usage Maximum (03h),        */
        0x14,                         /*      Logical Minimum (0),        */
        0x25, 0x01,                   /*      Logical Maximum (1),        */
+       0x75, 0x01,                   /*      Report Size (1),            */
        0x95, 0x03,                   /*      Report Count (3),           */
        0x81, 0x02,                   /*      Input (Variable),           */
        0x95, 0x04,                   /*      Report Count (4),           */
        0x81, 0x01,                   /*      Input (Constant),           */
+       0x05, 0x0D,                   /*      Usage Page (Digitizer),     */
+       0x09, 0x32,                   /*      Usage (In Range),           */
        0x95, 0x01,                   /*      Report Count (1),           */
-       0x0B, 0x32, 0x00, 0x0D, 0x00, /*      Usage (Digitizer In Range), */
-       0x14,                         /*      Logical Minimum (0),        */
-       0x25, 0x01,                   /*      Logical Maximum (1),        */
        0x81, 0x02,                   /*      Input (Variable),           */
-       0xA4,                         /*      Push,                       */
        0x05, 0x01,                   /*      Usage Page (Desktop),       */
-       0x75, 0x10,                   /*      Report Size (16),           */
-       0x95, 0x01,                   /*      Report Count (1),           */
-       0x55, 0xFD,                   /*      Unit Exponent (-3),         */
-       0x65, 0x13,                   /*      Unit (Inch),                */
-       0x14,                         /*      Logical Minimum (0),        */
-       0x34,                         /*      Physical Minimum (0),       */
+       0xA4,                         /*      Push,                       */
        0x09, 0x30,                   /*      Usage (X),                  */
-       0x27, 0x00, 0xF0, 0x00, 0x00, /*      Logical Maximum (61440),    */
-       0x46, 0xE0, 0x2E,             /*      Physical Maximum (12000),   */
+       0x27, 0xFF, 0x7F, 0x00, 0x00, /*      Logical Maximum (32767),    */
+       0x34,                         /*      Physical Minimum (0),       */
+       0x47, 0x00, 0x00, 0x00, 0x00, /*      Physical Maximum (0),       */
+       0x65, 0x11,                   /*      Unit (Centimeter),          */
+       0x55, 0x00,                   /*      Unit Exponent (0),          */
+       0x75, 0x10,                   /*      Report Size (16),           */
        0x81, 0x02,                   /*      Input (Variable),           */
        0x09, 0x31,                   /*      Usage (Y),                  */
-       0x27, 0x00, 0xB4, 0x00, 0x00, /*      Logical Maximum (46080),    */
-       0x46, 0x28, 0x23,             /*      Physical Maximum (9000),    */
+       0x27, 0xFF, 0x7F, 0x00, 0x00, /*      Logical Maximum (32767),    */
+       0x47, 0x00, 0x00, 0x00, 0x00, /*      Physical Maximum (0),       */
        0x81, 0x02,                   /*      Input (Variable),           */
+       0xB4,                         /*      Pop,                        */
        0x09, 0x38,                   /*      Usage (Wheel),              */
+       0x15, 0xFF,                   /*      Logical Minimum (-1),       */
        0x75, 0x08,                   /*      Report Size (8),            */
        0x95, 0x01,                   /*      Report Count (1),           */
-       0x15, 0xFF,                   /*      Logical Minimum (-1),       */
-       0x25, 0x01,                   /*      Logical Maximum (1),        */
-       0x34,                         /*      Physical Minimum (0),       */
-       0x44,                         /*      Physical Maximum (0),       */
        0x81, 0x06,                   /*      Input (Variable, Relative), */
-       0xB4,                         /*      Pop,                        */
+       0x81, 0x01,                   /*      Input (Constant),           */
        0xC0,                         /*    End Collection,               */
-       0xC0,                         /*  End Collection,                 */
-       0x05, 0x0C,                   /*  Usage Page (Consumer),          */
-       0x09, 0x01,                   /*  Usage (Consumer Control),       */
-       0xA1, 0x01,                   /*  Collection (Application),       */
-       0x85, 0x12,                   /*    Report ID (18),               */
-       0x14,                         /*    Logical Minimum (0),          */
-       0x25, 0x01,                   /*    Logical Maximum (1),          */
-       0x75, 0x01,                   /*    Report Size (1),              */
-       0x95, 0x08,                   /*    Report Count (8),             */
-       0x05, 0x0C,                   /*    Usage Page (Consumer),        */
-       0x0A, 0x6A, 0x02,             /*    Usage (AC Delete),            */
-       0x0A, 0x1A, 0x02,             /*    Usage (AC Undo),              */
-       0x0A, 0x01, 0x02,             /*    Usage (AC New),               */
-       0x0A, 0x2F, 0x02,             /*    Usage (AC Zoom),              */
-       0x0A, 0x25, 0x02,             /*    Usage (AC Forward),           */
-       0x0A, 0x24, 0x02,             /*    Usage (AC Back),              */
-       0x0A, 0x2D, 0x02,             /*    Usage (AC Zoom In),           */
-       0x0A, 0x2E, 0x02,             /*    Usage (AC Zoom Out),          */
-       0x81, 0x02,                   /*    Input (Variable),             */
-       0x95, 0x30,                   /*    Report Count (48),            */
-       0x81, 0x03,                   /*    Input (Constant, Variable),   */
        0xC0                          /*  End Collection                  */
 };
 
-/* Original EasyPen M406XE report descriptor size */
-#define EASYPEN_M406XE_RDESC_ORIG_SIZE 476
-
-/* Fixed EasyPen M406XE  report descriptor */
-static __u8 easypen_m406xe_rdesc_fixed[] = {
-       0x05, 0x01,         /*  Usage Page (Desktop),               */
-       0x09, 0x01,         /*  Usage (01h),                        */
-       0xA1, 0x01,         /*  Collection (Application),           */
-       0x85, 0x05,         /*      Report ID (5),                  */
-       0x09, 0x01,         /*      Usage (01h),                    */
-       0x15, 0x80,         /*      Logical Minimum (-128),         */
-       0x25, 0x7F,         /*      Logical Maximum (127),          */
-       0x75, 0x08,         /*      Report Size (8),                */
-       0x95, 0x07,         /*      Report Count (7),               */
-       0xB1, 0x02,         /*      Feature (Variable),             */
-       0xC0,               /*  End Collection,                     */
-       0x05, 0x0D,         /*  Usage Page (Digitizer),             */
-       0x09, 0x01,         /*  Usage (Digitizer),                  */
-       0xA1, 0x01,         /*  Collection (Application),           */
-       0x85, 0x10,         /*      Report ID (16),                 */
-       0x09, 0x20,         /*      Usage (Stylus),                 */
-       0xA0,               /*      Collection (Physical),          */
-       0x14,               /*          Logical Minimum (0),        */
-       0x25, 0x01,         /*          Logical Maximum (1),        */
-       0x75, 0x01,         /*          Report Size (1),            */
-       0x09, 0x42,         /*          Usage (Tip Switch),         */
-       0x09, 0x44,         /*          Usage (Barrel Switch),      */
-       0x09, 0x46,         /*          Usage (Tablet Pick),        */
-       0x95, 0x03,         /*          Report Count (3),           */
-       0x81, 0x02,         /*          Input (Variable),           */
-       0x95, 0x04,         /*          Report Count (4),           */
-       0x81, 0x03,         /*          Input (Constant, Variable), */
-       0x09, 0x32,         /*          Usage (In Range),           */
-       0x95, 0x01,         /*          Report Count (1),           */
-       0x81, 0x02,         /*          Input (Variable),           */
-       0x75, 0x10,         /*          Report Size (16),           */
-       0x95, 0x01,         /*          Report Count (1),           */
-       0xA4,               /*          Push,                       */
-       0x05, 0x01,         /*          Usage Page (Desktop),       */
-       0x55, 0xFD,         /*          Unit Exponent (-3),         */
-       0x65, 0x13,         /*          Unit (Inch),                */
-       0x34,               /*          Physical Minimum (0),       */
-       0x09, 0x30,         /*          Usage (X),                  */
-       0x46, 0x70, 0x17,   /*          Physical Maximum (6000),    */
-       0x26, 0x00, 0x3C,   /*          Logical Maximum (15360),    */
-       0x81, 0x02,         /*          Input (Variable),           */
-       0x09, 0x31,         /*          Usage (Y),                  */
-       0x46, 0xA0, 0x0F,   /*          Physical Maximum (4000),    */
-       0x26, 0x00, 0x28,   /*          Logical Maximum (10240),    */
-       0x81, 0x02,         /*          Input (Variable),           */
-       0xB4,               /*          Pop,                        */
-       0x09, 0x30,         /*          Usage (Tip Pressure),       */
-       0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
-       0x81, 0x02,         /*          Input (Variable),           */
-       0xC0,               /*      End Collection,                 */
-       0xC0,               /*  End Collection                      */
-       0x05, 0x0C,         /*  Usage Page (Consumer),              */
-       0x09, 0x01,         /*  Usage (Consumer Control),           */
-       0xA1, 0x01,         /*  Collection (Application),           */
-       0x85, 0x12,         /*      Report ID (18),                 */
-       0x14,               /*      Logical Minimum (0),            */
-       0x25, 0x01,         /*      Logical Maximum (1),            */
-       0x75, 0x01,         /*      Report Size (1),                */
-       0x95, 0x04,         /*      Report Count (4),               */
-       0x0A, 0x79, 0x02,   /*      Usage (AC Redo Or Repeat),      */
-       0x0A, 0x1A, 0x02,   /*      Usage (AC Undo),                */
-       0x0A, 0x2D, 0x02,   /*      Usage (AC Zoom In),             */
-       0x0A, 0x2E, 0x02,   /*      Usage (AC Zoom Out),            */
-       0x81, 0x02,         /*      Input (Variable),               */
-       0x95, 0x34,         /*      Report Count (52),              */
-       0x81, 0x03,         /*      Input (Constant, Variable),     */
-       0xC0                /*  End Collection                      */
+static const struct kye_tablet_info {
+       __u32 product;
+       __s32 x_logical_maximum;
+       __s32 y_logical_maximum;
+       __s32 pressure_logical_maximum;
+       __s32 x_physical_maximum;
+       __s32 y_physical_maximum;
+       __s8 unit_exponent;
+       __s8 unit;
+       bool has_punk;
+       unsigned int control_rsize;
+       const __u8 *control_rdesc;
+} kye_tablets_info[] = {
+       {USB_DEVICE_ID_KYE_EASYPEN_M406,  /* 0x5005 */
+               15360, 10240, 1023,    6,   4,  0, 0x13, false,
+               sizeof(easypen_m406_control_rdesc), easypen_m406_control_rdesc},
+       {USB_DEVICE_ID_KYE_EASYPEN_M506,  /* 0x500F */
+               24576, 20480, 1023,    6,   5,  0, 0x13, false,
+               sizeof(easypen_m506_control_rdesc), easypen_m506_control_rdesc},
+       {USB_DEVICE_ID_KYE_EASYPEN_I405X,  /* 0x5010 */
+               14080, 10240, 1023,   55,  40, -1, 0x13, false},
+       {USB_DEVICE_ID_KYE_MOUSEPEN_I608X,  /* 0x5011 */
+               20480, 15360, 2047,    8,   6,  0, 0x13,  true},
+       {USB_DEVICE_ID_KYE_EASYPEN_M406W,  /* 0x5012 */
+               15360, 10240, 1023,    6,   4,  0, 0x13, false,
+               sizeof(easypen_m406w_control_rdesc), easypen_m406w_control_rdesc},
+       {USB_DEVICE_ID_KYE_EASYPEN_M610X,  /* 0x5013 */
+               40960, 25600, 1023, 1000, 625, -2, 0x13, false,
+               sizeof(easypen_m610x_control_rdesc), easypen_m610x_control_rdesc},
+       {USB_DEVICE_ID_KYE_EASYPEN_340,  /* 0x5014 */
+               10240,  7680, 1023,    4,   3,  0, 0x13, false},
+       {USB_DEVICE_ID_KYE_PENSKETCH_M912,  /* 0x5015 */
+               61440, 46080, 2047,   12,   9,  0, 0x13,  true,
+               sizeof(pensketch_m912_control_rdesc), pensketch_m912_control_rdesc},
+       {USB_DEVICE_ID_KYE_MOUSEPEN_M508WX,  /* 0x5016 */
+               40960, 25600, 2047,    8,   5,  0, 0x13,  true,
+               sizeof(mousepen_m508wx_control_rdesc), mousepen_m508wx_control_rdesc},
+       {USB_DEVICE_ID_KYE_MOUSEPEN_M508X,  /* 0x5017 */
+               40960, 25600, 2047,    8,   5,  0, 0x13,  true,
+               sizeof(mousepen_m508x_control_rdesc), mousepen_m508x_control_rdesc},
+       {USB_DEVICE_ID_KYE_EASYPEN_M406XE,  /* 0x5019 */
+               15360, 10240, 1023,    6,   4,  0, 0x13, false,
+               sizeof(easypen_m406xe_control_rdesc), easypen_m406xe_control_rdesc},
+       {USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2,  /* 0x501A */
+               40960, 30720, 2047,    8,   6,  0, 0x13,  true},
+       {USB_DEVICE_ID_KYE_PENSKETCH_T609A,  /* 0x501B */
+               43520, 28160, 1023,   85,  55, -1, 0x13, false,
+               sizeof(pensketch_t609a_control_rdesc), pensketch_t609a_control_rdesc},
+       {}
 };
 
 static __u8 *kye_consumer_control_fixup(struct hid_device *hdev, __u8 *rdesc,
-               unsigned int *rsize, int offset, const char *device_name) {
+               unsigned int *rsize, int offset, const char *device_name)
+{
        /*
         * the fixup that need to be done:
         *   - change Usage Maximum in the Consumer Control
@@ -574,6 +380,79 @@ static __u8 *kye_consumer_control_fixup(struct hid_device *hdev, __u8 *rdesc,
        return rdesc;
 }
 
+/*
+ * Fix tablet descriptor of so-called "DataFormat 2".
+ *
+ * Though we may achieve a usable descriptor from original vendor-defined one,
+ * some problems exist:
+ *  - Their Logical Maximum never exceed 32767 (7F FF), though device do report
+ *    values greater than that;
+ *  - Physical Maximums are arbitrarily filled (always equal to Logical
+ *    Maximum);
+ *  - Detail for control buttons are not provided (a vendor-defined Usage Page
+ *    with fixed content).
+ *
+ * Thus we use a pre-defined parameter table rather than digging it from
+ * original descriptor.
+ *
+ * We may as well write a fallback routine for unrecognized kye tablet, but it's
+ * clear kye are unlikely to produce new models in the foreseeable future, so we
+ * simply enumerate all possible models.
+ */
+static __u8 *kye_tablet_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize)
+{
+       const struct kye_tablet_info *info;
+       unsigned int newsize;
+
+       if (*rsize < sizeof(kye_tablet_rdesc)) {
+               hid_warn(hdev,
+                        "tablet report size too small, or kye_tablet_rdesc unexpectedly large\n");
+               return rdesc;
+       }
+
+       for (info = kye_tablets_info; info->product; info++) {
+               if (hdev->product == info->product)
+                       break;
+       }
+
+       if (!info->product) {
+               hid_err(hdev, "tablet unknown, someone forget to add kye_tablet_info entry?\n");
+               return rdesc;
+       }
+
+       newsize = info->has_punk ? sizeof(kye_tablet_rdesc) : 112;
+       memcpy(rdesc, kye_tablet_rdesc, newsize);
+
+       put_unaligned_le32(info->x_logical_maximum, rdesc + 66);
+       put_unaligned_le32(info->x_physical_maximum, rdesc + 72);
+       rdesc[77] = info->unit;
+       rdesc[79] = info->unit_exponent;
+       put_unaligned_le32(info->y_logical_maximum, rdesc + 87);
+       put_unaligned_le32(info->y_physical_maximum, rdesc + 92);
+       put_unaligned_le32(info->pressure_logical_maximum, rdesc + 104);
+
+       if (info->has_punk) {
+               put_unaligned_le32(info->x_logical_maximum, rdesc + 156);
+               put_unaligned_le32(info->x_physical_maximum, rdesc + 162);
+               rdesc[167] = info->unit;
+               rdesc[169] = info->unit_exponent;
+               put_unaligned_le32(info->y_logical_maximum, rdesc + 177);
+               put_unaligned_le32(info->y_physical_maximum, rdesc + 182);
+       }
+
+       if (info->control_rsize) {
+               if (newsize + info->control_rsize > *rsize)
+                       hid_err(hdev, "control rdesc unexpectedly large");
+               else {
+                       memcpy(rdesc + newsize, info->control_rdesc, info->control_rsize);
+                       newsize += info->control_rsize;
+               }
+       }
+
+       *rsize = newsize;
+       return rdesc;
+}
+
 static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc,
                unsigned int *rsize)
 {
@@ -602,66 +481,37 @@ static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc,
                        rdesc[74] = 0x08;
                }
                break;
-       case USB_DEVICE_ID_KYE_EASYPEN_I405X:
-               if (*rsize == EASYPEN_I405X_RDESC_ORIG_SIZE) {
-                       rdesc = easypen_i405x_rdesc_fixed;
-                       *rsize = sizeof(easypen_i405x_rdesc_fixed);
-               }
-               break;
-       case USB_DEVICE_ID_KYE_MOUSEPEN_I608X:
-               if (*rsize == MOUSEPEN_I608X_RDESC_ORIG_SIZE) {
-                       rdesc = mousepen_i608x_rdesc_fixed;
-                       *rsize = sizeof(mousepen_i608x_rdesc_fixed);
-               }
-               break;
-       case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2:
-               if (*rsize == MOUSEPEN_I608X_V2_RDESC_ORIG_SIZE) {
-                       rdesc = mousepen_i608x_v2_rdesc_fixed;
-                       *rsize = sizeof(mousepen_i608x_v2_rdesc_fixed);
-               }
-               break;
-       case USB_DEVICE_ID_KYE_EASYPEN_M610X:
-               if (*rsize == EASYPEN_M610X_RDESC_ORIG_SIZE) {
-                       rdesc = easypen_m610x_rdesc_fixed;
-                       *rsize = sizeof(easypen_m610x_rdesc_fixed);
-               }
-               break;
-       case USB_DEVICE_ID_KYE_EASYPEN_M406XE:
-               if (*rsize == EASYPEN_M406XE_RDESC_ORIG_SIZE) {
-                       rdesc = easypen_m406xe_rdesc_fixed;
-                       *rsize = sizeof(easypen_m406xe_rdesc_fixed);
-               }
-               break;
-       case USB_DEVICE_ID_KYE_PENSKETCH_M912:
-               if (*rsize == PENSKETCH_M912_RDESC_ORIG_SIZE) {
-                       rdesc = pensketch_m912_rdesc_fixed;
-                       *rsize = sizeof(pensketch_m912_rdesc_fixed);
-               }
-               break;
        case USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE:
                rdesc = kye_consumer_control_fixup(hdev, rdesc, rsize, 104,
                                        "Genius Gila Gaming Mouse");
                break;
+       case USB_DEVICE_ID_GENIUS_MANTICORE:
+               rdesc = kye_consumer_control_fixup(hdev, rdesc, rsize, 104,
+                                       "Genius Manticore Keyboard");
+               break;
        case USB_DEVICE_ID_GENIUS_GX_IMPERATOR:
                rdesc = kye_consumer_control_fixup(hdev, rdesc, rsize, 83,
                                        "Genius Gx Imperator Keyboard");
                break;
-       case USB_DEVICE_ID_GENIUS_MANTICORE:
-               rdesc = kye_consumer_control_fixup(hdev, rdesc, rsize, 104,
-                                       "Genius Manticore Keyboard");
+       case USB_DEVICE_ID_KYE_EASYPEN_M406:
+       case USB_DEVICE_ID_KYE_EASYPEN_M506:
+       case USB_DEVICE_ID_KYE_EASYPEN_I405X:
+       case USB_DEVICE_ID_KYE_MOUSEPEN_I608X:
+       case USB_DEVICE_ID_KYE_EASYPEN_M406W:
+       case USB_DEVICE_ID_KYE_EASYPEN_M610X:
+       case USB_DEVICE_ID_KYE_EASYPEN_340:
+       case USB_DEVICE_ID_KYE_PENSKETCH_M912:
+       case USB_DEVICE_ID_KYE_MOUSEPEN_M508WX:
+       case USB_DEVICE_ID_KYE_MOUSEPEN_M508X:
+       case USB_DEVICE_ID_KYE_EASYPEN_M406XE:
+       case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2:
+       case USB_DEVICE_ID_KYE_PENSKETCH_T609A:
+               rdesc = kye_tablet_fixup(hdev, rdesc, rsize);
                break;
        }
        return rdesc;
 }
 
-/**
- * kye_tablet_enable() - Enable fully-functional tablet mode by setting a special feature report.
- *
- * @hdev:      HID device
- *
- * The specific report ID and data were discovered by sniffing the
- * Windows driver traffic.
- */
 static int kye_tablet_enable(struct hid_device *hdev)
 {
        struct list_head *list;
@@ -688,6 +538,15 @@ static int kye_tablet_enable(struct hid_device *hdev)
 
        value = report->field[0]->value;
 
+       /*
+        * The code is for DataFormat 2 of config xml. They have no obvious
+        * meaning (at least not configurable in Windows driver) except enabling
+        * fully-functional tablet mode (absolute positioning). Otherwise, the
+        * tablet acts like a relative mouse.
+        *
+        * Though there're magic codes for DataFormat 3 and 4, no devices use
+        * these DataFormats.
+        */
        value[0] = 0x12;
        value[1] = 0x10;
        value[2] = 0x11;
@@ -717,26 +576,33 @@ static int kye_probe(struct hid_device *hdev, const struct hid_device_id *id)
        }
 
        switch (id->product) {
+       case USB_DEVICE_ID_GENIUS_MANTICORE:
+               /*
+                * The manticore keyboard needs to have all the interfaces
+                * opened at least once to be fully functional.
+                */
+               if (hid_hw_open(hdev))
+                       hid_hw_close(hdev);
+               break;
+       case USB_DEVICE_ID_KYE_EASYPEN_M406:
+       case USB_DEVICE_ID_KYE_EASYPEN_M506:
        case USB_DEVICE_ID_KYE_EASYPEN_I405X:
        case USB_DEVICE_ID_KYE_MOUSEPEN_I608X:
-       case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2:
+       case USB_DEVICE_ID_KYE_EASYPEN_M406W:
        case USB_DEVICE_ID_KYE_EASYPEN_M610X:
-       case USB_DEVICE_ID_KYE_EASYPEN_M406XE:
+       case USB_DEVICE_ID_KYE_EASYPEN_340:
        case USB_DEVICE_ID_KYE_PENSKETCH_M912:
+       case USB_DEVICE_ID_KYE_MOUSEPEN_M508WX:
+       case USB_DEVICE_ID_KYE_MOUSEPEN_M508X:
+       case USB_DEVICE_ID_KYE_EASYPEN_M406XE:
+       case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2:
+       case USB_DEVICE_ID_KYE_PENSKETCH_T609A:
                ret = kye_tablet_enable(hdev);
                if (ret) {
                        hid_err(hdev, "tablet enabling failed\n");
                        goto enabling_err;
                }
                break;
-       case USB_DEVICE_ID_GENIUS_MANTICORE:
-               /*
-                * The manticore keyboard needs to have all the interfaces
-                * opened at least once to be fully functional.
-                */
-               if (hid_hw_open(hdev))
-                       hid_hw_close(hdev);
-               break;
        }
 
        return 0;
@@ -749,23 +615,37 @@ err:
 static const struct hid_device_id kye_devices[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+                               USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+                               USB_DEVICE_ID_GENIUS_MANTICORE) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+                               USB_DEVICE_ID_GENIUS_GX_IMPERATOR) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+                               USB_DEVICE_ID_KYE_EASYPEN_M406) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+                               USB_DEVICE_ID_KYE_EASYPEN_M506) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
                                USB_DEVICE_ID_KYE_EASYPEN_I405X) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
                                USB_DEVICE_ID_KYE_MOUSEPEN_I608X) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
-                               USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2) },
+                               USB_DEVICE_ID_KYE_EASYPEN_M406W) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
                                USB_DEVICE_ID_KYE_EASYPEN_M610X) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
-                               USB_DEVICE_ID_KYE_EASYPEN_M406XE) },
+                               USB_DEVICE_ID_KYE_EASYPEN_340) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
-                               USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE) },
+                               USB_DEVICE_ID_KYE_PENSKETCH_M912) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
-                               USB_DEVICE_ID_GENIUS_GX_IMPERATOR) },
+                               USB_DEVICE_ID_KYE_MOUSEPEN_M508WX) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
-                               USB_DEVICE_ID_GENIUS_MANTICORE) },
+                               USB_DEVICE_ID_KYE_MOUSEPEN_M508X) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
-                               USB_DEVICE_ID_KYE_PENSKETCH_M912) },
+                               USB_DEVICE_ID_KYE_EASYPEN_M406XE) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+                               USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+                               USB_DEVICE_ID_KYE_PENSKETCH_T609A) },
        { }
 };
 MODULE_DEVICE_TABLE(hid, kye_devices);
index c8f82bc..acbec1d 100644 (file)
@@ -7,6 +7,7 @@
 
 #include <linux/device.h>
 #include <linux/hid.h>
+#include <linux/leds.h>
 #include <linux/module.h>
 #include <linux/random.h>
 #include <linux/sched.h>
index 5fc88a0..0fcfd85 100644 (file)
@@ -74,6 +74,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
 #define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS     BIT(27)
 #define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(28)
 #define HIDPP_QUIRK_HI_RES_SCROLL_1P0          BIT(29)
+#define HIDPP_QUIRK_WIRELESS_STATUS            BIT(30)
 
 /* These are just aliases for now */
 #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -94,6 +95,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
 #define HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL  BIT(7)
 #define HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL BIT(8)
 #define HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL   BIT(9)
+#define HIDPP_CAPABILITY_ADC_MEASUREMENT       BIT(10)
 
 #define lg_map_key_clear(c)  hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
 
@@ -145,6 +147,7 @@ struct hidpp_battery {
        u8 feature_index;
        u8 solar_feature_index;
        u8 voltage_feature_index;
+       u8 adc_measurement_feature_index;
        struct power_supply_desc desc;
        struct power_supply *ps;
        char name[64];
@@ -471,6 +474,26 @@ static void hidpp_prefix_name(char **name, int name_length)
        *name = new_name;
 }
 
+/*
+ * Updates the USB wireless_status based on whether the headset
+ * is turned on and reachable.
+ */
+static void hidpp_update_usb_wireless_status(struct hidpp_device *hidpp)
+{
+       struct hid_device *hdev = hidpp->hid_dev;
+       struct usb_interface *intf;
+
+       if (!(hidpp->quirks & HIDPP_QUIRK_WIRELESS_STATUS))
+               return;
+       if (!hid_is_usb(hdev))
+               return;
+
+       intf = to_usb_interface(hdev->dev.parent);
+       usb_set_wireless_status(intf, hidpp->battery.online ?
+                               USB_WIRELESS_STATUS_CONNECTED :
+                               USB_WIRELESS_STATUS_DISCONNECTED);
+}
+
 /**
  * hidpp_scroll_counter_handle_scroll() - Send high- and low-resolution scroll
  *                                        events given a high-resolution wheel
@@ -853,8 +876,7 @@ static int hidpp_unifying_init(struct hidpp_device *hidpp)
        if (ret)
                return ret;
 
-       snprintf(hdev->uniq, sizeof(hdev->uniq), "%04x-%4phD",
-                hdev->product, &serial);
+       snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", &serial);
        dbg_hid("HID++ Unifying: Got serial: %s\n", hdev->uniq);
 
        name = hidpp_unifying_get_name(hidpp);
@@ -948,6 +970,54 @@ print_version:
 }
 
 /* -------------------------------------------------------------------------- */
+/* 0x0003: Device Information                                                 */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_DEVICE_INFORMATION                  0x0003
+
+#define CMD_GET_DEVICE_INFO                            0x00
+
+static int hidpp_get_serial(struct hidpp_device *hidpp, u32 *serial)
+{
+       struct hidpp_report response;
+       u8 feature_type;
+       u8 feature_index;
+       int ret;
+
+       ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_DEVICE_INFORMATION,
+                                    &feature_index,
+                                    &feature_type);
+       if (ret)
+               return ret;
+
+       ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+                                         CMD_GET_DEVICE_INFO,
+                                         NULL, 0, &response);
+       if (ret)
+               return ret;
+
+       /* See hidpp_unifying_get_serial() */
+       *serial = *((u32 *)&response.rap.params[1]);
+       return 0;
+}
+
+static int hidpp_serial_init(struct hidpp_device *hidpp)
+{
+       struct hid_device *hdev = hidpp->hid_dev;
+       u32 serial;
+       int ret;
+
+       ret = hidpp_get_serial(hidpp, &serial);
+       if (ret)
+               return ret;
+
+       snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", &serial);
+       dbg_hid("HID++ DeviceInformation: Got serial: %s\n", hdev->uniq);
+
+       return 0;
+}
+
+/* -------------------------------------------------------------------------- */
 /* 0x0005: GetDeviceNameType                                                  */
 /* -------------------------------------------------------------------------- */
 
@@ -1357,7 +1427,7 @@ static int hidpp20_map_battery_capacity(struct hid_device *hid_dev, int voltage)
         * there are a few devices that use different battery technology.
         */
 
-       static const int voltages[] = {
+       static const int voltages[100] = {
                4186, 4156, 4143, 4133, 4122, 4113, 4103, 4094, 4086, 4075,
                4067, 4059, 4051, 4043, 4035, 4027, 4019, 4011, 4003, 3997,
                3989, 3983, 3976, 3969, 3961, 3955, 3949, 3942, 3935, 3929,
@@ -1372,8 +1442,6 @@ static int hidpp20_map_battery_capacity(struct hid_device *hid_dev, int voltage)
 
        int i;
 
-       BUILD_BUG_ON(ARRAY_SIZE(voltages) != 100);
-
        if (unlikely(voltage < 3500 || voltage >= 5000))
                hid_warn_once(hid_dev,
                              "%s: possibly using the wrong voltage curve\n",
@@ -1746,6 +1814,164 @@ static int hidpp_set_wireless_feature_index(struct hidpp_device *hidpp)
 }
 
 /* -------------------------------------------------------------------------- */
+/* 0x1f20: ADC measurement                                                    */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_ADC_MEASUREMENT 0x1f20
+
+#define CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT 0x00
+
+#define EVENT_ADC_MEASUREMENT_STATUS_BROADCAST 0x00
+
+static int hidpp20_map_adc_measurement_1f20_capacity(struct hid_device *hid_dev, int voltage)
+{
+       /* NB: This voltage curve doesn't necessarily map perfectly to all
+        * devices that implement the ADC_MEASUREMENT feature. This is because
+        * there are a few devices that use different battery technology.
+        *
+        * Adapted from:
+        * https://github.com/Sapd/HeadsetControl/blob/acd972be0468e039b93aae81221f20a54d2d60f7/src/devices/logitech_g633_g933_935.c#L44-L52
+        */
+       static const int voltages[100] = {
+               4030, 4024, 4018, 4011, 4003, 3994, 3985, 3975, 3963, 3951,
+               3937, 3922, 3907, 3893, 3880, 3868, 3857, 3846, 3837, 3828,
+               3820, 3812, 3805, 3798, 3791, 3785, 3779, 3773, 3768, 3762,
+               3757, 3752, 3747, 3742, 3738, 3733, 3729, 3724, 3720, 3716,
+               3712, 3708, 3704, 3700, 3696, 3692, 3688, 3685, 3681, 3677,
+               3674, 3670, 3667, 3663, 3660, 3657, 3653, 3650, 3646, 3643,
+               3640, 3637, 3633, 3630, 3627, 3624, 3620, 3617, 3614, 3611,
+               3608, 3604, 3601, 3598, 3595, 3592, 3589, 3585, 3582, 3579,
+               3576, 3573, 3569, 3566, 3563, 3560, 3556, 3553, 3550, 3546,
+               3543, 3539, 3536, 3532, 3529, 3525, 3499, 3466, 3433, 3399,
+       };
+
+       int i;
+
+       if (voltage == 0)
+               return 0;
+
+       if (unlikely(voltage < 3400 || voltage >= 5000))
+               hid_warn_once(hid_dev,
+                             "%s: possibly using the wrong voltage curve\n",
+                             __func__);
+
+       for (i = 0; i < ARRAY_SIZE(voltages); i++) {
+               if (voltage >= voltages[i])
+                       return ARRAY_SIZE(voltages) - i;
+       }
+
+       return 0;
+}
+
+static int hidpp20_map_adc_measurement_1f20(u8 data[3], int *voltage)
+{
+       int status;
+       u8 flags;
+
+       flags = data[2];
+
+       switch (flags) {
+       case 0x01:
+               status = POWER_SUPPLY_STATUS_DISCHARGING;
+               break;
+       case 0x03:
+               status = POWER_SUPPLY_STATUS_CHARGING;
+               break;
+       case 0x07:
+               status = POWER_SUPPLY_STATUS_FULL;
+               break;
+       case 0x0F:
+       default:
+               status = POWER_SUPPLY_STATUS_UNKNOWN;
+               break;
+       }
+
+       *voltage = get_unaligned_be16(data);
+
+       dbg_hid("Parsed 1f20 data as flag 0x%02x voltage %dmV\n",
+               flags, *voltage);
+
+       return status;
+}
+
+/* Return value is whether the device is online */
+static bool hidpp20_get_adc_measurement_1f20(struct hidpp_device *hidpp,
+                                                u8 feature_index,
+                                                int *status, int *voltage)
+{
+       struct hidpp_report response;
+       int ret;
+       u8 *params = (u8 *)response.fap.params;
+
+       *status = POWER_SUPPLY_STATUS_UNKNOWN;
+       *voltage = 0;
+       ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+                                         CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT,
+                                         NULL, 0, &response);
+
+       if (ret > 0) {
+               hid_dbg(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+                       __func__, ret);
+               return false;
+       }
+
+       *status = hidpp20_map_adc_measurement_1f20(params, voltage);
+       return true;
+}
+
+static int hidpp20_query_adc_measurement_info_1f20(struct hidpp_device *hidpp)
+{
+       u8 feature_type;
+
+       if (hidpp->battery.adc_measurement_feature_index == 0xff) {
+               int ret;
+
+               ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_ADC_MEASUREMENT,
+                                            &hidpp->battery.adc_measurement_feature_index,
+                                            &feature_type);
+               if (ret)
+                       return ret;
+
+               hidpp->capabilities |= HIDPP_CAPABILITY_ADC_MEASUREMENT;
+       }
+
+       hidpp->battery.online = hidpp20_get_adc_measurement_1f20(hidpp,
+                                                                hidpp->battery.adc_measurement_feature_index,
+                                                                &hidpp->battery.status,
+                                                                &hidpp->battery.voltage);
+       hidpp->battery.capacity = hidpp20_map_adc_measurement_1f20_capacity(hidpp->hid_dev,
+                                                                           hidpp->battery.voltage);
+       hidpp_update_usb_wireless_status(hidpp);
+
+       return 0;
+}
+
+static int hidpp20_adc_measurement_event_1f20(struct hidpp_device *hidpp,
+                                           u8 *data, int size)
+{
+       struct hidpp_report *report = (struct hidpp_report *)data;
+       int status, voltage;
+
+       if (report->fap.feature_index != hidpp->battery.adc_measurement_feature_index ||
+               report->fap.funcindex_clientid != EVENT_ADC_MEASUREMENT_STATUS_BROADCAST)
+               return 0;
+
+       status = hidpp20_map_adc_measurement_1f20(report->fap.params, &voltage);
+
+       hidpp->battery.online = status != POWER_SUPPLY_STATUS_UNKNOWN;
+
+       if (voltage != hidpp->battery.voltage || status != hidpp->battery.status) {
+               hidpp->battery.status = status;
+               hidpp->battery.voltage = voltage;
+               hidpp->battery.capacity = hidpp20_map_adc_measurement_1f20_capacity(hidpp->hid_dev, voltage);
+               if (hidpp->battery.ps)
+                       power_supply_changed(hidpp->battery.ps);
+               hidpp_update_usb_wireless_status(hidpp);
+       }
+       return 0;
+}
+
+/* -------------------------------------------------------------------------- */
 /* 0x2120: Hi-resolution scrolling                                            */
 /* -------------------------------------------------------------------------- */
 
@@ -3663,6 +3889,9 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
                ret = hidpp20_battery_voltage_event(hidpp, data, size);
                if (ret != 0)
                        return ret;
+               ret = hidpp20_adc_measurement_event_1f20(hidpp, data, size);
+               if (ret != 0)
+                       return ret;
        }
 
        if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
@@ -3786,6 +4015,7 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
        hidpp->battery.feature_index = 0xff;
        hidpp->battery.solar_feature_index = 0xff;
        hidpp->battery.voltage_feature_index = 0xff;
+       hidpp->battery.adc_measurement_feature_index = 0xff;
 
        if (hidpp->protocol_major >= 2) {
                if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750)
@@ -3799,6 +4029,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
                                ret = hidpp20_query_battery_info_1004(hidpp);
                        if (ret)
                                ret = hidpp20_query_battery_voltage_info(hidpp);
+                       if (ret)
+                               ret = hidpp20_query_adc_measurement_info_1f20(hidpp);
                }
 
                if (ret)
@@ -3828,7 +4060,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
 
        if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE ||
            hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE ||
-           hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
+           hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE ||
+           hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
                battery_props[num_battery_props++] =
                                POWER_SUPPLY_PROP_CAPACITY;
 
@@ -3836,7 +4069,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
                battery_props[num_battery_props++] =
                                POWER_SUPPLY_PROP_CAPACITY_LEVEL;
 
-       if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
+       if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE ||
+           hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
                battery_props[num_battery_props++] =
                        POWER_SUPPLY_PROP_VOLTAGE_NOW;
 
@@ -4009,6 +4243,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
                        hidpp20_query_battery_voltage_info(hidpp);
                else if (hidpp->capabilities & HIDPP_CAPABILITY_UNIFIED_BATTERY)
                        hidpp20_query_battery_info_1004(hidpp);
+               else if (hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
+                       hidpp20_query_adc_measurement_info_1f20(hidpp);
                else
                        hidpp20_query_battery_info_1000(hidpp);
        }
@@ -4210,6 +4446,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 
        if (hidpp->quirks & HIDPP_QUIRK_UNIFYING)
                hidpp_unifying_init(hidpp);
+       else if (hid_is_usb(hidpp->hid_dev))
+               hidpp_serial_init(hidpp);
 
        connected = hidpp_root_get_protocol_version(hidpp) == 0;
        atomic_set(&hidpp->connected, connected);
@@ -4379,6 +4617,10 @@ static const struct hid_device_id hidpp_devices[] = {
        { /* Logitech G Pro Gaming Mouse over USB */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
 
+       { /* G935 Gaming Headset */
+         HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0x0a87),
+               .driver_data = HIDPP_QUIRK_WIRELESS_STATUS },
+
        { /* MX5000 keyboard over Bluetooth */
          HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb305),
          .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
index f74a977..72883e0 100644 (file)
@@ -79,8 +79,8 @@ struct mcp_get_gpio {
        u8 cmd;
        u8 dummy;
        struct {
-               u8 direction;
                u8 value;
+               u8 direction;
        } gpio[MCP_NGPIO];
 } __packed;
 
@@ -594,7 +594,7 @@ static int mcp_gpio_get(struct gpio_chip *gc,
 
        mcp->txbuf[0] = MCP2221_GPIO_GET;
 
-       mcp->gp_idx = offsetof(struct mcp_get_gpio, gpio[offset].value);
+       mcp->gp_idx = offsetof(struct mcp_get_gpio, gpio[offset]);
 
        mutex_lock(&mcp->lock);
        ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
@@ -675,7 +675,7 @@ static int mcp_gpio_get_direction(struct gpio_chip *gc,
 
        mcp->txbuf[0] = MCP2221_GPIO_GET;
 
-       mcp->gp_idx = offsetof(struct mcp_get_gpio, gpio[offset].direction);
+       mcp->gp_idx = offsetof(struct mcp_get_gpio, gpio[offset]);
 
        mutex_lock(&mcp->lock);
        ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
index 5bfc0c4..250f5d2 100644 (file)
@@ -433,7 +433,9 @@ struct joycon_ctlr {
        u8 usb_ack_match;
        u8 subcmd_ack_match;
        bool received_input_report;
+       unsigned int last_input_report_msecs;
        unsigned int last_subcmd_sent_msecs;
+       unsigned int consecutive_valid_report_deltas;
 
        /* factory calibration data */
        struct joycon_stick_cal left_stick_cal_x;
@@ -543,19 +545,54 @@ static void joycon_wait_for_input_report(struct joycon_ctlr *ctlr)
  * Sending subcommands and/or rumble data at too high a rate can cause bluetooth
  * controller disconnections.
  */
+#define JC_INPUT_REPORT_MIN_DELTA      8
+#define JC_INPUT_REPORT_MAX_DELTA      17
+#define JC_SUBCMD_TX_OFFSET_MS         4
+#define JC_SUBCMD_VALID_DELTA_REQ      3
+#define JC_SUBCMD_RATE_MAX_ATTEMPTS    500
+#define JC_SUBCMD_RATE_LIMITER_USB_MS  20
+#define JC_SUBCMD_RATE_LIMITER_BT_MS   60
+#define JC_SUBCMD_RATE_LIMITER_MS(ctlr)        ((ctlr)->hdev->bus == BUS_USB ? JC_SUBCMD_RATE_LIMITER_USB_MS : JC_SUBCMD_RATE_LIMITER_BT_MS)
 static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr)
 {
-       static const unsigned int max_subcmd_rate_ms = 25;
-       unsigned int current_ms = jiffies_to_msecs(jiffies);
-       unsigned int delta_ms = current_ms - ctlr->last_subcmd_sent_msecs;
+       unsigned int current_ms;
+       unsigned long subcmd_delta;
+       int consecutive_valid_deltas = 0;
+       int attempts = 0;
+       unsigned long flags;
+
+       if (unlikely(ctlr->ctlr_state != JOYCON_CTLR_STATE_READ))
+               return;
 
-       while (delta_ms < max_subcmd_rate_ms &&
-              ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) {
+       do {
                joycon_wait_for_input_report(ctlr);
                current_ms = jiffies_to_msecs(jiffies);
-               delta_ms = current_ms - ctlr->last_subcmd_sent_msecs;
+               subcmd_delta = current_ms - ctlr->last_subcmd_sent_msecs;
+
+               spin_lock_irqsave(&ctlr->lock, flags);
+               consecutive_valid_deltas = ctlr->consecutive_valid_report_deltas;
+               spin_unlock_irqrestore(&ctlr->lock, flags);
+
+               attempts++;
+       } while ((consecutive_valid_deltas < JC_SUBCMD_VALID_DELTA_REQ ||
+                 subcmd_delta < JC_SUBCMD_RATE_LIMITER_MS(ctlr)) &&
+                ctlr->ctlr_state == JOYCON_CTLR_STATE_READ &&
+                attempts < JC_SUBCMD_RATE_MAX_ATTEMPTS);
+
+       if (attempts >= JC_SUBCMD_RATE_MAX_ATTEMPTS) {
+               hid_warn(ctlr->hdev, "%s: exceeded max attempts", __func__);
+               return;
        }
+
        ctlr->last_subcmd_sent_msecs = current_ms;
+
+       /*
+        * Wait a short time after receiving an input report before
+        * transmitting. This should reduce odds of a TX coinciding with an RX.
+        * Minimizing concurrent BT traffic with the controller seems to lower
+        * the rate of disconnections.
+        */
+       msleep(JC_SUBCMD_TX_OFFSET_MS);
 }
 
 static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len,
@@ -1223,6 +1260,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
        u8 tmp;
        u32 btns;
        unsigned long msecs = jiffies_to_msecs(jiffies);
+       unsigned long report_delta_ms = msecs - ctlr->last_input_report_msecs;
 
        spin_lock_irqsave(&ctlr->lock, flags);
        if (IS_ENABLED(CONFIG_NINTENDO_FF) && rep->vibrator_report &&
@@ -1364,6 +1402,31 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
 
        input_sync(dev);
 
+       spin_lock_irqsave(&ctlr->lock, flags);
+       ctlr->last_input_report_msecs = msecs;
+       /*
+        * Was this input report a reasonable time delta compared to the prior
+        * report? We use this information to decide when a safe time is to send
+        * rumble packets or subcommand packets.
+        */
+       if (report_delta_ms >= JC_INPUT_REPORT_MIN_DELTA &&
+           report_delta_ms <= JC_INPUT_REPORT_MAX_DELTA) {
+               if (ctlr->consecutive_valid_report_deltas < JC_SUBCMD_VALID_DELTA_REQ)
+                       ctlr->consecutive_valid_report_deltas++;
+       } else {
+               ctlr->consecutive_valid_report_deltas = 0;
+       }
+       /*
+        * Our consecutive valid report tracking is only relevant for
+        * bluetooth-connected controllers. For USB devices, we're beholden to
+        * USB's underlying polling rate anyway. Always set to the consecutive
+        * delta requirement.
+        */
+       if (ctlr->hdev->bus == BUS_USB)
+               ctlr->consecutive_valid_report_deltas = JC_SUBCMD_VALID_DELTA_REQ;
+
+       spin_unlock_irqrestore(&ctlr->lock, flags);
+
        /*
         * Immediately after receiving a report is the most reliable time to
         * send a subcommand to the controller. Wake any subcommand senders
@@ -1527,6 +1590,7 @@ static int joycon_set_rumble(struct joycon_ctlr *ctlr, u16 amp_r, u16 amp_l,
        u16 freq_l_low;
        u16 freq_l_high;
        unsigned long flags;
+       int next_rq_head;
 
        spin_lock_irqsave(&ctlr->lock, flags);
        freq_r_low = ctlr->rumble_rl_freq;
@@ -1547,8 +1611,21 @@ static int joycon_set_rumble(struct joycon_ctlr *ctlr, u16 amp_r, u16 amp_l,
        joycon_encode_rumble(data, freq_l_low, freq_l_high, amp);
 
        spin_lock_irqsave(&ctlr->lock, flags);
-       if (++ctlr->rumble_queue_head >= JC_RUMBLE_QUEUE_SIZE)
-               ctlr->rumble_queue_head = 0;
+
+       next_rq_head = ctlr->rumble_queue_head + 1;
+       if (next_rq_head >= JC_RUMBLE_QUEUE_SIZE)
+               next_rq_head = 0;
+
+       /* Did we overrun the circular buffer?
+        * If so, be sure we keep the latest intended rumble state.
+        */
+       if (next_rq_head == ctlr->rumble_queue_tail) {
+               hid_dbg(ctlr->hdev, "rumble queue is full");
+               /* overwrite the prior value at the end of the circular buf */
+               next_rq_head = ctlr->rumble_queue_head;
+       }
+
+       ctlr->rumble_queue_head = next_rq_head;
        memcpy(ctlr->rumble_data[ctlr->rumble_queue_head], data,
               JC_RUMBLE_DATA_SIZE);
 
@@ -2128,7 +2205,7 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 
        ctlr->hdev = hdev;
        ctlr->ctlr_state = JOYCON_CTLR_STATE_INIT;
-       ctlr->rumble_queue_head = JC_RUMBLE_QUEUE_SIZE - 1;
+       ctlr->rumble_queue_head = 0;
        ctlr->rumble_queue_tail = 0;
        hid_set_drvdata(hdev, ctlr);
        mutex_init(&ctlr->output_mutex);
index 66e6435..804fc03 100644 (file)
@@ -104,12 +104,20 @@ static const struct hid_device_id hid_quirks[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a), HID_QUIRK_ALWAYS_POLL },
        { HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, USB_DEVICE_ID_IDEACOM_IDC6680), HID_QUIRK_MULTI_INPUT },
        { HID_USB_DEVICE(USB_VENDOR_ID_INNOMEDIA, USB_DEVICE_ID_INNEX_GENESIS_ATARI), HID_QUIRK_MULTI_INPUT },
-       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X), HID_QUIRK_MULTI_INPUT },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2), HID_QUIRK_ALWAYS_POLL },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406), HID_QUIRK_MULTI_INPUT },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M506), HID_QUIRK_MULTI_INPUT },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_I405X), HID_QUIRK_MULTI_INPUT },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X), HID_QUIRK_MULTI_INPUT },
-       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2), HID_QUIRK_MULTI_INPUT },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406W), HID_QUIRK_MULTI_INPUT },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X), HID_QUIRK_MULTI_INPUT },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_340), HID_QUIRK_MULTI_INPUT },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912), HID_QUIRK_MULTI_INPUT },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_M508WX), HID_QUIRK_MULTI_INPUT },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_M508X), HID_QUIRK_MULTI_INPUT },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406XE), HID_QUIRK_MULTI_INPUT },
-       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2), HID_QUIRK_ALWAYS_POLL },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2), HID_QUIRK_MULTI_INPUT },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_T609A), HID_QUIRK_MULTI_INPUT },
        { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_OPTICAL_USB_MOUSE_600E), HID_QUIRK_ALWAYS_POLL },
        { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D), HID_QUIRK_ALWAYS_POLL },
        { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019), HID_QUIRK_ALWAYS_POLL },
index 37353c4..aae3afc 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/device.h>
 #include <linux/hid.h>
 #include <linux/module.h>
+#include <linux/leds.h>
 
 #include "hid-ids.h"
 
index 4439be7..3be1710 100644 (file)
@@ -23,12 +23,14 @@ config I2C_HID_ACPI
 
 config I2C_HID_OF
        tristate "HID over I2C transport layer Open Firmware driver"
-       depends on OF
+       # No "depends on OF" because this can also be used for manually
+       # (board-file) instantiated "hid-over-i2c" type i2c-clients.
        select I2C_HID_CORE
        help
          Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
          other HID based devices which is connected to your computer via I2C.
-         This driver supports Open Firmware (Device Tree)-based systems.
+         This driver supports Open Firmware (Device Tree)-based systems as
+         well as binding to manually (board-file) instantiated i2c-hid-clients.
 
          If unsure, say N.
 
index 1017656..855f530 100644 (file)
@@ -21,6 +21,7 @@
 
 #include <linux/delay.h>
 #include <linux/device.h>
+#include <linux/gpio/consumer.h>
 #include <linux/hid.h>
 #include <linux/i2c.h>
 #include <linux/kernel.h>
@@ -35,8 +36,10 @@ struct i2c_hid_of {
        struct i2chid_ops ops;
 
        struct i2c_client *client;
+       struct gpio_desc *reset_gpio;
        struct regulator_bulk_data supplies[2];
        int post_power_delay_ms;
+       int post_reset_delay_ms;
 };
 
 static int i2c_hid_of_power_up(struct i2chid_ops *ops)
@@ -55,6 +58,10 @@ static int i2c_hid_of_power_up(struct i2chid_ops *ops)
        if (ihid_of->post_power_delay_ms)
                msleep(ihid_of->post_power_delay_ms);
 
+       gpiod_set_value_cansleep(ihid_of->reset_gpio, 0);
+       if (ihid_of->post_reset_delay_ms)
+               msleep(ihid_of->post_reset_delay_ms);
+
        return 0;
 }
 
@@ -62,6 +69,7 @@ static void i2c_hid_of_power_down(struct i2chid_ops *ops)
 {
        struct i2c_hid_of *ihid_of = container_of(ops, struct i2c_hid_of, ops);
 
+       gpiod_set_value_cansleep(ihid_of->reset_gpio, 1);
        regulator_bulk_disable(ARRAY_SIZE(ihid_of->supplies),
                               ihid_of->supplies);
 }
@@ -75,33 +83,43 @@ static int i2c_hid_of_probe(struct i2c_client *client)
        int ret;
        u32 val;
 
-       ihid_of = devm_kzalloc(&client->dev, sizeof(*ihid_of), GFP_KERNEL);
+       ihid_of = devm_kzalloc(dev, sizeof(*ihid_of), GFP_KERNEL);
        if (!ihid_of)
                return -ENOMEM;
 
        ihid_of->ops.power_up = i2c_hid_of_power_up;
        ihid_of->ops.power_down = i2c_hid_of_power_down;
 
-       ret = of_property_read_u32(dev->of_node, "hid-descr-addr", &val);
+       ret = device_property_read_u32(dev, "hid-descr-addr", &val);
        if (ret) {
-               dev_err(&client->dev, "HID register address not provided\n");
+               dev_err(dev, "HID register address not provided\n");
                return -ENODEV;
        }
        if (val >> 16) {
-               dev_err(&client->dev, "Bad HID register address: 0x%08x\n",
-                       val);
+               dev_err(dev, "Bad HID register address: 0x%08x\n", val);
                return -EINVAL;
        }
        hid_descriptor_address = val;
 
-       if (!device_property_read_u32(&client->dev, "post-power-on-delay-ms",
-                                     &val))
+       if (!device_property_read_u32(dev, "post-power-on-delay-ms", &val))
                ihid_of->post_power_delay_ms = val;
 
+       /*
+        * Note this is a kernel internal device-property set by x86 platform code,
+        * this MUST not be used in devicetree files without first adding it to
+        * the DT bindings.
+        */
+       if (!device_property_read_u32(dev, "post-reset-deassert-delay-ms", &val))
+               ihid_of->post_reset_delay_ms = val;
+
+       /* Start out with reset asserted */
+       ihid_of->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+       if (IS_ERR(ihid_of->reset_gpio))
+               return PTR_ERR(ihid_of->reset_gpio);
+
        ihid_of->supplies[0].supply = "vdd";
        ihid_of->supplies[1].supply = "vddl";
-       ret = devm_regulator_bulk_get(&client->dev,
-                                     ARRAY_SIZE(ihid_of->supplies),
+       ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ihid_of->supplies),
                                      ihid_of->supplies);
        if (ret)
                return ret;
@@ -116,11 +134,13 @@ static int i2c_hid_of_probe(struct i2c_client *client)
                                  hid_descriptor_address, quirks);
 }
 
+#ifdef CONFIG_OF
 static const struct of_device_id i2c_hid_of_match[] = {
        { .compatible = "hid-over-i2c" },
        {},
 };
 MODULE_DEVICE_TABLE(of, i2c_hid_of_match);
+#endif
 
 static const struct i2c_device_id i2c_hid_of_id_table[] = {
        { "hid", 0 },
index fb538a6..8214896 100644 (file)
@@ -2372,13 +2372,6 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless)
        if (error)
                goto fail;
 
-       if (!(features->device_type & WACOM_DEVICETYPE_WL_MONITOR) &&
-            (features->quirks & WACOM_QUIRK_BATTERY)) {
-               error = wacom_initialize_battery(wacom);
-               if (error)
-                       goto fail;
-       }
-
        error = wacom_register_inputs(wacom);
        if (error)
                goto fail;
@@ -2509,9 +2502,6 @@ static void wacom_wireless_work(struct work_struct *work)
 
                strscpy(wacom_wac->name, wacom_wac1->name,
                        sizeof(wacom_wac->name));
-               error = wacom_initialize_battery(wacom);
-               if (error)
-                       goto fail;
        }
 
        return;
index 9312d61..dc0f7d9 100644 (file)
@@ -113,6 +113,11 @@ static void wacom_notify_battery(struct wacom_wac *wacom_wac,
        bool bat_connected, bool ps_connected)
 {
        struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+       bool bat_initialized = wacom->battery.battery;
+       bool has_quirk = wacom_wac->features.quirks & WACOM_QUIRK_BATTERY;
+
+       if (bat_initialized != has_quirk)
+               wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY);
 
        __wacom_notify_battery(&wacom->battery, bat_status, bat_capacity,
                               bat_charging, bat_connected, ps_connected);
@@ -1308,6 +1313,9 @@ static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
 
        struct input_dev *pen_input = wacom->pen_input;
        unsigned char *data = wacom->data;
+       int number_of_valid_frames = 0;
+       int time_interval = 15000000;
+       ktime_t time_packet_received = ktime_get();
        int i;
 
        if (wacom->features.type == INTUOSP2_BT ||
@@ -1328,12 +1336,30 @@ static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
                wacom->id[0] |= (wacom->serial[0] >> 32) & 0xFFFFF;
        }
 
+       /* number of valid frames */
        for (i = 0; i < pen_frames; i++) {
                unsigned char *frame = &data[i*pen_frame_len + 1];
                bool valid = frame[0] & 0x80;
+
+               if (valid)
+                       number_of_valid_frames++;
+       }
+
+       if (number_of_valid_frames) {
+               if (wacom->hid_data.time_delayed)
+                       time_interval = ktime_get() - wacom->hid_data.time_delayed;
+               time_interval /= number_of_valid_frames;
+               wacom->hid_data.time_delayed = time_packet_received;
+       }
+
+       for (i = 0; i < number_of_valid_frames; i++) {
+               unsigned char *frame = &data[i*pen_frame_len + 1];
+               bool valid = frame[0] & 0x80;
                bool prox = frame[0] & 0x40;
                bool range = frame[0] & 0x20;
                bool invert = frame[0] & 0x10;
+               int frames_number_reversed = number_of_valid_frames - i - 1;
+               int event_timestamp = time_packet_received - frames_number_reversed * time_interval;
 
                if (!valid)
                        continue;
@@ -1346,6 +1372,7 @@ static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
                        wacom->tool[0] = 0;
                        wacom->id[0] = 0;
                        wacom->serial[0] = 0;
+                       wacom->hid_data.time_delayed = 0;
                        return;
                }
 
@@ -1382,6 +1409,7 @@ static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
                                                 get_unaligned_le16(&frame[11]));
                        }
                }
+
                if (wacom->tool[0]) {
                        input_report_abs(pen_input, ABS_PRESSURE, get_unaligned_le16(&frame[5]));
                        if (wacom->features.type == INTUOSP2_BT ||
@@ -1405,6 +1433,9 @@ static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
 
                wacom->shared->stylus_in_proximity = prox;
 
+               /* add timestamp to unpack the frames */
+               input_set_timestamp(pen_input, event_timestamp);
+
                input_sync(pen_input);
        }
 }
@@ -1895,6 +1926,7 @@ static void wacom_map_usage(struct input_dev *input, struct hid_usage *usage,
        int fmax = field->logical_maximum;
        unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid);
        int resolution_code = code;
+       int resolution = hidinput_calc_abs_res(field, resolution_code);
 
        if (equivalent_usage == HID_DG_TWIST) {
                resolution_code = ABS_RZ;
@@ -1915,8 +1947,15 @@ static void wacom_map_usage(struct input_dev *input, struct hid_usage *usage,
        switch (type) {
        case EV_ABS:
                input_set_abs_params(input, code, fmin, fmax, fuzz, 0);
-               input_abs_set_res(input, code,
-                                 hidinput_calc_abs_res(field, resolution_code));
+
+               /* older tablet may miss physical usage */
+               if ((code == ABS_X || code == ABS_Y) && !resolution) {
+                       resolution = WACOM_INTUOS_RES;
+                       hid_warn(input,
+                                "Wacom usage (%d) missing resolution \n",
+                                code);
+               }
+               input_abs_set_res(input, code, resolution);
                break;
        case EV_KEY:
        case EV_MSC:
@@ -1929,18 +1968,7 @@ static void wacom_map_usage(struct input_dev *input, struct hid_usage *usage,
 static void wacom_wac_battery_usage_mapping(struct hid_device *hdev,
                struct hid_field *field, struct hid_usage *usage)
 {
-       struct wacom *wacom = hid_get_drvdata(hdev);
-       struct wacom_wac *wacom_wac = &wacom->wacom_wac;
-       struct wacom_features *features = &wacom_wac->features;
-       unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);
-
-       switch (equivalent_usage) {
-       case HID_DG_BATTERYSTRENGTH:
-       case WACOM_HID_WD_BATTERY_LEVEL:
-       case WACOM_HID_WD_BATTERY_CHARGING:
-               features->quirks |= WACOM_QUIRK_BATTERY;
-               break;
-       }
+       return;
 }
 
 static void wacom_wac_battery_event(struct hid_device *hdev, struct hid_field *field,
@@ -1961,18 +1989,21 @@ static void wacom_wac_battery_event(struct hid_device *hdev, struct hid_field *f
                        wacom_wac->hid_data.bat_connected = 1;
                        wacom_wac->hid_data.bat_status = WACOM_POWER_SUPPLY_STATUS_AUTO;
                }
+               wacom_wac->features.quirks |= WACOM_QUIRK_BATTERY;
                break;
        case WACOM_HID_WD_BATTERY_LEVEL:
                value = value * 100 / (field->logical_maximum - field->logical_minimum);
                wacom_wac->hid_data.battery_capacity = value;
                wacom_wac->hid_data.bat_connected = 1;
                wacom_wac->hid_data.bat_status = WACOM_POWER_SUPPLY_STATUS_AUTO;
+               wacom_wac->features.quirks |= WACOM_QUIRK_BATTERY;
                break;
        case WACOM_HID_WD_BATTERY_CHARGING:
                wacom_wac->hid_data.bat_charging = value;
                wacom_wac->hid_data.ps_connected = value;
                wacom_wac->hid_data.bat_connected = 1;
                wacom_wac->hid_data.bat_status = WACOM_POWER_SUPPLY_STATUS_AUTO;
+               wacom_wac->features.quirks |= WACOM_QUIRK_BATTERY;
                break;
        }
 }
@@ -1988,18 +2019,15 @@ static void wacom_wac_battery_report(struct hid_device *hdev,
 {
        struct wacom *wacom = hid_get_drvdata(hdev);
        struct wacom_wac *wacom_wac = &wacom->wacom_wac;
-       struct wacom_features *features = &wacom_wac->features;
 
-       if (features->quirks & WACOM_QUIRK_BATTERY) {
-               int status = wacom_wac->hid_data.bat_status;
-               int capacity = wacom_wac->hid_data.battery_capacity;
-               bool charging = wacom_wac->hid_data.bat_charging;
-               bool connected = wacom_wac->hid_data.bat_connected;
-               bool powered = wacom_wac->hid_data.ps_connected;
+       int status = wacom_wac->hid_data.bat_status;
+       int capacity = wacom_wac->hid_data.battery_capacity;
+       bool charging = wacom_wac->hid_data.bat_charging;
+       bool connected = wacom_wac->hid_data.bat_connected;
+       bool powered = wacom_wac->hid_data.ps_connected;
 
-               wacom_notify_battery(wacom_wac, status, capacity, charging,
-                                    connected, powered);
-       }
+       wacom_notify_battery(wacom_wac, status, capacity, charging,
+                            connected, powered);
 }
 
 static void wacom_wac_pad_usage_mapping(struct hid_device *hdev,
@@ -3365,19 +3393,13 @@ static int wacom_status_irq(struct wacom_wac *wacom_wac, size_t len)
                int battery = (data[8] & 0x3f) * 100 / 31;
                bool charging = !!(data[8] & 0x80);
 
+               features->quirks |= WACOM_QUIRK_BATTERY;
                wacom_notify_battery(wacom_wac, WACOM_POWER_SUPPLY_STATUS_AUTO,
                                     battery, charging, battery || charging, 1);
-
-               if (!wacom->battery.battery &&
-                   !(features->quirks & WACOM_QUIRK_BATTERY)) {
-                       features->quirks |= WACOM_QUIRK_BATTERY;
-                       wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY);
-               }
        }
        else if ((features->quirks & WACOM_QUIRK_BATTERY) &&
                 wacom->battery.battery) {
                features->quirks &= ~WACOM_QUIRK_BATTERY;
-               wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY);
                wacom_notify_battery(wacom_wac, POWER_SUPPLY_STATUS_UNKNOWN, 0, 0, 0, 0);
        }
        return 0;
index 16f2213..1a40bb8 100644 (file)
@@ -324,6 +324,7 @@ struct hid_data {
        int ps_connected;
        bool pad_input_event_flag;
        unsigned short sequence_number;
+       int time_delayed;
 };
 
 struct wacom_remote_data {
index cc404bb..b581162 100644 (file)
@@ -1908,6 +1908,45 @@ static void __usb_queue_reset_device(struct work_struct *ws)
        usb_put_intf(iface);    /* Undo _get_ in usb_queue_reset_device() */
 }
 
+/*
+ * Internal function to set the wireless_status sysfs attribute
+ * See usb_set_wireless_status() for more details
+ */
+static void __usb_wireless_status_intf(struct work_struct *ws)
+{
+       struct usb_interface *iface =
+               container_of(ws, struct usb_interface, wireless_status_work);
+
+       device_lock(iface->dev.parent);
+       if (iface->sysfs_files_created)
+               usb_update_wireless_status_attr(iface);
+       device_unlock(iface->dev.parent);
+       usb_put_intf(iface);    /* Undo _get_ in usb_set_wireless_status() */
+}
+
+/**
+ * usb_set_wireless_status - sets the wireless_status struct member
+ * @iface: the interface to modify
+ * @status: the new wireless status
+ *
+ * Set the wireless_status struct member to the new value, and emit
+ * sysfs changes as necessary.
+ *
+ * Returns: 0 on success, -EALREADY if already set.
+ */
+int usb_set_wireless_status(struct usb_interface *iface,
+               enum usb_wireless_status status)
+{
+       if (iface->wireless_status == status)
+               return -EALREADY;
+
+       usb_get_intf(iface);
+       iface->wireless_status = status;
+       schedule_work(&iface->wireless_status_work);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(usb_set_wireless_status);
 
 /*
  * usb_set_configuration - Makes a particular device setting be current
@@ -2100,6 +2139,7 @@ free_interfaces:
                intf->dev.type = &usb_if_device_type;
                intf->dev.groups = usb_interface_groups;
                INIT_WORK(&intf->reset_ws, __usb_queue_reset_device);
+               INIT_WORK(&intf->wireless_status_work, __usb_wireless_status_intf);
                intf->minor = -1;
                device_initialize(&intf->dev);
                pm_runtime_no_callbacks(&intf->dev);
index b63f78e..323dc02 100644 (file)
@@ -1227,9 +1227,59 @@ static const struct attribute_group intf_assoc_attr_grp = {
        .is_visible =   intf_assoc_attrs_are_visible,
 };
 
+static ssize_t wireless_status_show(struct device *dev,
+                                   struct device_attribute *attr, char *buf)
+{
+       struct usb_interface *intf;
+
+       intf = to_usb_interface(dev);
+       if (intf->wireless_status == USB_WIRELESS_STATUS_DISCONNECTED)
+               return sysfs_emit(buf, "%s\n", "disconnected");
+       return sysfs_emit(buf, "%s\n", "connected");
+}
+static DEVICE_ATTR_RO(wireless_status);
+
+static struct attribute *intf_wireless_status_attrs[] = {
+       &dev_attr_wireless_status.attr,
+       NULL
+};
+
+static umode_t intf_wireless_status_attr_is_visible(struct kobject *kobj,
+               struct attribute *a, int n)
+{
+       struct device *dev = kobj_to_dev(kobj);
+       struct usb_interface *intf = to_usb_interface(dev);
+
+       if (a != &dev_attr_wireless_status.attr ||
+           intf->wireless_status != USB_WIRELESS_STATUS_NA)
+               return a->mode;
+       return 0;
+}
+
+static const struct attribute_group intf_wireless_status_attr_grp = {
+       .attrs =        intf_wireless_status_attrs,
+       .is_visible =   intf_wireless_status_attr_is_visible,
+};
+
+int usb_update_wireless_status_attr(struct usb_interface *intf)
+{
+       struct device *dev = &intf->dev;
+       int ret;
+
+       ret = sysfs_update_group(&dev->kobj, &intf_wireless_status_attr_grp);
+       if (ret < 0)
+               return ret;
+
+       sysfs_notify(&dev->kobj, NULL, "wireless_status");
+       kobject_uevent(&dev->kobj, KOBJ_CHANGE);
+
+       return 0;
+}
+
 const struct attribute_group *usb_interface_groups[] = {
        &intf_attr_grp,
        &intf_assoc_attr_grp,
+       &intf_wireless_status_attr_grp,
        NULL
 };
 
index 0eac7d4..3f14e15 100644 (file)
@@ -15,6 +15,7 @@ extern int usb_create_sysfs_dev_files(struct usb_device *dev);
 extern void usb_remove_sysfs_dev_files(struct usb_device *dev);
 extern void usb_create_sysfs_intf_files(struct usb_interface *intf);
 extern void usb_remove_sysfs_intf_files(struct usb_interface *intf);
+extern int usb_update_wireless_status_attr(struct usb_interface *intf);
 extern int usb_create_ep_devs(struct device *parent,
                                struct usb_host_endpoint *endpoint,
                                struct usb_device *udev);
index 1ea8c7a..4e4c4fe 100644 (file)
@@ -156,6 +156,7 @@ struct hid_item {
 #define HID_UP_DIGITIZER       0x000d0000
 #define HID_UP_PID             0x000f0000
 #define HID_UP_BATTERY         0x00850000
+#define HID_UP_CAMERA          0x00900000
 #define HID_UP_HPVENDOR         0xff7f0000
 #define HID_UP_HPVENDOR2        0xff010000
 #define HID_UP_MSVENDOR                0xff000000
@@ -873,7 +874,7 @@ extern bool hid_is_usb(const struct hid_device *hdev);
 /* We ignore a few input applications that are not widely used */
 #define IS_INPUT_APPLICATION(a) \
                (((a >= HID_UP_GENDESK) && (a <= HID_GD_MULTIAXIS)) \
-               || ((a >= HID_DG_PEN) && (a <= HID_DG_WHITEBOARD)) \
+               || ((a >= HID_DG_DIGITIZER) && (a <= HID_DG_WHITEBOARD)) \
                || (a == HID_GD_SYSTEM_CONTROL) || (a == HID_CP_CONSUMER_CONTROL) \
                || (a == HID_GD_WIRELESS_RADIO_CTLS))
 
index 9642ee0..0a81e0f 100644 (file)
@@ -170,6 +170,12 @@ usb_find_last_int_out_endpoint(struct usb_host_interface *alt,
        return usb_find_common_endpoints_reverse(alt, NULL, NULL, NULL, int_out);
 }
 
+enum usb_wireless_status {
+       USB_WIRELESS_STATUS_NA = 0,
+       USB_WIRELESS_STATUS_DISCONNECTED,
+       USB_WIRELESS_STATUS_CONNECTED,
+};
+
 /**
  * struct usb_interface - what usb device drivers talk to
  * @altsetting: array of interface structures, one for each alternate
@@ -197,6 +203,10 @@ usb_find_last_int_out_endpoint(struct usb_host_interface *alt,
  *     following a reset or suspend operation it doesn't support.
  * @authorized: This allows to (de)authorize individual interfaces instead
  *     a whole device in contrast to the device authorization.
+ * @wireless_status: if the USB device uses a receiver/emitter combo, whether
+ *     the emitter is connected.
+ * @wireless_status_work: Used for scheduling wireless status changes
+ *     from atomic context.
  * @dev: driver model's view of this device
  * @usb_dev: if an interface is bound to the USB major, this will point
  *     to the sysfs representation for that device.
@@ -253,6 +263,8 @@ struct usb_interface {
        unsigned needs_binding:1;       /* needs delayed unbind/rebind */
        unsigned resetting_device:1;    /* true: bandwidth alloc after reset */
        unsigned authorized:1;          /* used for interface authorization */
+       enum usb_wireless_status wireless_status;
+       struct work_struct wireless_status_work;
 
        struct device dev;              /* interface specific device info */
        struct device *usb_dev;
@@ -887,6 +899,10 @@ static inline int usb_interface_claimed(struct usb_interface *iface)
 
 extern void usb_driver_release_interface(struct usb_driver *driver,
                        struct usb_interface *iface);
+
+int usb_set_wireless_status(struct usb_interface *iface,
+                       enum usb_wireless_status status);
+
 const struct usb_device_id *usb_match_id(struct usb_interface *interface,
                                         const struct usb_device_id *id);
 extern int usb_match_one_id(struct usb_interface *interface,
index 83e8f87..01c0491 100644 (file)
@@ -5,6 +5,18 @@ include ../../../build/Build.include
 include ../../../scripts/Makefile.arch
 include ../../../scripts/Makefile.include
 
+TEST_PROGS := hid-core.sh
+TEST_PROGS += hid-apple.sh
+TEST_PROGS += hid-gamepad.sh
+TEST_PROGS += hid-ite.sh
+TEST_PROGS += hid-keyboard.sh
+TEST_PROGS += hid-mouse.sh
+TEST_PROGS += hid-multitouch.sh
+TEST_PROGS += hid-sony.sh
+TEST_PROGS += hid-tablet.sh
+TEST_PROGS += hid-usb_crash.sh
+TEST_PROGS += hid-wacom.sh
+
 CXX ?= $(CROSS_COMPILE)g++
 
 HOSTPKG_CONFIG := pkg-config
index 5b5cef4..4f42517 100644 (file)
@@ -20,3 +20,14 @@ CONFIG_HID=y
 CONFIG_HID_BPF=y
 CONFIG_INPUT_EVDEV=y
 CONFIG_UHID=y
+CONFIG_LEDS_CLASS_MULTICOLOR=y
+CONFIG_USB=y
+CONFIG_USB_HID=y
+CONFIG_HID_APPLE=y
+CONFIG_HID_ITE=y
+CONFIG_HID_MULTITOUCH=y
+CONFIG_HID_PLAYSTATION=y
+CONFIG_PLAYSTATION_FF=y
+CONFIG_HID_SONY=y
+CONFIG_SONY_FF=y
+CONFIG_HID_WACOM=y
diff --git a/tools/testing/selftests/hid/hid-apple.sh b/tools/testing/selftests/hid/hid-apple.sh
new file mode 100755 (executable)
index 0000000..656f2d5
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_apple_keyboard.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-core.sh b/tools/testing/selftests/hid/hid-core.sh
new file mode 100755 (executable)
index 0000000..5bbabc1
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_hid_core.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-gamepad.sh b/tools/testing/selftests/hid/hid-gamepad.sh
new file mode 100755 (executable)
index 0000000..1ba00c0
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_gamepad.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-ite.sh b/tools/testing/selftests/hid/hid-ite.sh
new file mode 100755 (executable)
index 0000000..52c5ccf
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_ite_keyboard.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-keyboard.sh b/tools/testing/selftests/hid/hid-keyboard.sh
new file mode 100755 (executable)
index 0000000..55368f1
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_keyboard.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-mouse.sh b/tools/testing/selftests/hid/hid-mouse.sh
new file mode 100755 (executable)
index 0000000..7b4ad4f
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_mouse.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-multitouch.sh b/tools/testing/selftests/hid/hid-multitouch.sh
new file mode 100755 (executable)
index 0000000..d03a1dd
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_multitouch.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-sony.sh b/tools/testing/selftests/hid/hid-sony.sh
new file mode 100755 (executable)
index 0000000..c863c44
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_sony.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-tablet.sh b/tools/testing/selftests/hid/hid-tablet.sh
new file mode 100755 (executable)
index 0000000..e86b3fe
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_tablet.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-usb_crash.sh b/tools/testing/selftests/hid/hid-usb_crash.sh
new file mode 100755 (executable)
index 0000000..3f0debe
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_usb_crash.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-wacom.sh b/tools/testing/selftests/hid/hid-wacom.sh
new file mode 100755 (executable)
index 0000000..1630c22
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_wacom_generic.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/run-hid-tools-tests.sh b/tools/testing/selftests/hid/run-hid-tools-tests.sh
new file mode 100755 (executable)
index 0000000..bdae846
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+if ! command -v python3 > /dev/null 2>&1; then
+       echo "hid-tools: [SKIP] python3 not installed"
+       exit 77
+fi
+
+if ! python3 -c "import pytest" > /dev/null 2>&1; then
+       echo "hid: [SKIP/ pytest module not installed"
+       exit 77
+fi
+
+if ! python3 -c "import pytest_tap" > /dev/null 2>&1; then
+       echo "hid: [SKIP/ pytest_tap module not installed"
+       exit 77
+fi
+
+if ! python3 -c "import hidtools" > /dev/null 2>&1; then
+       echo "hid: [SKIP/ hid-tools module not installed"
+       exit 77
+fi
+
+TARGET=${TARGET:=.}
+
+echo TAP version 13
+python3 -u -m pytest $PYTEST_XDIST ./tests/$TARGET --tap-stream --udevd
diff --git a/tools/testing/selftests/hid/settings b/tools/testing/selftests/hid/settings
new file mode 100644 (file)
index 0000000..b3cbfc5
--- /dev/null
@@ -0,0 +1,3 @@
+# HID tests can be long, so give a little bit more time
+# to them
+timeout=200
diff --git a/tools/testing/selftests/hid/tests/__init__.py b/tools/testing/selftests/hid/tests/__init__.py
new file mode 100644 (file)
index 0000000..c940e92
--- /dev/null
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+# Just to make sphinx-apidoc document this directory
diff --git a/tools/testing/selftests/hid/tests/base.py b/tools/testing/selftests/hid/tests/base.py
new file mode 100644 (file)
index 0000000..1305cfc
--- /dev/null
@@ -0,0 +1,345 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+
+import libevdev
+import os
+import pytest
+import time
+
+import logging
+
+from hidtools.device.base_device import BaseDevice, EvdevMatch, SysfsFile
+from pathlib import Path
+from typing import Final
+
+logger = logging.getLogger("hidtools.test.base")
+
+# application to matches
+application_matches: Final = {
+    # pyright: ignore
+    "Accelerometer": EvdevMatch(
+        req_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ]
+    ),
+    "Game Pad": EvdevMatch(  # in systemd, this is a lot more complex, but that will do
+        requires=[
+            libevdev.EV_ABS.ABS_X,
+            libevdev.EV_ABS.ABS_Y,
+            libevdev.EV_ABS.ABS_RX,
+            libevdev.EV_ABS.ABS_RY,
+            libevdev.EV_KEY.BTN_START,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Joystick": EvdevMatch(  # in systemd, this is a lot more complex, but that will do
+        requires=[
+            libevdev.EV_ABS.ABS_RX,
+            libevdev.EV_ABS.ABS_RY,
+            libevdev.EV_KEY.BTN_START,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Key": EvdevMatch(
+        requires=[
+            libevdev.EV_KEY.KEY_A,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+            libevdev.INPUT_PROP_DIRECT,
+            libevdev.INPUT_PROP_POINTER,
+        ],
+    ),
+    "Mouse": EvdevMatch(
+        requires=[
+            libevdev.EV_REL.REL_X,
+            libevdev.EV_REL.REL_Y,
+            libevdev.EV_KEY.BTN_LEFT,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Pad": EvdevMatch(
+        requires=[
+            libevdev.EV_KEY.BTN_0,
+        ],
+        excludes=[
+            libevdev.EV_KEY.BTN_TOOL_PEN,
+            libevdev.EV_KEY.BTN_TOUCH,
+            libevdev.EV_ABS.ABS_DISTANCE,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Pen": EvdevMatch(
+        requires=[
+            libevdev.EV_KEY.BTN_STYLUS,
+            libevdev.EV_ABS.ABS_X,
+            libevdev.EV_ABS.ABS_Y,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Stylus": EvdevMatch(
+        requires=[
+            libevdev.EV_KEY.BTN_STYLUS,
+            libevdev.EV_ABS.ABS_X,
+            libevdev.EV_ABS.ABS_Y,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Touch Pad": EvdevMatch(
+        requires=[
+            libevdev.EV_KEY.BTN_LEFT,
+            libevdev.EV_ABS.ABS_X,
+            libevdev.EV_ABS.ABS_Y,
+        ],
+        excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS],
+        req_properties=[
+            libevdev.INPUT_PROP_POINTER,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Touch Screen": EvdevMatch(
+        requires=[
+            libevdev.EV_KEY.BTN_TOUCH,
+            libevdev.EV_ABS.ABS_X,
+            libevdev.EV_ABS.ABS_Y,
+        ],
+        excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS],
+        req_properties=[
+            libevdev.INPUT_PROP_DIRECT,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+}
+
+
+class UHIDTestDevice(BaseDevice):
+    def __init__(self, name, application, rdesc_str=None, rdesc=None, input_info=None):
+        super().__init__(name, application, rdesc_str, rdesc, input_info)
+        self.application_matches = application_matches
+        if name is None:
+            name = f"uhid test {self.__class__.__name__}"
+        if not name.startswith("uhid test "):
+            name = "uhid test " + self.name
+        self.name = name
+
+
+class BaseTestCase:
+    class TestUhid(object):
+        syn_event = libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT)  # type: ignore
+        key_event = libevdev.InputEvent(libevdev.EV_KEY)  # type: ignore
+        abs_event = libevdev.InputEvent(libevdev.EV_ABS)  # type: ignore
+        rel_event = libevdev.InputEvent(libevdev.EV_REL)  # type: ignore
+        msc_event = libevdev.InputEvent(libevdev.EV_MSC.MSC_SCAN)  # type: ignore
+
+        # List of kernel modules to load before starting the test
+        # if any module is not available (not compiled), the test will skip.
+        # Each element is a tuple '(kernel driver name, kernel module)',
+        # for example ("playstation", "hid-playstation")
+        kernel_modules = []
+
+        def assertInputEventsIn(self, expected_events, effective_events):
+            effective_events = effective_events.copy()
+            for ev in expected_events:
+                assert ev in effective_events
+                effective_events.remove(ev)
+            return effective_events
+
+        def assertInputEvents(self, expected_events, effective_events):
+            remaining = self.assertInputEventsIn(expected_events, effective_events)
+            assert remaining == []
+
+        @classmethod
+        def debug_reports(cls, reports, uhdev=None, events=None):
+            data = [" ".join([f"{v:02x}" for v in r]) for r in reports]
+
+            if uhdev is not None:
+                human_data = [
+                    uhdev.parsed_rdesc.format_report(r, split_lines=True)
+                    for r in reports
+                ]
+                try:
+                    human_data = [
+                        f'\n\t       {" " * h.index("/")}'.join(h.split("\n"))
+                        for h in human_data
+                    ]
+                except ValueError:
+                    # '/' not found: not a numbered report
+                    human_data = ["\n\t      ".join(h.split("\n")) for h in human_data]
+                data = [f"{d}\n\t ====> {h}" for d, h in zip(data, human_data)]
+
+            reports = data
+
+            if len(reports) == 1:
+                print("sending 1 report:")
+            else:
+                print(f"sending {len(reports)} reports:")
+            for report in reports:
+                print("\t", report)
+
+            if events is not None:
+                print("events received:", events)
+
+        def create_device(self):
+            raise Exception("please reimplement me in subclasses")
+
+        def _load_kernel_module(self, kernel_driver, kernel_module):
+            sysfs_path = Path("/sys/bus/hid/drivers")
+            if kernel_driver is not None:
+                sysfs_path /= kernel_driver
+            else:
+                # special case for when testing all available modules:
+                # we don't know beforehand the name of the module from modinfo
+                sysfs_path = Path("/sys/module") / kernel_module.replace("-", "_")
+            if not sysfs_path.exists():
+                import subprocess
+
+                ret = subprocess.run(["/usr/sbin/modprobe", kernel_module])
+                if ret.returncode != 0:
+                    pytest.skip(
+                        f"module {kernel_module} could not be loaded, skipping the test"
+                    )
+
+        @pytest.fixture()
+        def load_kernel_module(self):
+            for kernel_driver, kernel_module in self.kernel_modules:
+                self._load_kernel_module(kernel_driver, kernel_module)
+            yield
+
+        @pytest.fixture()
+        def new_uhdev(self, load_kernel_module):
+            return self.create_device()
+
+        def assertName(self, uhdev):
+            evdev = uhdev.get_evdev()
+            assert uhdev.name in evdev.name
+
+        @pytest.fixture(autouse=True)
+        def context(self, new_uhdev, request):
+            try:
+                with HIDTestUdevRule.instance():
+                    with new_uhdev as self.uhdev:
+                        skip_cond = request.node.get_closest_marker("skip_if_uhdev")
+                        if skip_cond:
+                            test, message, *rest = skip_cond.args
+
+                            if test(self.uhdev):
+                                pytest.skip(message)
+
+                        self.uhdev.create_kernel_device()
+                        now = time.time()
+                        while not self.uhdev.is_ready() and time.time() - now < 5:
+                            self.uhdev.dispatch(1)
+                        if self.uhdev.get_evdev() is None:
+                            logger.warning(
+                                f"available list of input nodes: (default application is '{self.uhdev.application}')"
+                            )
+                            logger.warning(self.uhdev.input_nodes)
+                        yield
+                        self.uhdev = None
+            except PermissionError:
+                pytest.skip("Insufficient permissions, run me as root")
+
+        @pytest.fixture(autouse=True)
+        def check_taint(self):
+            # we are abusing SysfsFile here, it's in /proc, but meh
+            taint_file = SysfsFile("/proc/sys/kernel/tainted")
+            taint = taint_file.int_value
+
+            yield
+
+            assert taint_file.int_value == taint
+
+        def test_creation(self):
+            """Make sure the device gets processed by the kernel and creates
+            the expected application input node.
+
+            If this fail, there is something wrong in the device report
+            descriptors."""
+            uhdev = self.uhdev
+            assert uhdev is not None
+            assert uhdev.get_evdev() is not None
+            self.assertName(uhdev)
+            assert len(uhdev.next_sync_events()) == 0
+            assert uhdev.get_evdev() is not None
+
+
+class HIDTestUdevRule(object):
+    _instance = None
+    """
+    A context-manager compatible class that sets up our udev rules file and
+    deletes it on context exit.
+
+    This class is tailored to our test setup: it only sets up the udev rule
+    on the **second** context and it cleans it up again on the last context
+    removed. This matches the expected pytest setup: we enter a context for
+    the session once, then once for each test (the first of which will
+    trigger the udev rule) and once the last test exited and the session
+    exited, we clean up after ourselves.
+    """
+
+    def __init__(self):
+        self.refs = 0
+        self.rulesfile = None
+
+    def __enter__(self):
+        self.refs += 1
+        if self.refs == 2 and self.rulesfile is None:
+            self.create_udev_rule()
+            self.reload_udev_rules()
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.refs -= 1
+        if self.refs == 0 and self.rulesfile:
+            os.remove(self.rulesfile.name)
+            self.reload_udev_rules()
+
+    def reload_udev_rules(self):
+        import subprocess
+
+        subprocess.run("udevadm control --reload-rules".split())
+        subprocess.run("systemd-hwdb update".split())
+
+    def create_udev_rule(self):
+        import tempfile
+
+        os.makedirs("/run/udev/rules.d", exist_ok=True)
+        with tempfile.NamedTemporaryFile(
+            prefix="91-uhid-test-device-REMOVEME-",
+            suffix=".rules",
+            mode="w+",
+            dir="/run/udev/rules.d",
+            delete=False,
+        ) as f:
+            f.write(
+                'KERNELS=="*input*", ATTRS{name}=="*uhid test *", ENV{LIBINPUT_IGNORE_DEVICE}="1"\n'
+            )
+            f.write(
+                'KERNELS=="*input*", ATTRS{name}=="*uhid test * System Multi Axis", ENV{ID_INPUT_TOUCHSCREEN}="", ENV{ID_INPUT_SYSTEM_MULTIAXIS}="1"\n'
+            )
+            self.rulesfile = f
+
+    @classmethod
+    def instance(cls):
+        if not cls._instance:
+            cls._instance = HIDTestUdevRule()
+        return cls._instance
diff --git a/tools/testing/selftests/hid/tests/conftest.py b/tools/testing/selftests/hid/tests/conftest.py
new file mode 100644 (file)
index 0000000..1361ec9
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+
+import platform
+import pytest
+import re
+import resource
+import subprocess
+from .base import HIDTestUdevRule
+from pathlib import Path
+
+
+# See the comment in HIDTestUdevRule, this doesn't set up but it will clean
+# up once the last test exited.
+@pytest.fixture(autouse=True, scope="session")
+def udev_rules_session_setup():
+    with HIDTestUdevRule.instance():
+        yield
+
+
+@pytest.fixture(autouse=True, scope="session")
+def setup_rlimit():
+    resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
+
+
+@pytest.fixture(autouse=True, scope="session")
+def start_udevd(pytestconfig):
+    if pytestconfig.getoption("udevd"):
+        import subprocess
+
+        with subprocess.Popen("/usr/lib/systemd/systemd-udevd") as proc:
+            yield
+            proc.kill()
+    else:
+        yield
+
+
+def pytest_configure(config):
+    config.addinivalue_line(
+        "markers",
+        "skip_if_uhdev(condition, message): mark test to skip if the condition on the uhdev device is met",
+    )
+
+
+# Generate the list of modules and modaliases
+# for the tests that need to be parametrized with those
+def pytest_generate_tests(metafunc):
+    if "usbVidPid" in metafunc.fixturenames:
+        modules = (
+            Path("/lib/modules/")
+            / platform.uname().release
+            / "kernel"
+            / "drivers"
+            / "hid"
+        )
+
+        modalias_re = re.compile(r"alias:\s+hid:b0003g.*v([0-9a-fA-F]+)p([0-9a-fA-F]+)")
+
+        params = []
+        ids = []
+        for module in modules.glob("*.ko"):
+            p = subprocess.run(
+                ["modinfo", module], capture_output=True, check=True, encoding="utf-8"
+            )
+            for line in p.stdout.split("\n"):
+                m = modalias_re.match(line)
+                if m is not None:
+                    vid, pid = m.groups()
+                    vid = int(vid, 16)
+                    pid = int(pid, 16)
+                    params.append([module.name.replace(".ko", ""), vid, pid])
+                    ids.append(f"{module.name} {vid:04x}:{pid:04x}")
+        metafunc.parametrize("usbVidPid", params, ids=ids)
+
+
+def pytest_addoption(parser):
+    parser.addoption("--udevd", action="store_true", default=False)
diff --git a/tools/testing/selftests/hid/tests/descriptors_wacom.py b/tools/testing/selftests/hid/tests/descriptors_wacom.py
new file mode 100644 (file)
index 0000000..91c16e0
--- /dev/null
@@ -0,0 +1,1360 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# fmt: off
+wacom_pth660_v145 = [
+    0x05, 0x01,                     # . Usage Page (Desktop),
+    0x09, 0x02,                     # . Usage (Mouse),
+    0xA1, 0x01,                     # . Collection (Application),
+    0x85, 0x01,                     # .     Report ID (1),
+    0x09, 0x01,                     # .     Usage (Pointer),
+    0xA1, 0x00,                     # .     Collection (Physical),
+    0x05, 0x09,                     # .         Usage Page (Button),
+    0x19, 0x01,                     # .         Usage Minimum (01h),
+    0x29, 0x03,                     # .         Usage Maximum (03h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x01,                     # .         Report Size (1),
+    0x95, 0x03,                     # .         Report Count (3),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x75, 0x01,                     # .         Report Size (1),
+    0x95, 0x05,                     # .         Report Count (5),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0x05, 0x01,                     # .         Usage Page (Desktop),
+    0x09, 0x30,                     # .         Usage (X),
+    0x09, 0x31,                     # .         Usage (Y),
+    0x15, 0x81,                     # .         Logical Minimum (-127),
+    0x25, 0x7F,                     # .         Logical Maximum (127),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x02,                     # .         Report Count (2),
+    0x81, 0x06,                     # .         Input (Variable, Relative),
+    0xC0,                           # .     End Collection,
+    0xC0,                           # . End Collection,
+    0x06, 0x0D, 0xFF,               # . Usage Page (FF0Dh),
+    0x09, 0x01,                     # . Usage (01h),
+    0xA1, 0x01,                     # . Collection (Application),
+    0x85, 0x10,                     # .     Report ID (16),
+    0x09, 0x20,                     # .     Usage (20h),
+    0xA1, 0x00,                     # .     Collection (Physical),
+    0x09, 0x42,                     # .         Usage (42h),
+    0x09, 0x44,                     # .         Usage (44h),
+    0x09, 0x5A,                     # .         Usage (5Ah),
+    0x09, 0x45,                     # .         Usage (45h),
+    0x09, 0x3C,                     # .         Usage (3Ch),
+    0x09, 0x32,                     # .         Usage (32h),
+    0x09, 0x36,                     # .         Usage (36h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x01,                     # .         Report Size (1),
+    0x95, 0x07,                     # .         Report Count (7),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0x0A, 0x30, 0x01,               # .         Usage (0130h),
+    0x65, 0x11,                     # .         Unit (Centimeter),
+    0x55, 0x0D,                     # .         Unit Exponent (13),
+    0x35, 0x00,                     # .         Physical Minimum (0),
+    0x47, 0x80, 0x57, 0x00, 0x00,   # .         Physical Maximum (22400),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x27, 0x00, 0xAF, 0x00, 0x00,   # .         Logical Maximum (44800),
+    0x75, 0x18,                     # .         Report Size (24),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x0A, 0x31, 0x01,               # .         Usage (0131h),
+    0x47, 0xD0, 0x39, 0x00, 0x00,   # .         Physical Maximum (14800),
+    0x27, 0xA0, 0x73, 0x00, 0x00,   # .         Logical Maximum (29600),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x30,                     # .         Usage (30h),
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x65, 0x00,                     # .         Unit,
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0xFF, 0x1F,               # .         Logical Maximum (8191),         # !!! Errata: Missing Physical Max = 0
+    0x75, 0x10,                     # .         Report Size (16),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x3D,                     # .         Usage (3Dh),
+    0x09, 0x3E,                     # .         Usage (3Eh),
+    0x65, 0x14,                     # .         Unit (Degrees),
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x35, 0xC0,                     # .         Physical Minimum (-64),
+    0x45, 0x3F,                     # .         Physical Maximum (63),
+    0x15, 0xC0,                     # .         Logical Minimum (-64),
+    0x25, 0x3F,                     # .         Logical Maximum (63),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x02,                     # .         Report Count (2),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x41,                     # .         Usage (41h),
+    0x65, 0x14,                     # .         Unit (Degrees),
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x36, 0x4C, 0xFF,               # .         Physical Minimum (-180),
+    0x46, 0xB3, 0x00,               # .         Physical Maximum (179),
+    0x16, 0x7C, 0xFC,               # .         Logical Minimum (-900),
+    0x26, 0x83, 0x03,               # .         Logical Maximum (899),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x0A,                     # .         Input (Variable, Wrap),
+    0x0A, 0x03, 0x0D,               # .         Usage (0D03h),
+    0x65, 0x00,                     # .         Unit,
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0xFF, 0x07,               # .         Logical Maximum (2047),         # !!! Errata: Missing Physical Min/Max = 0
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x0A, 0x32, 0x01,               # .         Usage (0132h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x3F,                     # .         Logical Maximum (63),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x5B,                     # .         Usage (5Bh),
+    0x09, 0x5C,                     # .         Usage (5Ch),
+    0x17, 0x00, 0x00, 0x00, 0x80,   # .         Logical Minimum (-2147483648),
+    0x27, 0xFF, 0xFF, 0xFF, 0x7F,   # .         Logical Maximum (2147483647),
+    0x75, 0x20,                     # .         Report Size (32),
+    0x95, 0x02,                     # .         Report Count (2),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x77,                     # .         Usage (77h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0xFF, 0x0F,               # .         Logical Maximum (4095),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0xC0,                           # .     End Collection,
+    0x85, 0x11,                     # .     Report ID (17),
+    0x09, 0x39,                     # .     Usage (39h),
+    0xA1, 0x00,                     # .     Collection (Physical),
+    0x1A, 0x10, 0x09,               # .         Usage Minimum (0910h),
+    0x2A, 0x17, 0x09,               # .         Usage Maximum (0917h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x01,                     # .         Report Size (1),
+    0x95, 0x08,                     # .         Report Count (8),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x1A, 0x40, 0x09,               # .         Usage Minimum (0940h),
+    0x2A, 0x47, 0x09,               # .         Usage Maximum (0947h),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x0A, 0x95, 0x09,               # .         Usage (0995h),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x95, 0x07,                     # .         Report Count (7),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0x0A, 0x38, 0x01,               # .         Usage (0138h),
+    0x65, 0x14,                     # .         Unit (Degrees),
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x35, 0x00,                     # .         Physical Minimum (0),
+    0x46, 0x67, 0x01,               # .         Physical Maximum (359),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x47,                     # .         Logical Maximum (71),
+    0x75, 0x07,                     # .         Report Size (7),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x0A,                     # .         Input (Variable, Wrap),
+    0x0A, 0x39, 0x01,               # .         Usage (0139h),
+    0x65, 0x00,                     # .         Unit,
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),            # !!! Errata: Missing Physical Max = 0
+    0x75, 0x01,                     # .         Report Size (1),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x04,                     # .         Report Count (4),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0xC0,                           # .     End Collection,
+    0x85, 0x13,                     # .     Report ID (19),
+    0x0A, 0x13, 0x10,               # .     Usage (1013h),
+    0xA1, 0x00,                     # .     Collection (Physical),
+    0x0A, 0x3B, 0x04,               # .         Usage (043Bh),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x64,                     # .         Logical Maximum (100),
+    0x75, 0x07,                     # .         Report Size (7),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x0A, 0x04, 0x04,               # .         Usage (0404h),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x01,                     # .         Report Size (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x0A, 0x52, 0x04,               # .         Usage (0452h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x01,                     # .         Report Size (1),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x95, 0x06,                     # .         Report Count (6),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0x0A, 0x54, 0x04,               # .         Usage (0454h),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x06,                     # .         Report Count (6),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0xC0,                           # .     End Collection,
+    0x09, 0x0E,                     # .     Usage (0Eh),
+    0xA1, 0x02,                     # .     Collection (Logical),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x85, 0x02,                     # .         Report ID (2),
+    0x09, 0x01,                     # .         Usage (01h),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x03,                     # .         Report ID (3),
+    0x0A, 0x03, 0x10,               # .         Usage (1003h),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x04,                     # .         Report ID (4),
+    0x0A, 0x04, 0x10,               # .         Usage (1004h),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x07,                     # .         Report ID (7),
+    0x0A, 0x09, 0x10,               # .         Usage (1009h),
+    0x25, 0x02,                     # .         Logical Maximum (2),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x03,                     # .         Feature (Constant, Variable),
+    0x0A, 0x07, 0x10,               # .         Usage (1007h),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x0A, 0x08, 0x10,               # .         Usage (1008h),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x27, 0xFF, 0xFF, 0x00, 0x00,   # .         Logical Maximum (65535),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x06,                     # .         Report Count (6),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x03,                     # .         Feature (Constant, Variable),
+    0x85, 0x0C,                     # .         Report ID (12),
+    0x0A, 0x30, 0x0D,               # .         Usage (0D30h),
+    0x0A, 0x31, 0x0D,               # .         Usage (0D31h),
+    0x0A, 0x32, 0x0D,               # .         Usage (0D32h),
+    0x0A, 0x33, 0x0D,               # .         Usage (0D33h),                  # !!! Errata: Missing Non-zero Physical Max
+    0x65, 0x11,                     # .         Unit (Centimeter),
+    0x55, 0x0D,                     # .         Unit Exponent (13),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x03,                     # .         Feature (Constant, Variable),
+    0x85, 0x0D,                     # .         Report ID (13),
+    0x65, 0x00,                     # .         Unit,
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x0A, 0x0D, 0x10,               # .         Usage (100Dh),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x14,                     # .         Report ID (20),
+    0x0A, 0x14, 0x10,               # .         Usage (1014h),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x95, 0x0D,                     # .         Report Count (13),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x31,                     # .         Report ID (49),
+    0x0A, 0x31, 0x10,               # .         Usage (1031h),
+    0x25, 0x64,                     # .         Logical Maximum (100),
+    0x95, 0x05,                     # .         Report Count (5),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x32,                     # .         Report ID (50),
+    0x0A, 0x31, 0x10,               # .         Usage (1031h),
+    0x25, 0x64,                     # .         Logical Maximum (100),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x0A, 0x32, 0x10,               # .         Usage (1032h),
+    0x25, 0x03,                     # .         Logical Maximum (3),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x34,                     # .         Report ID (52),
+    0x0A, 0x34, 0x10,               # .         Usage (1034h),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x35,                     # .         Report ID (53),
+    0x0A, 0x35, 0x10,               # .         Usage (1035h),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x95, 0x0A,                     # .         Report Count (10),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x36,                     # .         Report ID (54),
+    0x0A, 0x35, 0x10,               # .         Usage (1035h),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x96, 0x01, 0x01,               # .         Report Count (257),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0xCC,                     # .         Report ID (204),
+    0x0A, 0xCC, 0x10,               # .         Usage (10CCh),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x95, 0x02,                     # .         Report Count (2),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0xC0,                           # .     End Collection,
+    0x0A, 0xAC, 0x10,               # .     Usage (10ACh),
+    0xA1, 0x02,                     # .     Collection (Logical),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x85, 0xAC,                     # .         Report ID (172),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0xBF,                     # .         Report Count (191),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x85, 0x33,                     # .         Report ID (51),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x12,                     # .         Report Count (18),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x64,                     # .         Report ID (100),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x0C,                     # .         Report Count (12),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x15,                     # .         Report ID (21),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x0E,                     # .         Report Count (14),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x12,                     # .         Report ID (18),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x16,                     # .         Report ID (22),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x0E,                     # .         Report Count (14),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x40,                     # .         Report ID (64),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x41,                     # .         Report ID (65),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x42,                     # .         Report ID (66),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x43,                     # .         Report ID (67),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x0D,                     # .         Report Count (13),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x44,                     # .         Report ID (68),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x3F,                     # .         Report Count (63),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x45,                     # .         Report ID (69),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x20,                     # .         Report Count (32),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x60,                     # .         Report ID (96),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x3F,                     # .         Report Count (63),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x61,                     # .         Report ID (97),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x3E,                     # .         Report Count (62),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x62,                     # .         Report ID (98),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x3E,                     # .         Report Count (62),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0xC0,                           # .     End Collection,
+    0x85, 0xD0,                     # .     Report ID (208),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x08, 0x00,               # .     Report Count (8),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD1,                     # .     Report ID (209),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x01,               # .     Report Count (260),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD2,                     # .     Report ID (210),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x01,               # .     Report Count (260),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD3,                     # .     Report ID (211),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD4,                     # .     Report ID (212),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD5,                     # .     Report ID (213),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD6,                     # .     Report ID (214),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD7,                     # .     Report ID (215),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x08, 0x00,               # .     Report Count (8),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD8,                     # .     Report ID (216),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x0C, 0x00,               # .     Report Count (12),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD9,                     # .     Report ID (217),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x00, 0x0A,               # .     Report Count (2560),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDA,                     # .     Report ID (218),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x04,               # .     Report Count (1028),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDB,                     # .     Report ID (219),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x06, 0x00,               # .     Report Count (6),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDC,                     # .     Report ID (220),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x02, 0x00,               # .     Report Count (2),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDD,                     # .     Report ID (221),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDE,                     # .     Report ID (222),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDF,                     # .     Report ID (223),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x22, 0x00,               # .     Report Count (34),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE0,                     # .     Report ID (224),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x01, 0x00,               # .     Report Count (1),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE1,                     # .     Report ID (225),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x02, 0x00,               # .     Report Count (2),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE2,                     # .     Report ID (226),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x02, 0x00,               # .     Report Count (2),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE3,                     # .     Report ID (227),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x02, 0x00,               # .     Report Count (2),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE4,                     # .     Report ID (228),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0xFF, 0x01,               # .     Report Count (511),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0xC0                            # . End Collection
+]
+# fmt: on
+
+# Report ID (20), Usage (1014h), Report Count (13) -> 15
+wacom_pth660_v150 = wacom_pth660_v145.copy()
+wacom_pth660_v150[0x2CB] = 0x0F
+
+# fmt: off
+wacom_pth860_v145 = [
+    0x05, 0x01,                     # . Usage Page (Desktop),
+    0x09, 0x02,                     # . Usage (Mouse),
+    0xA1, 0x01,                     # . Collection (Application),
+    0x85, 0x01,                     # .     Report ID (1),
+    0x09, 0x01,                     # .     Usage (Pointer),
+    0xA1, 0x00,                     # .     Collection (Physical),
+    0x05, 0x09,                     # .         Usage Page (Button),
+    0x19, 0x01,                     # .         Usage Minimum (01h),
+    0x29, 0x03,                     # .         Usage Maximum (03h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x01,                     # .         Report Size (1),
+    0x95, 0x03,                     # .         Report Count (3),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x95, 0x05,                     # .         Report Count (5),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0x05, 0x01,                     # .         Usage Page (Desktop),
+    0x09, 0x30,                     # .         Usage (X),
+    0x09, 0x31,                     # .         Usage (Y),
+    0x15, 0x80,                     # .         Logical Minimum (-128),
+    0x25, 0x7F,                     # .         Logical Maximum (127),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x02,                     # .         Report Count (2),
+    0x81, 0x06,                     # .         Input (Variable, Relative),
+    0xC0,                           # .     End Collection,
+    0xC0,                           # . End Collection,
+    0x06, 0x0D, 0xFF,               # . Usage Page (FF0Dh),
+    0x09, 0x01,                     # . Usage (01h),
+    0xA1, 0x01,                     # . Collection (Application),
+    0x85, 0x10,                     # .     Report ID (16),
+    0x09, 0x20,                     # .     Usage (20h),
+    0xA1, 0x00,                     # .     Collection (Physical),
+    0x09, 0x42,                     # .         Usage (42h),
+    0x09, 0x44,                     # .         Usage (44h),
+    0x09, 0x5A,                     # .         Usage (5Ah),
+    0x09, 0x45,                     # .         Usage (45h),
+    0x09, 0x3C,                     # .         Usage (3Ch),
+    0x09, 0x32,                     # .         Usage (32h),
+    0x09, 0x36,                     # .         Usage (36h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x01,                     # .         Report Size (1),
+    0x95, 0x07,                     # .         Report Count (7),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0x0A, 0x30, 0x01,               # .         Usage (0130h),
+    0x65, 0x11,                     # .         Unit (Centimeter),
+    0x55, 0x0D,                     # .         Unit Exponent (13),
+    0x35, 0x00,                     # .         Physical Minimum (0),
+    0x47, 0x7C, 0x79, 0x00, 0x00,   # .         Physical Maximum (31100),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x27, 0xF8, 0xF2, 0x00, 0x00,   # .         Logical Maximum (62200),
+    0x75, 0x18,                     # .         Report Size (24),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x0A, 0x31, 0x01,               # .         Usage (0131h),
+    0x47, 0x60, 0x54, 0x00, 0x00,   # .         Physical Maximum (21600),
+    0x27, 0xC0, 0xA8, 0x00, 0x00,   # .         Logical Maximum (43200),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x30,                     # .         Usage (30h),                    # !!! Errata: Missing Physical Max = 0
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x65, 0x00,                     # .         Unit,
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0xFF, 0x1F,               # .         Logical Maximum (8191),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x3D,                     # .         Usage (3Dh),
+    0x09, 0x3E,                     # .         Usage (3Eh),
+    0x65, 0x14,                     # .         Unit (Degrees),
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x35, 0xC0,                     # .         Physical Minimum (-64),
+    0x45, 0x3F,                     # .         Physical Maximum (63),
+    0x15, 0xC0,                     # .         Logical Minimum (-64),
+    0x25, 0x3F,                     # .         Logical Maximum (63),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x02,                     # .         Report Count (2),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x41,                     # .         Usage (41h),
+    0x65, 0x14,                     # .         Unit (Degrees),
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x36, 0x4C, 0xFF,               # .         Physical Minimum (-180),
+    0x46, 0xB3, 0x00,               # .         Physical Maximum (179),
+    0x16, 0x7C, 0xFC,               # .         Logical Minimum (-900),
+    0x26, 0x83, 0x03,               # .         Logical Maximum (899),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x0A,                     # .         Input (Variable, Wrap),
+    0x0A, 0x03, 0x0D,               # .         Usage (0D03h),
+    0x65, 0x00,                     # .         Unit,
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0xFF, 0x07,               # .         Logical Maximum (2047),         # !!! Errata: Missing Physical Min/Max = 0
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x0A, 0x32, 0x01,               # .         Usage (0132h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x3F,                     # .         Logical Maximum (63),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x5B,                     # .         Usage (5Bh),
+    0x09, 0x5C,                     # .         Usage (5Ch),
+    0x17, 0x00, 0x00, 0x00, 0x80,   # .         Logical Minimum (-2147483648),
+    0x27, 0xFF, 0xFF, 0xFF, 0x7F,   # .         Logical Maximum (2147483647),
+    0x75, 0x20,                     # .         Report Size (32),
+    0x95, 0x02,                     # .         Report Count (2),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x77,                     # .         Usage (77h),
+    0x16, 0x00, 0x00,               # .         Logical Minimum (0),
+    0x26, 0xFF, 0x0F,               # .         Logical Maximum (4095),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0xC0,                           # .     End Collection,
+    0x85, 0x11,                     # .     Report ID (17),
+    0x09, 0x39,                     # .     Usage (39h),
+    0xA1, 0x00,                     # .     Collection (Physical),
+    0x1A, 0x10, 0x09,               # .         Usage Minimum (0910h),
+    0x2A, 0x17, 0x09,               # .         Usage Maximum (0917h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x01,                     # .         Report Size (1),
+    0x95, 0x08,                     # .         Report Count (8),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x1A, 0x40, 0x09,               # .         Usage Minimum (0940h),
+    0x2A, 0x47, 0x09,               # .         Usage Maximum (0947h),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x0A, 0x95, 0x09,               # .         Usage (0995h),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x95, 0x07,                     # .         Report Count (7),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0x0A, 0x38, 0x01,               # .         Usage (0138h),
+    0x65, 0x14,                     # .         Unit (Degrees),
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x35, 0x00,                     # .         Physical Minimum (0),
+    0x46, 0x67, 0x01,               # .         Physical Maximum (359),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x47,                     # .         Logical Maximum (71),
+    0x75, 0x07,                     # .         Report Size (7),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x0A,                     # .         Input (Variable, Wrap),
+    0x0A, 0x39, 0x01,               # .         Usage (0139h),
+    0x65, 0x00,                     # .         Unit,
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),            # !!! Errata: Missing Physical Max = 0
+    0x75, 0x01,                     # .         Report Size (1),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x04,                     # .         Report Count (4),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0xC0,                           # .     End Collection,
+    0x85, 0x13,                     # .     Report ID (19),
+    0x0A, 0x13, 0x10,               # .     Usage (1013h),
+    0xA1, 0x00,                     # .     Collection (Physical),
+    0x0A, 0x3B, 0x04,               # .         Usage (043Bh),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x64,                     # .         Logical Maximum (100),
+    0x75, 0x07,                     # .         Report Size (7),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x0A, 0x04, 0x04,               # .         Usage (0404h),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x01,                     # .         Report Size (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x0A, 0x52, 0x04,               # .         Usage (0452h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x01,                     # .         Report Size (1),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x95, 0x06,                     # .         Report Count (6),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0x0A, 0x54, 0x04,               # .         Usage (0454h),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x06,                     # .         Report Count (6),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0xC0,                           # .     End Collection,
+    0x09, 0x0E,                     # .     Usage (0Eh),
+    0xA1, 0x02,                     # .     Collection (Logical),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x85, 0x02,                     # .         Report ID (2),
+    0x09, 0x01,                     # .         Usage (01h),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x03,                     # .         Report ID (3),
+    0x0A, 0x03, 0x10,               # .         Usage (1003h),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x04,                     # .         Report ID (4),
+    0x0A, 0x04, 0x10,               # .         Usage (1004h),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x07,                     # .         Report ID (7),
+    0x0A, 0x09, 0x10,               # .         Usage (1009h),
+    0x25, 0x02,                     # .         Logical Maximum (2),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x03,                     # .         Feature (Constant, Variable),
+    0x0A, 0x07, 0x10,               # .         Usage (1007h),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x0A, 0x08, 0x10,               # .         Usage (1008h),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x27, 0xFF, 0xFF, 0x00, 0x00,   # .         Logical Maximum (65535),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x06,                     # .         Report Count (6),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x03,                     # .         Feature (Constant, Variable),
+    0x85, 0x0C,                     # .         Report ID (12),
+    0x0A, 0x30, 0x0D,               # .         Usage (0D30h),
+    0x0A, 0x31, 0x0D,               # .         Usage (0D31h),
+    0x0A, 0x32, 0x0D,               # .         Usage (0D32h),
+    0x0A, 0x33, 0x0D,               # .         Usage (0D33h),                  # !!! Errata: Missing Non-zero Physical Max
+    0x65, 0x11,                     # .         Unit (Centimeter),
+    0x55, 0x0D,                     # .         Unit Exponent (13),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x03,                     # .         Feature (Constant, Variable),
+    0x85, 0x0D,                     # .         Report ID (13),
+    0x65, 0x00,                     # .         Unit,
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x0A, 0x0D, 0x10,               # .         Usage (100Dh),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x14,                     # .         Report ID (20),
+    0x0A, 0x14, 0x10,               # .         Usage (1014h),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x95, 0x0D,                     # .         Report Count (13),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x31,                     # .         Report ID (49),
+    0x0A, 0x31, 0x10,               # .         Usage (1031h),
+    0x25, 0x64,                     # .         Logical Maximum (100),
+    0x95, 0x05,                     # .         Report Count (5),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x32,                     # .         Report ID (50),
+    0x0A, 0x31, 0x10,               # .         Usage (1031h),
+    0x25, 0x64,                     # .         Logical Maximum (100),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x0A, 0x32, 0x10,               # .         Usage (1032h),
+    0x25, 0x03,                     # .         Logical Maximum (3),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x34,                     # .         Report ID (52),
+    0x0A, 0x34, 0x10,               # .         Usage (1034h),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x35,                     # .         Report ID (53),
+    0x0A, 0x35, 0x10,               # .         Usage (1035h),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x95, 0x0A,                     # .         Report Count (10),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x36,                     # .         Report ID (54),
+    0x0A, 0x35, 0x10,               # .         Usage (1035h),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x96, 0x01, 0x01,               # .         Report Count (257),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0xCC,                     # .         Report ID (204),
+    0x0A, 0xCC, 0x10,               # .         Usage (10CCh),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x95, 0x02,                     # .         Report Count (2),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0xC0,                           # .     End Collection,
+    0x0A, 0xAC, 0x10,               # .     Usage (10ACh),
+    0xA1, 0x02,                     # .     Collection (Logical),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x85, 0xAC,                     # .         Report ID (172),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0xBF,                     # .         Report Count (191),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x85, 0x33,                     # .         Report ID (51),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x12,                     # .         Report Count (18),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x64,                     # .         Report ID (100),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x0C,                     # .         Report Count (12),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x15,                     # .         Report ID (21),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x0E,                     # .         Report Count (14),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x12,                     # .         Report ID (18),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x16,                     # .         Report ID (22),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x0E,                     # .         Report Count (14),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x40,                     # .         Report ID (64),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x41,                     # .         Report ID (65),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x42,                     # .         Report ID (66),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x43,                     # .         Report ID (67),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x0D,                     # .         Report Count (13),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x44,                     # .         Report ID (68),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x3F,                     # .         Report Count (63),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x45,                     # .         Report ID (69),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x20,                     # .         Report Count (32),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x60,                     # .         Report ID (96),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x3F,                     # .         Report Count (63),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x61,                     # .         Report ID (97),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x3E,                     # .         Report Count (62),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x62,                     # .         Report ID (98),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x3E,                     # .         Report Count (62),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0xC0,                           # .     End Collection,
+    0x85, 0xD0,                     # .     Report ID (208),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x08, 0x00,               # .     Report Count (8),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD1,                     # .     Report ID (209),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x01,               # .     Report Count (260),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD2,                     # .     Report ID (210),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x01,               # .     Report Count (260),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD3,                     # .     Report ID (211),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD4,                     # .     Report ID (212),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD5,                     # .     Report ID (213),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD6,                     # .     Report ID (214),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD7,                     # .     Report ID (215),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x08, 0x00,               # .     Report Count (8),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD8,                     # .     Report ID (216),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x0C, 0x00,               # .     Report Count (12),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD9,                     # .     Report ID (217),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x00, 0x0A,               # .     Report Count (2560),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDA,                     # .     Report ID (218),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x04,               # .     Report Count (1028),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDB,                     # .     Report ID (219),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x06, 0x00,               # .     Report Count (6),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDC,                     # .     Report ID (220),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x02, 0x00,               # .     Report Count (2),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDD,                     # .     Report ID (221),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDE,                     # .     Report ID (222),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDF,                     # .     Report ID (223),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x22, 0x00,               # .     Report Count (34),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE0,                     # .     Report ID (224),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x01, 0x00,               # .     Report Count (1),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE1,                     # .     Report ID (225),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x02, 0x00,               # .     Report Count (2),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE2,                     # .     Report ID (226),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x02, 0x00,               # .     Report Count (2),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE3,                     # .     Report ID (227),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x02, 0x00,               # .     Report Count (2),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE4,                     # .     Report ID (228),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0xFF, 0x01,               # .     Report Count (511),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0xC0                            # . End Collection
+]
+# fmt: on
+
+# Report ID (20), Usage (1014h), Report Count (13) -> 15
+wacom_pth860_v150 = wacom_pth860_v145.copy()
+wacom_pth860_v150[0x2CA] = 0x0F
+
+# fmt: off
+wacom_pth460_v105 = [
+    0x06, 0x0D, 0xFF,               # . Usage Page (FF0Dh),
+    0x09, 0x01,                     # . Usage (01h),
+    0xA1, 0x01,                     # . Collection (Application),
+    0x85, 0x10,                     # .     Report ID (16),
+    0x09, 0x20,                     # .     Usage (20h),
+    0x35, 0x00,                     # .     Physical Minimum (0),
+    0x45, 0x00,                     # .     Physical Maximum (0),
+    0x15, 0x00,                     # .     Logical Minimum (0),
+    0x25, 0x01,                     # .     Logical Maximum (1),
+    0xA1, 0x00,                     # .     Collection (Physical),
+    0x09, 0x42,                     # .         Usage (42h),
+    0x09, 0x44,                     # .         Usage (44h),
+    0x09, 0x5A,                     # .         Usage (5Ah),
+    0x09, 0x45,                     # .         Usage (45h),
+    0x09, 0x3C,                     # .         Usage (3Ch),
+    0x09, 0x32,                     # .         Usage (32h),
+    0x09, 0x36,                     # .         Usage (36h),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x01,                     # .         Report Size (1),
+    0x95, 0x07,                     # .         Report Count (7),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0x0A, 0x30, 0x01,               # .         Usage (0130h),
+    0x65, 0x11,                     # .         Unit (Centimeter),
+    0x55, 0x0D,                     # .         Unit Exponent (13),
+    0x47, 0x58, 0x3E, 0x00, 0x00,   # .         Physical Maximum (15960),
+    0x27, 0xB0, 0x7C, 0x00, 0x00,   # .         Logical Maximum (31920),
+    0x75, 0x18,                     # .         Report Size (24),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x0A, 0x31, 0x01,               # .         Usage (0131h),
+    0x47, 0xF7, 0x26, 0x00, 0x00,   # .         Physical Maximum (9975),
+    0x27, 0xEE, 0x4D, 0x00, 0x00,   # .         Logical Maximum (19950),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x30,                     # .         Usage (30h),
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x65, 0x00,                     # .         Unit,
+    0x26, 0xFF, 0x1F,               # .         Logical Maximum (8191),         # !!! Errata: Missing Physical Max = 0
+    0x75, 0x10,                     # .         Report Size (16),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x3D,                     # .         Usage (3Dh),
+    0x09, 0x3E,                     # .         Usage (3Eh),
+    0x65, 0x14,                     # .         Unit (Degrees),
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x35, 0xC0,                     # .         Physical Minimum (-64),
+    0x45, 0x3F,                     # .         Physical Maximum (63),
+    0x15, 0xC0,                     # .         Logical Minimum (-64),
+    0x25, 0x3F,                     # .         Logical Maximum (63),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x02,                     # .         Report Count (2),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x41,                     # .         Usage (41h),
+    0x65, 0x14,                     # .         Unit (Degrees),
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x36, 0x4C, 0xFF,               # .         Physical Minimum (-180),
+    0x46, 0xB3, 0x00,               # .         Physical Maximum (179),
+    0x16, 0x7C, 0xFC,               # .         Logical Minimum (-900),
+    0x26, 0x83, 0x03,               # .         Logical Maximum (899),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x0A,                     # .         Input (Variable, Wrap),
+    0x0A, 0x03, 0x0D,               # .         Usage (0D03h),
+    0x65, 0x00,                     # .         Unit,
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x35, 0x00,                     # .         Physical Minimum (0),
+    0x45, 0x00,                     # .         Physical Maximum (0),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0xFF, 0x07,               # .         Logical Maximum (2047),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x0A, 0x32, 0x01,               # .         Usage (0132h),
+    0x25, 0x3F,                     # .         Logical Maximum (63),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x5B,                     # .         Usage (5Bh),
+    0x09, 0x5C,                     # .         Usage (5Ch),
+    0x17, 0x00, 0x00, 0x00, 0x80,   # .         Logical Minimum (-2147483648),
+    0x27, 0xFF, 0xFF, 0xFF, 0x7F,   # .         Logical Maximum (2147483647),
+    0x75, 0x20,                     # .         Report Size (32),
+    0x95, 0x02,                     # .         Report Count (2),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x09, 0x77,                     # .         Usage (77h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0xFF, 0x0F,               # .         Logical Maximum (4095),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x02,                     # .         Input (Variable),
+    0xC0,                           # .     End Collection,
+    0x85, 0x11,                     # .     Report ID (17),
+    0x65, 0x00,                     # .     Unit,
+    0x55, 0x00,                     # .     Unit Exponent (0),
+    0x35, 0x00,                     # .     Physical Minimum (0),
+    0x45, 0x00,                     # .     Physical Maximum (0),
+    0x09, 0x39,                     # .     Usage (39h),
+    0xA1, 0x00,                     # .     Collection (Physical),
+    0x09, 0x39,                     # .         Usage (39h),
+    0xA1, 0x00,                     # .         Collection (Physical),
+    0x35, 0x00,                     # .             Physical Minimum (0),
+    0x45, 0x00,                     # .             Physical Maximum (0),
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x1A, 0x10, 0x09,               # .             Usage Minimum (0910h),
+    0x2A, 0x15, 0x09,               # .             Usage Maximum (0915h),
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x25, 0x01,                     # .             Logical Maximum (1),
+    0x75, 0x01,                     # .             Report Size (1),
+    0x95, 0x06,                     # .             Report Count (6),
+    0x81, 0x02,                     # .             Input (Variable),
+    0x95, 0x02,                     # .             Report Count (2),
+    0x81, 0x03,                     # .             Input (Constant, Variable),
+    0xC0,                           # .         End Collection,
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x01,                     # .         Report Count (1),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0x09, 0x39,                     # .         Usage (39h),
+    0xA1, 0x00,                     # .         Collection (Physical),
+    0x35, 0x00,                     # .             Physical Minimum (0),
+    0x45, 0x00,                     # .             Physical Maximum (0),
+    0x0A, 0x95, 0x09,               # .             Usage (0995h),
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x25, 0x01,                     # .             Logical Maximum (1),
+    0x75, 0x01,                     # .             Report Size (1),
+    0x95, 0x01,                     # .             Report Count (1),
+    0x81, 0x02,                     # .             Input (Variable),
+    0x95, 0x07,                     # .             Report Count (7),
+    0x81, 0x03,                     # .             Input (Constant, Variable),
+    0xC0,                           # .         End Collection,
+    0x09, 0x39,                     # .         Usage (39h),
+    0xA1, 0x00,                     # .         Collection (Physical),
+    0x35, 0x00,                     # .             Physical Minimum (0),
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x0A, 0x38, 0x01,               # .             Usage (0138h),
+    0x65, 0x14,                     # .             Unit (Degrees),
+    0x55, 0x00,                     # .             Unit Exponent (0),
+    0x35, 0x00,                     # .             Physical Minimum (0),
+    0x46, 0x67, 0x01,               # .             Physical Maximum (359),
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x25, 0x47,                     # .             Logical Maximum (71),
+    0x75, 0x07,                     # .             Report Size (7),
+    0x95, 0x01,                     # .             Report Count (1),
+    0x81, 0x4A,                     # .             Input (Variable, Wrap, Null State),
+    0x0A, 0x39, 0x01,               # .             Usage (0139h),
+    0x65, 0x00,                     # .             Unit,
+    0x55, 0x00,                     # .             Unit Exponent (0),
+    0x45, 0x00,                     # .             Physical Maximum (0),
+    0x25, 0x01,                     # .             Logical Maximum (1),
+    0x75, 0x01,                     # .             Report Size (1),
+    0x95, 0x01,                     # .             Report Count (1),
+    0x81, 0x02,                     # .             Input (Variable),
+    0xC0,                           # .         End Collection,
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x04,                     # .         Report Count (4),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0xC0,                           # .     End Collection,
+    0x85, 0x13,                     # .     Report ID (19),
+    0x65, 0x00,                     # .     Unit,
+    0x55, 0x00,                     # .     Unit Exponent (0),
+    0x35, 0x00,                     # .     Physical Minimum (0),
+    0x45, 0x00,                     # .     Physical Maximum (0),
+    0x0A, 0x13, 0x10,               # .     Usage (1013h),
+    0xA1, 0x00,                     # .     Collection (Physical),
+    0x0A, 0x13, 0x10,               # .         Usage (1013h),
+    0xA1, 0x00,                     # .         Collection (Physical),
+    0x35, 0x00,                     # .             Physical Minimum (0),
+    0x45, 0x00,                     # .             Physical Maximum (0),
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x0A, 0x3B, 0x04,               # .             Usage (043Bh),
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x25, 0x64,                     # .             Logical Maximum (100),
+    0x75, 0x07,                     # .             Report Size (7),
+    0x95, 0x01,                     # .             Report Count (1),
+    0x81, 0x02,                     # .             Input (Variable),
+    0x0A, 0x04, 0x04,               # .             Usage (0404h),
+    0x25, 0x01,                     # .             Logical Maximum (1),
+    0x75, 0x01,                     # .             Report Size (1),
+    0x81, 0x02,                     # .             Input (Variable),
+    0xC0,                           # .         End Collection,
+    0x0A, 0x13, 0x10,               # .         Usage (1013h),
+    0xA1, 0x00,                     # .         Collection (Physical),
+    0x35, 0x00,                     # .             Physical Minimum (0),
+    0x45, 0x00,                     # .             Physical Maximum (0),
+    0x0A, 0x52, 0x04,               # .             Usage (0452h),
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x25, 0x01,                     # .             Logical Maximum (1),
+    0x75, 0x01,                     # .             Report Size (1),
+    0x95, 0x01,                     # .             Report Count (1),
+    0x81, 0x02,                     # .             Input (Variable),
+    0x0A, 0x41, 0x04,               # .             Usage (0441h),
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x25, 0x07,                     # .             Logical Maximum (7),
+    0x75, 0x03,                     # .             Report Size (3),
+    0x95, 0x02,                     # .             Report Count (2),
+    0x81, 0x02,                     # .             Input (Variable),
+    0x0A, 0x54, 0x04,               # .             Usage (0454h),
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x25, 0x01,                     # .             Logical Maximum (1),
+    0x75, 0x01,                     # .             Report Size (1),
+    0x95, 0x01,                     # .             Report Count (1),
+    0x81, 0x02,                     # .             Input (Variable),
+    0xC0,                           # .         End Collection,
+    0x0A, 0x13, 0x10,               # .         Usage (1013h),
+    0xA1, 0x00,                     # .         Collection (Physical),
+    0x35, 0x00,                     # .             Physical Minimum (0),
+    0x45, 0x00,                     # .             Physical Maximum (0),
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x0A, 0x3C, 0x04,               # .             Usage (043Ch),
+    0x55, 0x00,                     # .             Unit Exponent (0),
+    0x65, 0x00,                     # .             Unit,
+    0x15, 0xFB,                     # .             Logical Minimum (-5),
+    0x25, 0x32,                     # .             Logical Maximum (50),
+    0x75, 0x08,                     # .             Report Size (8),
+    0x95, 0x01,                     # .             Report Count (1),
+    0x81, 0x02,                     # .             Input (Variable),
+    0xC0,                           # .         End Collection,
+    0x0A, 0x13, 0x10,               # .         Usage (1013h),
+    0xA1, 0x00,                     # .         Collection (Physical),
+    0x35, 0x00,                     # .             Physical Minimum (0),
+    0x45, 0x00,                     # .             Physical Maximum (0),
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x0A, 0x3D, 0x04,               # .             Usage (043Dh),
+    0x55, 0x00,                     # .             Unit Exponent (0),
+    0x65, 0x00,                     # .             Unit,
+    0x15, 0x00,                     # .             Logical Minimum (0),
+    0x26, 0xFF, 0x0F,               # .             Logical Maximum (4095),
+    0x75, 0x10,                     # .             Report Size (16),
+    0x95, 0x01,                     # .             Report Count (1),
+    0x81, 0x02,                     # .             Input (Variable),
+    0xC0,                           # .         End Collection,
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x03,                     # .         Report Count (3),
+    0x81, 0x03,                     # .         Input (Constant, Variable),
+    0xC0,                           # .     End Collection,
+    0x09, 0x0E,                     # .     Usage (0Eh),
+    0xA1, 0x02,                     # .     Collection (Logical),
+    0x85, 0x02,                     # .         Report ID (2),
+    0x0A, 0x02, 0x10,               # .         Usage (1002h),
+    0x15, 0x02,                     # .         Logical Minimum (2),
+    0x25, 0x02,                     # .         Logical Maximum (2),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x03,                     # .         Report ID (3),
+    0x0A, 0x03, 0x10,               # .         Usage (1003h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x04,                     # .         Report ID (4),
+    0x0A, 0x04, 0x10,               # .         Usage (1004h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x07,                     # .         Report ID (7),
+    0x0A, 0x09, 0x10,               # .         Usage (1009h),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0xB1, 0x03,                     # .         Feature (Constant, Variable),
+    0x0A, 0x07, 0x10,               # .         Usage (1007h),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x0A, 0x08, 0x10,               # .         Usage (1008h),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x27, 0xFF, 0xFF, 0x00, 0x00,   # .         Logical Maximum (65535),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x06,                     # .         Report Count (6),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x25, 0x00,                     # .         Logical Maximum (0),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x03,                     # .         Feature (Constant, Variable),
+    0x85, 0x0C,                     # .         Report ID (12),
+    0x0A, 0x30, 0x0D,               # .         Usage (0D30h),
+    0x0A, 0x31, 0x0D,               # .         Usage (0D31h),
+    0x0A, 0x32, 0x0D,               # .         Usage (0D32h),
+    0x0A, 0x33, 0x0D,               # .         Usage (0D33h),
+    0x65, 0x11,                     # .         Unit (Centimeter),
+    0x55, 0x0D,                     # .         Unit Exponent (13),
+    0x35, 0x00,                     # .         Physical Minimum (0),
+    0x46, 0xC8, 0x00,               # .         Physical Maximum (200),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0x90, 0x01,               # .         Logical Maximum (400),
+    0x75, 0x10,                     # .         Report Size (16),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x0D,                     # .         Report ID (13),
+    0x0A, 0x0D, 0x10,               # .         Usage (100Dh),
+    0x65, 0x00,                     # .         Unit,
+    0x55, 0x00,                     # .         Unit Exponent (0),
+    0x45, 0x00,                     # .         Physical Maximum (0),
+    0x25, 0x01,                     # .         Logical Maximum (1),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x95, 0x01,                     # .         Report Count (1),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x14,                     # .         Report ID (20),
+    0x0A, 0x14, 0x10,               # .         Usage (1014h),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x95, 0x0D,                     # .         Report Count (13),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0xCC,                     # .         Report ID (204),
+    0x0A, 0xCC, 0x10,               # .         Usage (10CCh),
+    0x95, 0x02,                     # .         Report Count (2),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0xC0,                           # .     End Collection,
+    0x09, 0x0E,                     # .     Usage (0Eh),
+    0xA1, 0x02,                     # .     Collection (Logical),
+    0x85, 0x31,                     # .         Report ID (49),
+    0x0A, 0x31, 0x10,               # .         Usage (1031h),
+    0x25, 0x64,                     # .         Logical Maximum (100),
+    0x95, 0x03,                     # .         Report Count (3),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x95, 0x02,                     # .         Report Count (2),
+    0xB1, 0x03,                     # .         Feature (Constant, Variable),
+    0xC0,                           # .     End Collection,
+    0x0A, 0xAC, 0x10,               # .     Usage (10ACh),
+    0xA1, 0x02,                     # .     Collection (Logical),
+    0x15, 0x00,                     # .         Logical Minimum (0),
+    0x26, 0xFF, 0x00,               # .         Logical Maximum (255),
+    0x75, 0x08,                     # .         Report Size (8),
+    0x85, 0xAC,                     # .         Report ID (172),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x96, 0xBF, 0x00,               # .         Report Count (191),
+    0x81, 0x02,                     # .         Input (Variable),
+    0x85, 0x15,                     # .         Report ID (21),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x0E,                     # .         Report Count (14),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x33,                     # .         Report ID (51),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x12,                     # .         Report Count (18),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x44,                     # .         Report ID (68),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x45,                     # .         Report ID (69),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x20,                     # .         Report Count (32),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x60,                     # .         Report ID (96),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x3F,                     # .         Report Count (63),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x61,                     # .         Report ID (97),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x3E,                     # .         Report Count (62),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x62,                     # .         Report ID (98),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x3E,                     # .         Report Count (62),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x65,                     # .         Report ID (101),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x66,                     # .         Report ID (102),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x67,                     # .         Report ID (103),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x04,                     # .         Report Count (4),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x68,                     # .         Report ID (104),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x11,                     # .         Report Count (17),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x6F,                     # .         Report ID (111),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x3E,                     # .         Report Count (62),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0xCD,                     # .         Report ID (205),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x02,                     # .         Report Count (2),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x16,                     # .         Report ID (22),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x0E,                     # .         Report Count (14),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0x85, 0x35,                     # .         Report ID (53),
+    0x09, 0x00,                     # .         Usage (00h),
+    0x95, 0x0A,                     # .         Report Count (10),
+    0xB1, 0x02,                     # .         Feature (Variable),
+    0xC0,                           # .     End Collection,
+    0x85, 0xD1,                     # .     Report ID (209),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x01,               # .     Report Count (260),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD2,                     # .     Report ID (210),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x01,               # .     Report Count (260),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD3,                     # .     Report ID (211),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD4,                     # .     Report ID (212),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD5,                     # .     Report ID (213),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD6,                     # .     Report ID (214),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD7,                     # .     Report ID (215),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x08, 0x00,               # .     Report Count (8),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD8,                     # .     Report ID (216),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x0C, 0x00,               # .     Report Count (12),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xD9,                     # .     Report ID (217),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x00, 0x0A,               # .     Report Count (2560),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDA,                     # .     Report ID (218),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x04,               # .     Report Count (1028),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDB,                     # .     Report ID (219),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x06, 0x00,               # .     Report Count (6),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDC,                     # .     Report ID (220),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x02, 0x00,               # .     Report Count (2),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDD,                     # .     Report ID (221),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDE,                     # .     Report ID (222),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x04, 0x00,               # .     Report Count (4),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xDF,                     # .     Report ID (223),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x22, 0x00,               # .     Report Count (34),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE0,                     # .     Report ID (224),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x01, 0x00,               # .     Report Count (1),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE1,                     # .     Report ID (225),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x02, 0x00,               # .     Report Count (2),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE2,                     # .     Report ID (226),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x02, 0x00,               # .     Report Count (2),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE3,                     # .     Report ID (227),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x02, 0x00,               # .     Report Count (2),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xE4,                     # .     Report ID (228),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0xFF, 0x01,               # .     Report Count (511),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0x85, 0xCB,                     # .     Report ID (203),
+    0x09, 0x01,                     # .     Usage (01h),
+    0x96, 0x1F, 0x00,               # .     Report Count (31),
+    0xB1, 0x02,                     # .     Feature (Variable),
+    0xC0                            # . End Collection
+]
+# fmt: on
diff --git a/tools/testing/selftests/hid/tests/test_apple_keyboard.py b/tools/testing/selftests/hid/tests/test_apple_keyboard.py
new file mode 100644 (file)
index 0000000..f81071d
--- /dev/null
@@ -0,0 +1,440 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2019 Red Hat, Inc.
+#
+
+from .test_keyboard import ArrayKeyboard, TestArrayKeyboard
+from hidtools.util import BusType
+
+import libevdev
+import logging
+
+logger = logging.getLogger("hidtools.test.apple-keyboard")
+
+KERNEL_MODULE = ("apple", "hid-apple")
+
+
+class KbdData(object):
+    pass
+
+
+class AppleKeyboard(ArrayKeyboard):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,         # Usage Page (Generic Desktop)
+        0x09, 0x06,         # Usage (Keyboard)
+        0xa1, 0x01,         # Collection (Application)
+        0x85, 0x01,         # .Report ID (1)
+        0x05, 0x07,         # .Usage Page (Keyboard)
+        0x19, 0xe0,         # .Usage Minimum (224)
+        0x29, 0xe7,         # .Usage Maximum (231)
+        0x15, 0x00,         # .Logical Minimum (0)
+        0x25, 0x01,         # .Logical Maximum (1)
+        0x75, 0x01,         # .Report Size (1)
+        0x95, 0x08,         # .Report Count (8)
+        0x81, 0x02,         # .Input (Data,Var,Abs)
+        0x75, 0x08,         # .Report Size (8)
+        0x95, 0x01,         # .Report Count (1)
+        0x81, 0x01,         # .Input (Cnst,Arr,Abs)
+        0x75, 0x01,         # .Report Size (1)
+        0x95, 0x05,         # .Report Count (5)
+        0x05, 0x08,         # .Usage Page (LEDs)
+        0x19, 0x01,         # .Usage Minimum (1)
+        0x29, 0x05,         # .Usage Maximum (5)
+        0x91, 0x02,         # .Output (Data,Var,Abs)
+        0x75, 0x03,         # .Report Size (3)
+        0x95, 0x01,         # .Report Count (1)
+        0x91, 0x01,         # .Output (Cnst,Arr,Abs)
+        0x75, 0x08,         # .Report Size (8)
+        0x95, 0x06,         # .Report Count (6)
+        0x15, 0x00,         # .Logical Minimum (0)
+        0x26, 0xff, 0x00,   # .Logical Maximum (255)
+        0x05, 0x07,         # .Usage Page (Keyboard)
+        0x19, 0x00,         # .Usage Minimum (0)
+        0x2a, 0xff, 0x00,   # .Usage Maximum (255)
+        0x81, 0x00,         # .Input (Data,Arr,Abs)
+        0xc0,               # End Collection
+        0x05, 0x0c,         # Usage Page (Consumer Devices)
+        0x09, 0x01,         # Usage (Consumer Control)
+        0xa1, 0x01,         # Collection (Application)
+        0x85, 0x47,         # .Report ID (71)
+        0x05, 0x01,         # .Usage Page (Generic Desktop)
+        0x09, 0x06,         # .Usage (Keyboard)
+        0xa1, 0x02,         # .Collection (Logical)
+        0x05, 0x06,         # ..Usage Page (Generic Device Controls)
+        0x09, 0x20,         # ..Usage (Battery Strength)
+        0x15, 0x00,         # ..Logical Minimum (0)
+        0x26, 0xff, 0x00,   # ..Logical Maximum (255)
+        0x75, 0x08,         # ..Report Size (8)
+        0x95, 0x01,         # ..Report Count (1)
+        0x81, 0x02,         # ..Input (Data,Var,Abs)
+        0xc0,               # .End Collection
+        0xc0,               # End Collection
+        0x05, 0x0c,         # Usage Page (Consumer Devices)
+        0x09, 0x01,         # Usage (Consumer Control)
+        0xa1, 0x01,         # Collection (Application)
+        0x85, 0x11,         # .Report ID (17)
+        0x15, 0x00,         # .Logical Minimum (0)
+        0x25, 0x01,         # .Logical Maximum (1)
+        0x75, 0x01,         # .Report Size (1)
+        0x95, 0x03,         # .Report Count (3)
+        0x81, 0x01,         # .Input (Cnst,Arr,Abs)
+        0x75, 0x01,         # .Report Size (1)
+        0x95, 0x01,         # .Report Count (1)
+        0x05, 0x0c,         # .Usage Page (Consumer Devices)
+        0x09, 0xb8,         # .Usage (Eject)
+        0x81, 0x02,         # .Input (Data,Var,Abs)
+        0x06, 0xff, 0x00,   # .Usage Page (Vendor Usage Page 0xff)
+        0x09, 0x03,         # .Usage (Vendor Usage 0x03)
+        0x81, 0x02,         # .Input (Data,Var,Abs)
+        0x75, 0x01,         # .Report Size (1)
+        0x95, 0x03,         # .Report Count (3)
+        0x81, 0x01,         # .Input (Cnst,Arr,Abs)
+        0x05, 0x0c,         # .Usage Page (Consumer Devices)
+        0x85, 0x12,         # .Report ID (18)
+        0x15, 0x00,         # .Logical Minimum (0)
+        0x25, 0x01,         # .Logical Maximum (1)
+        0x75, 0x01,         # .Report Size (1)
+        0x95, 0x01,         # .Report Count (1)
+        0x09, 0xcd,         # .Usage (Play/Pause)
+        0x81, 0x02,         # .Input (Data,Var,Abs)
+        0x09, 0xb3,         # .Usage (Fast Forward)
+        0x81, 0x02,         # .Input (Data,Var,Abs)
+        0x09, 0xb4,         # .Usage (Rewind)
+        0x81, 0x02,         # .Input (Data,Var,Abs)
+        0x09, 0xb5,         # .Usage (Scan Next Track)
+        0x81, 0x02,         # .Input (Data,Var,Abs)
+        0x09, 0xb6,         # .Usage (Scan Previous Track)
+        0x81, 0x02,         # .Input (Data,Var,Abs)
+        0x81, 0x01,         # .Input (Cnst,Arr,Abs)
+        0x81, 0x01,         # .Input (Cnst,Arr,Abs)
+        0x81, 0x01,         # .Input (Cnst,Arr,Abs)
+        0x85, 0x13,         # .Report ID (19)
+        0x15, 0x00,         # .Logical Minimum (0)
+        0x25, 0x01,         # .Logical Maximum (1)
+        0x75, 0x01,         # .Report Size (1)
+        0x95, 0x01,         # .Report Count (1)
+        0x06, 0x01, 0xff,   # .Usage Page (Vendor Usage Page 0xff01)
+        0x09, 0x0a,         # .Usage (Vendor Usage 0x0a)
+        0x81, 0x02,         # .Input (Data,Var,Abs)
+        0x06, 0x01, 0xff,   # .Usage Page (Vendor Usage Page 0xff01)
+        0x09, 0x0c,         # .Usage (Vendor Usage 0x0c)
+        0x81, 0x22,         # .Input (Data,Var,Abs,NoPref)
+        0x75, 0x01,         # .Report Size (1)
+        0x95, 0x06,         # .Report Count (6)
+        0x81, 0x01,         # .Input (Cnst,Arr,Abs)
+        0x85, 0x09,         # .Report ID (9)
+        0x09, 0x0b,         # .Usage (Vendor Usage 0x0b)
+        0x75, 0x08,         # .Report Size (8)
+        0x95, 0x01,         # .Report Count (1)
+        0xb1, 0x02,         # .Feature (Data,Var,Abs)
+        0x75, 0x08,         # .Report Size (8)
+        0x95, 0x02,         # .Report Count (2)
+        0xb1, 0x01,         # .Feature (Cnst,Arr,Abs)
+        0xc0,               # End Collection
+    ]
+    # fmt: on
+
+    def __init__(
+        self,
+        rdesc=report_descriptor,
+        name="Apple Wireless Keyboard",
+        input_info=(BusType.BLUETOOTH, 0x05AC, 0x0256),
+    ):
+        super().__init__(rdesc, name, input_info)
+        self.default_reportID = 1
+
+    def send_fn_state(self, state):
+        data = KbdData()
+        setattr(data, "0xff0003", state)
+        r = self.create_report(data, reportID=17)
+        self.call_input_event(r)
+        return [r]
+
+
+class TestAppleKeyboard(TestArrayKeyboard):
+    kernel_modules = [KERNEL_MODULE]
+
+    def create_device(self):
+        return AppleKeyboard()
+
+    def test_single_function_key(self):
+        """check for function key reliability."""
+        uhdev = self.uhdev
+        evdev = uhdev.get_evdev()
+        syn_event = self.syn_event
+
+        r = uhdev.event(["F4"])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
+
+        r = uhdev.event([])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 0
+
+    def test_single_fn_function_key(self):
+        """check for function key reliability with the fn key."""
+        uhdev = self.uhdev
+        evdev = uhdev.get_evdev()
+        syn_event = self.syn_event
+
+        r = uhdev.send_fn_state(1)
+        r.extend(uhdev.event(["F4"]))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 1
+
+        r = uhdev.event([])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+        r = uhdev.send_fn_state(0)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+
+    def test_single_fn_function_key_release_first(self):
+        """check for function key reliability with the fn key."""
+        uhdev = self.uhdev
+        evdev = uhdev.get_evdev()
+        syn_event = self.syn_event
+
+        r = uhdev.send_fn_state(1)
+        r.extend(uhdev.event(["F4"]))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 1
+
+        r = uhdev.send_fn_state(0)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+
+        r = uhdev.event([])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+
+    def test_single_fn_function_key_inverted(self):
+        """check for function key reliability with the fn key."""
+        uhdev = self.uhdev
+        evdev = uhdev.get_evdev()
+        syn_event = self.syn_event
+
+        r = uhdev.event(["F4"])
+        r.extend(uhdev.send_fn_state(1))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
+
+        r = uhdev.event([])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+        r = uhdev.send_fn_state(0)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+
+    def test_multiple_fn_function_key_release_first(self):
+        """check for function key reliability with the fn key."""
+        uhdev = self.uhdev
+        evdev = uhdev.get_evdev()
+        syn_event = self.syn_event
+
+        r = uhdev.send_fn_state(1)
+        r.extend(uhdev.event(["F4"]))
+        r.extend(uhdev.event(["F4", "F6"]))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F6, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+        r = uhdev.event(["F6"])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+        r = uhdev.send_fn_state(0)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
+
+        r = uhdev.event([])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F6, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_F6] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
+
+    def test_multiple_fn_function_key_release_between(self):
+        """check for function key reliability with the fn key."""
+        uhdev = self.uhdev
+        evdev = uhdev.get_evdev()
+        syn_event = self.syn_event
+
+        # press F4
+        r = uhdev.event(["F4"])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_F6] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
+
+        # press Fn key
+        r = uhdev.send_fn_state(1)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_F6] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+        # keep F4 and press F6
+        r = uhdev.event(["F4", "F6"])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F6, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+        # keep F4 and F6
+        r = uhdev.event(["F4", "F6"])
+        expected = []
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+        # release Fn key and all keys
+        r = uhdev.send_fn_state(0)
+        r.extend(uhdev.event([]))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F6, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_F6] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
+
+    def test_single_pageup_key_release_first(self):
+        """check for function key reliability with the [page] up key."""
+        uhdev = self.uhdev
+        evdev = uhdev.get_evdev()
+        syn_event = self.syn_event
+
+        r = uhdev.send_fn_state(1)
+        r.extend(uhdev.event(["UpArrow"]))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_PAGEUP, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_PAGEUP] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_UP] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+        r = uhdev.send_fn_state(0)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_PAGEUP] == 1
+        assert evdev.value[libevdev.EV_KEY.KEY_UP] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
+
+        r = uhdev.event([])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_PAGEUP, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+        assert evdev.value[libevdev.EV_KEY.KEY_PAGEUP] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_UP] == 0
+        assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
diff --git a/tools/testing/selftests/hid/tests/test_gamepad.py b/tools/testing/selftests/hid/tests/test_gamepad.py
new file mode 100644 (file)
index 0000000..26c7404
--- /dev/null
@@ -0,0 +1,209 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2019 Red Hat, Inc.
+#
+
+from . import base
+import libevdev
+import pytest
+
+from hidtools.device.base_gamepad import AsusGamepad, SaitekGamepad
+
+import logging
+
+logger = logging.getLogger("hidtools.test.gamepad")
+
+
+class BaseTest:
+    class TestGamepad(base.BaseTestCase.TestUhid):
+        @pytest.fixture(autouse=True)
+        def send_initial_state(self):
+            """send an empty report to initialize the axes"""
+            uhdev = self.uhdev
+
+            r = uhdev.event()
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+
+        def assert_button(self, button):
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+            syn_event = self.syn_event
+
+            buttons = {}
+            key = libevdev.evbit(uhdev.buttons_map[button])
+
+            buttons[button] = True
+            r = uhdev.event(buttons=buttons)
+            expected_event = libevdev.InputEvent(key, 1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[key] == 1
+
+            buttons[button] = False
+            r = uhdev.event(buttons=buttons)
+            expected_event = libevdev.InputEvent(key, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[key] == 0
+
+        def test_buttons(self):
+            """check for button reliability."""
+            uhdev = self.uhdev
+
+            for b in uhdev.buttons:
+                self.assert_button(b)
+
+        def test_dual_buttons(self):
+            """check for button reliability when pressing 2 buttons"""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+            syn_event = self.syn_event
+
+            # can change intended b1 b2 values
+            b1 = uhdev.buttons[0]
+            key1 = libevdev.evbit(uhdev.buttons_map[b1])
+            b2 = uhdev.buttons[1]
+            key2 = libevdev.evbit(uhdev.buttons_map[b2])
+
+            buttons = {b1: True, b2: True}
+            r = uhdev.event(buttons=buttons)
+            expected_event0 = libevdev.InputEvent(key1, 1)
+            expected_event1 = libevdev.InputEvent(key2, 1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(
+                (syn_event, expected_event0, expected_event1), events
+            )
+            assert evdev.value[key1] == 1
+            assert evdev.value[key2] == 1
+
+            buttons = {b1: False, b2: None}
+            r = uhdev.event(buttons=buttons)
+            expected_event = libevdev.InputEvent(key1, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[key1] == 0
+            assert evdev.value[key2] == 1
+
+            buttons = {b1: None, b2: False}
+            r = uhdev.event(buttons=buttons)
+            expected_event = libevdev.InputEvent(key2, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[key1] == 0
+            assert evdev.value[key2] == 0
+
+        def _get_libevdev_abs_events(self, which):
+            """Returns which ABS_* evdev axes are expected for the given stick"""
+            abs_map = self.uhdev.axes_map[which]
+
+            x = abs_map["x"].evdev
+            y = abs_map["y"].evdev
+
+            assert x
+            assert y
+
+            return x, y
+
+        def _test_joystick_press(self, which, data):
+            uhdev = self.uhdev
+
+            libevdev_axes = self._get_libevdev_abs_events(which)
+
+            r = None
+            if which == "left_stick":
+                r = uhdev.event(left=data)
+            else:
+                r = uhdev.event(right=data)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+
+            for i, d in enumerate(data):
+                if d is not None and d != 127:
+                    assert libevdev.InputEvent(libevdev_axes[i], d) in events
+                else:
+                    assert libevdev.InputEvent(libevdev_axes[i]) not in events
+
+        def test_left_joystick_press_left(self):
+            """check for the left joystick reliability"""
+            self._test_joystick_press("left_stick", (63, None))
+            self._test_joystick_press("left_stick", (0, 127))
+
+        def test_left_joystick_press_right(self):
+            """check for the left joystick reliability"""
+            self._test_joystick_press("left_stick", (191, 127))
+            self._test_joystick_press("left_stick", (255, None))
+
+        def test_left_joystick_press_up(self):
+            """check for the left joystick reliability"""
+            self._test_joystick_press("left_stick", (None, 63))
+            self._test_joystick_press("left_stick", (127, 0))
+
+        def test_left_joystick_press_down(self):
+            """check for the left joystick reliability"""
+            self._test_joystick_press("left_stick", (127, 191))
+            self._test_joystick_press("left_stick", (None, 255))
+
+        def test_right_joystick_press_left(self):
+            """check for the right joystick reliability"""
+            self._test_joystick_press("right_stick", (63, None))
+            self._test_joystick_press("right_stick", (0, 127))
+
+        def test_right_joystick_press_right(self):
+            """check for the right joystick reliability"""
+            self._test_joystick_press("right_stick", (191, 127))
+            self._test_joystick_press("right_stick", (255, None))
+
+        def test_right_joystick_press_up(self):
+            """check for the right joystick reliability"""
+            self._test_joystick_press("right_stick", (None, 63))
+            self._test_joystick_press("right_stick", (127, 0))
+
+        def test_right_joystick_press_down(self):
+            """check for the right joystick reliability"""
+            self._test_joystick_press("right_stick", (127, 191))
+            self._test_joystick_press("right_stick", (None, 255))
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: "Hat switch" not in uhdev.fields,
+            "Device not compatible, missing Hat switch usage",
+        )
+        @pytest.mark.parametrize(
+            "hat_value,expected_evdev,evdev_value",
+            [
+                (0, "ABS_HAT0Y", -1),
+                (2, "ABS_HAT0X", 1),
+                (4, "ABS_HAT0Y", 1),
+                (6, "ABS_HAT0X", -1),
+            ],
+        )
+        def test_hat_switch(self, hat_value, expected_evdev, evdev_value):
+            uhdev = self.uhdev
+
+            r = uhdev.event(hat_switch=hat_value)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert (
+                libevdev.InputEvent(
+                    libevdev.evbit("EV_ABS", expected_evdev), evdev_value
+                )
+                in events
+            )
+
+
+class TestSaitekGamepad(BaseTest.TestGamepad):
+    def create_device(self):
+        return SaitekGamepad()
+
+
+class TestAsusGamepad(BaseTest.TestGamepad):
+    def create_device(self):
+        return AsusGamepad()
diff --git a/tools/testing/selftests/hid/tests/test_hid_core.py b/tools/testing/selftests/hid/tests/test_hid_core.py
new file mode 100644 (file)
index 0000000..9a7fe40
--- /dev/null
@@ -0,0 +1,154 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# This is for generic devices
+
+from . import base
+import logging
+
+logger = logging.getLogger("hidtools.test.hid")
+
+
+class TestCollectionOverflow(base.BaseTestCase.TestUhid):
+    """
+    Test class to test re-allocation of the HID collection stack in
+    hid-core.c.
+    """
+
+    def create_device(self):
+        # fmt: off
+        report_descriptor = [
+            0x05, 0x01,         # .Usage Page (Generic Desktop)
+            0x09, 0x02,         # .Usage (Mouse)
+            0xa1, 0x01,         # .Collection (Application)
+            0x09, 0x02,         # ..Usage (Mouse)
+            0xa1, 0x02,         # ..Collection (Logical)
+            0x09, 0x01,         # ...Usage (Pointer)
+            0xa1, 0x00,         # ...Collection (Physical)
+            0x05, 0x09,         # ....Usage Page (Button)
+            0x19, 0x01,         # ....Usage Minimum (1)
+            0x29, 0x03,         # ....Usage Maximum (3)
+            0x15, 0x00,         # ....Logical Minimum (0)
+            0x25, 0x01,         # ....Logical Maximum (1)
+            0x75, 0x01,         # ....Report Size (1)
+            0x95, 0x03,         # ....Report Count (3)
+            0x81, 0x02,         # ....Input (Data,Var,Abs)
+            0x75, 0x05,         # ....Report Size (5)
+            0x95, 0x01,         # ....Report Count (1)
+            0x81, 0x03,         # ....Input (Cnst,Var,Abs)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0x05, 0x01,         # .....Usage Page (Generic Desktop)
+            0x09, 0x30,         # .....Usage (X)
+            0x09, 0x31,         # .....Usage (Y)
+            0x15, 0x81,         # .....Logical Minimum (-127)
+            0x25, 0x7f,         # .....Logical Maximum (127)
+            0x75, 0x08,         # .....Report Size (8)
+            0x95, 0x02,         # .....Report Count (2)
+            0x81, 0x06,         # .....Input (Data,Var,Rel)
+            0xa1, 0x02,         # ...Collection (Logical)
+            0x85, 0x12,         # ....Report ID (18)
+            0x09, 0x48,         # ....Usage (Resolution Multiplier)
+            0x95, 0x01,         # ....Report Count (1)
+            0x75, 0x02,         # ....Report Size (2)
+            0x15, 0x00,         # ....Logical Minimum (0)
+            0x25, 0x01,         # ....Logical Maximum (1)
+            0x35, 0x01,         # ....Physical Minimum (1)
+            0x45, 0x0c,         # ....Physical Maximum (12)
+            0xb1, 0x02,         # ....Feature (Data,Var,Abs)
+            0x85, 0x1a,         # ....Report ID (26)
+            0x09, 0x38,         # ....Usage (Wheel)
+            0x35, 0x00,         # ....Physical Minimum (0)
+            0x45, 0x00,         # ....Physical Maximum (0)
+            0x95, 0x01,         # ....Report Count (1)
+            0x75, 0x10,         # ....Report Size (16)
+            0x16, 0x01, 0x80,   # ....Logical Minimum (-32767)
+            0x26, 0xff, 0x7f,   # ....Logical Maximum (32767)
+            0x81, 0x06,         # ....Input (Data,Var,Rel)
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ..End Collection
+            0xc0,               # .End Collection
+        ]
+        # fmt: on
+        return base.UHIDTestDevice(
+            name=None, rdesc=report_descriptor, application="Mouse"
+        )
+
+    def test_rdesc(self):
+        """
+        This test can only check for negatives. If the kernel crashes, you
+        know why. If this test passes, either the bug isn't present or just
+        didn't get triggered. No way to know.
+
+        For an explanation, see kernel patch
+            HID: core: replace the collection tree pointers with indices
+        """
+        pass
diff --git a/tools/testing/selftests/hid/tests/test_ite_keyboard.py b/tools/testing/selftests/hid/tests/test_ite_keyboard.py
new file mode 100644 (file)
index 0000000..38550c1
--- /dev/null
@@ -0,0 +1,166 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2020 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2020 Red Hat, Inc.
+#
+
+from .test_keyboard import ArrayKeyboard, TestArrayKeyboard
+from hidtools.util import BusType
+
+import libevdev
+import logging
+
+logger = logging.getLogger("hidtools.test.ite-keyboard")
+
+KERNEL_MODULE = ("itetech", "hid_ite")
+
+
+class KbdData(object):
+    pass
+
+
+# The ITE keyboards have an issue regarding the Wifi key:
+# nothing comes in when pressing the key, but we get a null
+# event on the key release.
+# This test covers this case.
+class ITEKeyboard(ArrayKeyboard):
+    # fmt: off
+    report_descriptor = [
+        0x06, 0x85, 0xff,              # Usage Page (Vendor Usage Page 0xff85)
+        0x09, 0x95,                    # Usage (Vendor Usage 0x95)           3
+        0xa1, 0x01,                    # Collection (Application)            5
+        0x85, 0x5a,                    # .Report ID (90)                     7
+        0x09, 0x01,                    # .Usage (Vendor Usage 0x01)          9
+        0x15, 0x00,                    # .Logical Minimum (0)                11
+        0x26, 0xff, 0x00,              # .Logical Maximum (255)              13
+        0x75, 0x08,                    # .Report Size (8)                    16
+        0x95, 0x10,                    # .Report Count (16)                  18
+        0xb1, 0x00,                    # .Feature (Data,Arr,Abs)             20
+        0xc0,                          # End Collection                      22
+        0x05, 0x01,                    # Usage Page (Generic Desktop)        23
+        0x09, 0x06,                    # Usage (Keyboard)                    25
+        0xa1, 0x01,                    # Collection (Application)            27
+        0x85, 0x01,                    # .Report ID (1)                      29
+        0x75, 0x01,                    # .Report Size (1)                    31
+        0x95, 0x08,                    # .Report Count (8)                   33
+        0x05, 0x07,                    # .Usage Page (Keyboard)              35
+        0x19, 0xe0,                    # .Usage Minimum (224)                37
+        0x29, 0xe7,                    # .Usage Maximum (231)                39
+        0x15, 0x00,                    # .Logical Minimum (0)                41
+        0x25, 0x01,                    # .Logical Maximum (1)                43
+        0x81, 0x02,                    # .Input (Data,Var,Abs)               45
+        0x95, 0x01,                    # .Report Count (1)                   47
+        0x75, 0x08,                    # .Report Size (8)                    49
+        0x81, 0x03,                    # .Input (Cnst,Var,Abs)               51
+        0x95, 0x05,                    # .Report Count (5)                   53
+        0x75, 0x01,                    # .Report Size (1)                    55
+        0x05, 0x08,                    # .Usage Page (LEDs)                  57
+        0x19, 0x01,                    # .Usage Minimum (1)                  59
+        0x29, 0x05,                    # .Usage Maximum (5)                  61
+        0x91, 0x02,                    # .Output (Data,Var,Abs)              63
+        0x95, 0x01,                    # .Report Count (1)                   65
+        0x75, 0x03,                    # .Report Size (3)                    67
+        0x91, 0x03,                    # .Output (Cnst,Var,Abs)              69
+        0x95, 0x06,                    # .Report Count (6)                   71
+        0x75, 0x08,                    # .Report Size (8)                    73
+        0x15, 0x00,                    # .Logical Minimum (0)                75
+        0x26, 0xff, 0x00,              # .Logical Maximum (255)              77
+        0x05, 0x07,                    # .Usage Page (Keyboard)              80
+        0x19, 0x00,                    # .Usage Minimum (0)                  82
+        0x2a, 0xff, 0x00,              # .Usage Maximum (255)                84
+        0x81, 0x00,                    # .Input (Data,Arr,Abs)               87
+        0xc0,                          # End Collection                      89
+        0x05, 0x0c,                    # Usage Page (Consumer Devices)       90
+        0x09, 0x01,                    # Usage (Consumer Control)            92
+        0xa1, 0x01,                    # Collection (Application)            94
+        0x85, 0x02,                    # .Report ID (2)                      96
+        0x19, 0x00,                    # .Usage Minimum (0)                  98
+        0x2a, 0x3c, 0x02,              # .Usage Maximum (572)                100
+        0x15, 0x00,                    # .Logical Minimum (0)                103
+        0x26, 0x3c, 0x02,              # .Logical Maximum (572)              105
+        0x75, 0x10,                    # .Report Size (16)                   108
+        0x95, 0x01,                    # .Report Count (1)                   110
+        0x81, 0x00,                    # .Input (Data,Arr,Abs)               112
+        0xc0,                          # End Collection                      114
+        0x05, 0x01,                    # Usage Page (Generic Desktop)        115
+        0x09, 0x0c,                    # Usage (Wireless Radio Controls)     117
+        0xa1, 0x01,                    # Collection (Application)            119
+        0x85, 0x03,                    # .Report ID (3)                      121
+        0x15, 0x00,                    # .Logical Minimum (0)                123
+        0x25, 0x01,                    # .Logical Maximum (1)                125
+        0x09, 0xc6,                    # .Usage (Wireless Radio Button)      127
+        0x95, 0x01,                    # .Report Count (1)                   129
+        0x75, 0x01,                    # .Report Size (1)                    131
+        0x81, 0x06,                    # .Input (Data,Var,Rel)               133
+        0x75, 0x07,                    # .Report Size (7)                    135
+        0x81, 0x03,                    # .Input (Cnst,Var,Abs)               137
+        0xc0,                          # End Collection                      139
+        0x05, 0x88,                    # Usage Page (Vendor Usage Page 0x88) 140
+        0x09, 0x01,                    # Usage (Vendor Usage 0x01)           142
+        0xa1, 0x01,                    # Collection (Application)            144
+        0x85, 0x04,                    # .Report ID (4)                      146
+        0x19, 0x00,                    # .Usage Minimum (0)                  148
+        0x2a, 0xff, 0xff,              # .Usage Maximum (65535)              150
+        0x15, 0x00,                    # .Logical Minimum (0)                153
+        0x26, 0xff, 0xff,              # .Logical Maximum (65535)            155
+        0x75, 0x08,                    # .Report Size (8)                    158
+        0x95, 0x02,                    # .Report Count (2)                   160
+        0x81, 0x02,                    # .Input (Data,Var,Abs)               162
+        0xc0,                          # End Collection                      164
+        0x05, 0x01,                    # Usage Page (Generic Desktop)        165
+        0x09, 0x80,                    # Usage (System Control)              167
+        0xa1, 0x01,                    # Collection (Application)            169
+        0x85, 0x05,                    # .Report ID (5)                      171
+        0x19, 0x81,                    # .Usage Minimum (129)                173
+        0x29, 0x83,                    # .Usage Maximum (131)                175
+        0x15, 0x00,                    # .Logical Minimum (0)                177
+        0x25, 0x01,                    # .Logical Maximum (1)                179
+        0x95, 0x08,                    # .Report Count (8)                   181
+        0x75, 0x01,                    # .Report Size (1)                    183
+        0x81, 0x02,                    # .Input (Data,Var,Abs)               185
+        0xc0,                          # End Collection                      187
+    ]
+    # fmt: on
+
+    def __init__(
+        self,
+        rdesc=report_descriptor,
+        name=None,
+        input_info=(BusType.USB, 0x06CB, 0x2968),
+    ):
+        super().__init__(rdesc, name, input_info)
+
+    def event(self, keys, reportID=None, application=None):
+        application = application or "Keyboard"
+        return super().event(keys, reportID, application)
+
+
+class TestITEKeyboard(TestArrayKeyboard):
+    kernel_modules = [KERNEL_MODULE]
+
+    def create_device(self):
+        return ITEKeyboard()
+
+    def test_wifi_key(self):
+        uhdev = self.uhdev
+        syn_event = self.syn_event
+
+        # the following sends a 'release' event on the Wifi key.
+        # the kernel is supposed to translate this into Wifi key
+        # down and up
+        r = [0x03, 0x00]
+        uhdev.call_input_event(r)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_RFKILL, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports([r], uhdev, events)
+        self.assertInputEventsIn(expected, events)
+
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_RFKILL, 0))
+        # the kernel sends the two down/up key events in a batch, no need to
+        # call events = uhdev.next_sync_events()
+        self.debug_reports([], uhdev, events)
+        self.assertInputEventsIn(expected, events)
diff --git a/tools/testing/selftests/hid/tests/test_keyboard.py b/tools/testing/selftests/hid/tests/test_keyboard.py
new file mode 100644 (file)
index 0000000..b3b2bdb
--- /dev/null
@@ -0,0 +1,485 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2018 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2018 Red Hat, Inc.
+#
+
+from . import base
+import hidtools.hid
+import libevdev
+import logging
+
+logger = logging.getLogger("hidtools.test.keyboard")
+
+
+class InvalidHIDCommunication(Exception):
+    pass
+
+
+class KeyboardData(object):
+    pass
+
+
+class BaseKeyboard(base.UHIDTestDevice):
+    def __init__(self, rdesc, name=None, input_info=None):
+        assert rdesc is not None
+        super().__init__(name, "Key", input_info=input_info, rdesc=rdesc)
+        self.keystates = {}
+
+    def _update_key_state(self, keys):
+        """
+        Update the internal state of keys with the new state given.
+
+        :param key: a tuple of chars for the currently pressed keys.
+        """
+        # First remove the already released keys
+        unused_keys = [k for k, v in self.keystates.items() if not v]
+        for key in unused_keys:
+            del self.keystates[key]
+
+        # self.keystates contains now the list of currently pressed keys,
+        # release them...
+        for key in self.keystates.keys():
+            self.keystates[key] = False
+
+        # ...and press those that are in parameter
+        for key in keys:
+            self.keystates[key] = True
+
+    def _create_report_data(self):
+        keyboard = KeyboardData()
+        for key, value in self.keystates.items():
+            key = key.replace(" ", "").lower()
+            setattr(keyboard, key, value)
+        return keyboard
+
+    def create_array_report(self, keys, reportID=None, application=None):
+        """
+        Return an input report for this device.
+
+        :param keys: a tuple of chars for the pressed keys. The class maintains
+            the list of currently pressed keys, so to release a key, the caller
+            needs to call again this function without the key in this tuple.
+        :param reportID: the numeric report ID for this report, if needed
+        """
+        self._update_key_state(keys)
+        reportID = reportID or self.default_reportID
+
+        keyboard = self._create_report_data()
+        return self.create_report(keyboard, reportID=reportID, application=application)
+
+    def event(self, keys, reportID=None, application=None):
+        """
+        Send an input event on the default report ID.
+
+        :param keys: a tuple of chars for the pressed keys. The class maintains
+            the list of currently pressed keys, so to release a key, the caller
+            needs to call again this function without the key in this tuple.
+        """
+        r = self.create_array_report(keys, reportID, application)
+        self.call_input_event(r)
+        return [r]
+
+
+class PlainKeyboard(BaseKeyboard):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,                    # Usage Page (Generic Desktop)
+        0x09, 0x06,                    # Usage (Keyboard)
+        0xa1, 0x01,                    # Collection (Application)
+        0x85, 0x01,                    # .Report ID (1)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x19, 0xe0,                    # .Usage Minimum (224)
+        0x29, 0xe7,                    # .Usage Maximum (231)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x25, 0x01,                    # .Logical Maximum (1)
+        0x75, 0x01,                    # .Report Size (1)
+        0x95, 0x08,                    # .Report Count (8)
+        0x81, 0x02,                    # .Input (Data,Var,Abs)
+        0x19, 0x00,                    # .Usage Minimum (0)
+        0x29, 0x97,                    # .Usage Maximum (151)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x25, 0x01,                    # .Logical Maximum (1)
+        0x75, 0x01,                    # .Report Size (1)
+        0x95, 0x98,                    # .Report Count (152)
+        0x81, 0x02,                    # .Input (Data,Var,Abs)
+        0xc0,                          # End Collection
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+        self.default_reportID = 1
+
+
+class ArrayKeyboard(BaseKeyboard):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,                    # Usage Page (Generic Desktop)
+        0x09, 0x06,                    # Usage (Keyboard)
+        0xa1, 0x01,                    # Collection (Application)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x19, 0xe0,                    # .Usage Minimum (224)
+        0x29, 0xe7,                    # .Usage Maximum (231)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x25, 0x01,                    # .Logical Maximum (1)
+        0x75, 0x01,                    # .Report Size (1)
+        0x95, 0x08,                    # .Report Count (8)
+        0x81, 0x02,                    # .Input (Data,Var,Abs)
+        0x95, 0x06,                    # .Report Count (6)
+        0x75, 0x08,                    # .Report Size (8)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x26, 0xa4, 0x00,              # .Logical Maximum (164)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x19, 0x00,                    # .Usage Minimum (0)
+        0x29, 0xa4,                    # .Usage Maximum (164)
+        0x81, 0x00,                    # .Input (Data,Arr,Abs)
+        0xc0,                          # End Collection
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+
+    def _create_report_data(self):
+        data = KeyboardData()
+        array = []
+
+        hut = hidtools.hut.HUT
+
+        # strip modifiers from the array
+        for k, v in self.keystates.items():
+            # we ignore depressed keys
+            if not v:
+                continue
+
+            usage = hut[0x07].from_name[k].usage
+            if usage >= 224 and usage <= 231:
+                # modifier
+                setattr(data, k.lower(), 1)
+            else:
+                array.append(k)
+
+        # if array length is bigger than 6, report ErrorRollOver
+        if len(array) > 6:
+            array = ["ErrorRollOver"] * 6
+
+        data.keyboard = array
+        return data
+
+
+class LEDKeyboard(ArrayKeyboard):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,                    # Usage Page (Generic Desktop)
+        0x09, 0x06,                    # Usage (Keyboard)
+        0xa1, 0x01,                    # Collection (Application)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x19, 0xe0,                    # .Usage Minimum (224)
+        0x29, 0xe7,                    # .Usage Maximum (231)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x25, 0x01,                    # .Logical Maximum (1)
+        0x75, 0x01,                    # .Report Size (1)
+        0x95, 0x08,                    # .Report Count (8)
+        0x81, 0x02,                    # .Input (Data,Var,Abs)
+        0x95, 0x01,                    # .Report Count (1)
+        0x75, 0x08,                    # .Report Size (8)
+        0x81, 0x01,                    # .Input (Cnst,Arr,Abs)
+        0x95, 0x05,                    # .Report Count (5)
+        0x75, 0x01,                    # .Report Size (1)
+        0x05, 0x08,                    # .Usage Page (LEDs)
+        0x19, 0x01,                    # .Usage Minimum (1)
+        0x29, 0x05,                    # .Usage Maximum (5)
+        0x91, 0x02,                    # .Output (Data,Var,Abs)
+        0x95, 0x01,                    # .Report Count (1)
+        0x75, 0x03,                    # .Report Size (3)
+        0x91, 0x01,                    # .Output (Cnst,Arr,Abs)
+        0x95, 0x06,                    # .Report Count (6)
+        0x75, 0x08,                    # .Report Size (8)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x26, 0xa4, 0x00,              # .Logical Maximum (164)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x19, 0x00,                    # .Usage Minimum (0)
+        0x29, 0xa4,                    # .Usage Maximum (164)
+        0x81, 0x00,                    # .Input (Data,Arr,Abs)
+        0xc0,                          # End Collection
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+
+
+# Some Primax manufactured keyboards set the Usage Page after having defined
+# some local Usages. It relies on the fact that the specification states that
+# Usages are to be concatenated with Usage Pages upon finding a Main item (see
+# 6.2.2.8). This test covers this case.
+class PrimaxKeyboard(ArrayKeyboard):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,                    # Usage Page (Generic Desktop)
+        0x09, 0x06,                    # Usage (Keyboard)
+        0xA1, 0x01,                    # Collection (Application)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x19, 0xE0,                    # .Usage Minimum (224)
+        0x29, 0xE7,                    # .Usage Maximum (231)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x25, 0x01,                    # .Logical Maximum (1)
+        0x75, 0x01,                    # .Report Size (1)
+        0x95, 0x08,                    # .Report Count (8)
+        0x81, 0x02,                    # .Input (Data,Var,Abs)
+        0x75, 0x08,                    # .Report Size (8)
+        0x95, 0x01,                    # .Report Count (1)
+        0x81, 0x01,                    # .Input (Data,Var,Abs)
+        0x05, 0x08,                    # .Usage Page (LEDs)
+        0x19, 0x01,                    # .Usage Minimum (1)
+        0x29, 0x03,                    # .Usage Maximum (3)
+        0x75, 0x01,                    # .Report Size (1)
+        0x95, 0x03,                    # .Report Count (3)
+        0x91, 0x02,                    # .Output (Data,Var,Abs)
+        0x95, 0x01,                    # .Report Count (1)
+        0x75, 0x05,                    # .Report Size (5)
+        0x91, 0x01,                    # .Output (Constant)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x26, 0xFF, 0x00,              # .Logical Maximum (255)
+        0x19, 0x00,                    # .Usage Minimum (0)
+        0x2A, 0xFF, 0x00,              # .Usage Maximum (255)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x75, 0x08,                    # .Report Size (8)
+        0x95, 0x06,                    # .Report Count (6)
+        0x81, 0x00,                    # .Input (Data,Arr,Abs)
+        0xC0,                          # End Collection
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+
+
+class BaseTest:
+    class TestKeyboard(base.BaseTestCase.TestUhid):
+        def test_single_key(self):
+            """check for key reliability."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+            syn_event = self.syn_event
+
+            r = uhdev.event(["a and A"])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 1))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_A] == 1
+
+            r = uhdev.event([])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 0))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_A] == 0
+
+        def test_two_keys(self):
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+            syn_event = self.syn_event
+
+            r = uhdev.event(["a and A", "q and Q"])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 1))
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_Q, 1))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_A] == 1
+
+            r = uhdev.event([])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 0))
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_Q, 0))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_A] == 0
+            assert evdev.value[libevdev.EV_KEY.KEY_Q] == 0
+
+            r = uhdev.event(["c and C"])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_C, 1))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_C] == 1
+
+            r = uhdev.event(["c and C", "Spacebar"])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE, 1))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.KEY_C) not in events
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_C] == 1
+            assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1
+
+            r = uhdev.event(["Spacebar"])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_C, 0))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE) not in events
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_C] == 0
+            assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1
+
+            r = uhdev.event([])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE, 0))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 0
+
+        def test_modifiers(self):
+            # ctrl-alt-del would be very nice :)
+            uhdev = self.uhdev
+            syn_event = self.syn_event
+
+            r = uhdev.event(["LeftControl", "LeftShift", "= and +"])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_LEFTCTRL, 1))
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_LEFTSHIFT, 1))
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_EQUAL, 1))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+
+
+class TestPlainKeyboard(BaseTest.TestKeyboard):
+    def create_device(self):
+        return PlainKeyboard()
+
+    def test_10_keys(self):
+        uhdev = self.uhdev
+        syn_event = self.syn_event
+
+        r = uhdev.event(
+            [
+                "1 and !",
+                "2 and @",
+                "3 and #",
+                "4 and $",
+                "5 and %",
+                "6 and ^",
+                "7 and &",
+                "8 and *",
+                "9 and (",
+                "0 and )",
+            ]
+        )
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_0, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_7, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_8, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_9, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+
+        r = uhdev.event([])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_0, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_7, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_8, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_9, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+
+
+class TestArrayKeyboard(BaseTest.TestKeyboard):
+    def create_device(self):
+        return ArrayKeyboard()
+
+    def test_10_keys(self):
+        uhdev = self.uhdev
+        syn_event = self.syn_event
+
+        r = uhdev.event(
+            [
+                "1 and !",
+                "2 and @",
+                "3 and #",
+                "4 and $",
+                "5 and %",
+                "6 and ^",
+            ]
+        )
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 1))
+        events = uhdev.next_sync_events()
+
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+
+        # ErrRollOver
+        r = uhdev.event(
+            [
+                "1 and !",
+                "2 and @",
+                "3 and #",
+                "4 and $",
+                "5 and %",
+                "6 and ^",
+                "7 and &",
+                "8 and *",
+                "9 and (",
+                "0 and )",
+            ]
+        )
+        events = uhdev.next_sync_events()
+
+        self.debug_reports(r, uhdev, events)
+
+        assert len(events) == 0
+
+        r = uhdev.event([])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+
+
+class TestLEDKeyboard(BaseTest.TestKeyboard):
+    def create_device(self):
+        return LEDKeyboard()
+
+
+class TestPrimaxKeyboard(BaseTest.TestKeyboard):
+    def create_device(self):
+        return PrimaxKeyboard()
diff --git a/tools/testing/selftests/hid/tests/test_mouse.py b/tools/testing/selftests/hid/tests/test_mouse.py
new file mode 100644 (file)
index 0000000..fd2ba62
--- /dev/null
@@ -0,0 +1,977 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+#
+
+from . import base
+import hidtools.hid
+from hidtools.util import BusType
+import libevdev
+import logging
+import pytest
+
+logger = logging.getLogger("hidtools.test.mouse")
+
+# workaround https://gitlab.freedesktop.org/libevdev/python-libevdev/issues/6
+try:
+    libevdev.EV_REL.REL_WHEEL_HI_RES
+except AttributeError:
+    libevdev.EV_REL.REL_WHEEL_HI_RES = libevdev.EV_REL.REL_0B
+    libevdev.EV_REL.REL_HWHEEL_HI_RES = libevdev.EV_REL.REL_0C
+
+
+class InvalidHIDCommunication(Exception):
+    pass
+
+
+class MouseData(object):
+    pass
+
+
+class BaseMouse(base.UHIDTestDevice):
+    def __init__(self, rdesc, name=None, input_info=None):
+        assert rdesc is not None
+        super().__init__(name, "Mouse", input_info=input_info, rdesc=rdesc)
+        self.left = False
+        self.right = False
+        self.middle = False
+
+    def create_report(self, x, y, buttons=None, wheels=None, reportID=None):
+        """
+        Return an input report for this device.
+
+        :param x: relative x
+        :param y: relative y
+        :param buttons: a (l, r, m) tuple of bools for the button states,
+            where ``None`` is "leave unchanged"
+        :param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for
+            the two wheels
+        :param reportID: the numeric report ID for this report, if needed
+        """
+        if buttons is not None:
+            l, r, m = buttons
+            if l is not None:
+                self.left = l
+            if r is not None:
+                self.right = r
+            if m is not None:
+                self.middle = m
+        left = self.left
+        right = self.right
+        middle = self.middle
+        # Note: the BaseMouse doesn't actually have a wheel but the
+        # create_report magic only fills in those fields exist, so let's
+        # make this generic here.
+        wheel, acpan = 0, 0
+        if wheels is not None:
+            if isinstance(wheels, tuple):
+                wheel = wheels[0]
+                acpan = wheels[1]
+            else:
+                wheel = wheels
+
+        reportID = reportID or self.default_reportID
+
+        mouse = MouseData()
+        mouse.b1 = int(left)
+        mouse.b2 = int(right)
+        mouse.b3 = int(middle)
+        mouse.x = x
+        mouse.y = y
+        mouse.wheel = wheel
+        mouse.acpan = acpan
+        return super().create_report(mouse, reportID=reportID)
+
+    def event(self, x, y, buttons=None, wheels=None):
+        """
+        Send an input event on the default report ID.
+
+        :param x: relative x
+        :param y: relative y
+        :param buttons: a (l, r, m) tuple of bools for the button states,
+            where ``None`` is "leave unchanged"
+        :param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for
+            the two wheels
+        """
+        r = self.create_report(x, y, buttons, wheels)
+        self.call_input_event(r)
+        return [r]
+
+
+class ButtonMouse(BaseMouse):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,  # .Usage Page (Generic Desktop)        0
+        0x09, 0x02,  # .Usage (Mouse)                       2
+        0xa1, 0x01,  # .Collection (Application)            4
+        0x09, 0x02,  # ..Usage (Mouse)                      6
+        0xa1, 0x02,  # ..Collection (Logical)               8
+        0x09, 0x01,  # ...Usage (Pointer)                   10
+        0xa1, 0x00,  # ...Collection (Physical)             12
+        0x05, 0x09,  # ....Usage Page (Button)              14
+        0x19, 0x01,  # ....Usage Minimum (1)                16
+        0x29, 0x03,  # ....Usage Maximum (3)                18
+        0x15, 0x00,  # ....Logical Minimum (0)              20
+        0x25, 0x01,  # ....Logical Maximum (1)              22
+        0x75, 0x01,  # ....Report Size (1)                  24
+        0x95, 0x03,  # ....Report Count (3)                 26
+        0x81, 0x02,  # ....Input (Data,Var,Abs)             28
+        0x75, 0x05,  # ....Report Size (5)                  30
+        0x95, 0x01,  # ....Report Count (1)                 32
+        0x81, 0x03,  # ....Input (Cnst,Var,Abs)             34
+        0x05, 0x01,  # ....Usage Page (Generic Desktop)     36
+        0x09, 0x30,  # ....Usage (X)                        38
+        0x09, 0x31,  # ....Usage (Y)                        40
+        0x15, 0x81,  # ....Logical Minimum (-127)           42
+        0x25, 0x7f,  # ....Logical Maximum (127)            44
+        0x75, 0x08,  # ....Report Size (8)                  46
+        0x95, 0x02,  # ....Report Count (2)                 48
+        0x81, 0x06,  # ....Input (Data,Var,Rel)             50
+        0xc0,        # ...End Collection                    52
+        0xc0,        # ..End Collection                     53
+        0xc0,        # .End Collection                      54
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+
+    def fake_report(self, x, y, buttons):
+        if buttons is not None:
+            left, right, middle = buttons
+            if left is None:
+                left = self.left
+            if right is None:
+                right = self.right
+            if middle is None:
+                middle = self.middle
+        else:
+            left = self.left
+            right = self.right
+            middle = self.middle
+
+        button_mask = sum(1 << i for i, b in enumerate([left, right, middle]) if b)
+        x = max(-127, min(127, x))
+        y = max(-127, min(127, y))
+        x = hidtools.util.to_twos_comp(x, 8)
+        y = hidtools.util.to_twos_comp(y, 8)
+        return [button_mask, x, y]
+
+
+class WheelMouse(ButtonMouse):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,  # Usage Page (Generic Desktop)        0
+        0x09, 0x02,  # Usage (Mouse)                       2
+        0xa1, 0x01,  # Collection (Application)            4
+        0x05, 0x09,  # .Usage Page (Button)                6
+        0x19, 0x01,  # .Usage Minimum (1)                  8
+        0x29, 0x03,  # .Usage Maximum (3)                  10
+        0x15, 0x00,  # .Logical Minimum (0)                12
+        0x25, 0x01,  # .Logical Maximum (1)                14
+        0x95, 0x03,  # .Report Count (3)                   16
+        0x75, 0x01,  # .Report Size (1)                    18
+        0x81, 0x02,  # .Input (Data,Var,Abs)               20
+        0x95, 0x01,  # .Report Count (1)                   22
+        0x75, 0x05,  # .Report Size (5)                    24
+        0x81, 0x03,  # .Input (Cnst,Var,Abs)               26
+        0x05, 0x01,  # .Usage Page (Generic Desktop)       28
+        0x09, 0x01,  # .Usage (Pointer)                    30
+        0xa1, 0x00,  # .Collection (Physical)              32
+        0x09, 0x30,  # ..Usage (X)                         34
+        0x09, 0x31,  # ..Usage (Y)                         36
+        0x15, 0x81,  # ..Logical Minimum (-127)            38
+        0x25, 0x7f,  # ..Logical Maximum (127)             40
+        0x75, 0x08,  # ..Report Size (8)                   42
+        0x95, 0x02,  # ..Report Count (2)                  44
+        0x81, 0x06,  # ..Input (Data,Var,Rel)              46
+        0xc0,        # .End Collection                     48
+        0x09, 0x38,  # .Usage (Wheel)                      49
+        0x15, 0x81,  # .Logical Minimum (-127)             51
+        0x25, 0x7f,  # .Logical Maximum (127)              53
+        0x75, 0x08,  # .Report Size (8)                    55
+        0x95, 0x01,  # .Report Count (1)                   57
+        0x81, 0x06,  # .Input (Data,Var,Rel)               59
+        0xc0,        # End Collection                      61
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+        self.wheel_multiplier = 1
+
+
+class TwoWheelMouse(WheelMouse):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,        # Usage Page (Generic Desktop)        0
+        0x09, 0x02,        # Usage (Mouse)                       2
+        0xa1, 0x01,        # Collection (Application)            4
+        0x09, 0x01,        # .Usage (Pointer)                    6
+        0xa1, 0x00,        # .Collection (Physical)              8
+        0x05, 0x09,        # ..Usage Page (Button)               10
+        0x19, 0x01,        # ..Usage Minimum (1)                 12
+        0x29, 0x10,        # ..Usage Maximum (16)                14
+        0x15, 0x00,        # ..Logical Minimum (0)               16
+        0x25, 0x01,        # ..Logical Maximum (1)               18
+        0x95, 0x10,        # ..Report Count (16)                 20
+        0x75, 0x01,        # ..Report Size (1)                   22
+        0x81, 0x02,        # ..Input (Data,Var,Abs)              24
+        0x05, 0x01,        # ..Usage Page (Generic Desktop)      26
+        0x16, 0x01, 0x80,  # ..Logical Minimum (-32767)          28
+        0x26, 0xff, 0x7f,  # ..Logical Maximum (32767)           31
+        0x75, 0x10,        # ..Report Size (16)                  34
+        0x95, 0x02,        # ..Report Count (2)                  36
+        0x09, 0x30,        # ..Usage (X)                         38
+        0x09, 0x31,        # ..Usage (Y)                         40
+        0x81, 0x06,        # ..Input (Data,Var,Rel)              42
+        0x15, 0x81,        # ..Logical Minimum (-127)            44
+        0x25, 0x7f,        # ..Logical Maximum (127)             46
+        0x75, 0x08,        # ..Report Size (8)                   48
+        0x95, 0x01,        # ..Report Count (1)                  50
+        0x09, 0x38,        # ..Usage (Wheel)                     52
+        0x81, 0x06,        # ..Input (Data,Var,Rel)              54
+        0x05, 0x0c,        # ..Usage Page (Consumer Devices)     56
+        0x0a, 0x38, 0x02,  # ..Usage (AC Pan)                    58
+        0x95, 0x01,        # ..Report Count (1)                  61
+        0x81, 0x06,        # ..Input (Data,Var,Rel)              63
+        0xc0,              # .End Collection                     65
+        0xc0,              # End Collection                      66
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+        self.hwheel_multiplier = 1
+
+
+class MIDongleMIWirelessMouse(TwoWheelMouse):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,         # Usage Page (Generic Desktop)
+        0x09, 0x02,         # Usage (Mouse)
+        0xa1, 0x01,         # Collection (Application)
+        0x85, 0x01,         # .Report ID (1)
+        0x09, 0x01,         # .Usage (Pointer)
+        0xa1, 0x00,         # .Collection (Physical)
+        0x95, 0x05,         # ..Report Count (5)
+        0x75, 0x01,         # ..Report Size (1)
+        0x05, 0x09,         # ..Usage Page (Button)
+        0x19, 0x01,         # ..Usage Minimum (1)
+        0x29, 0x05,         # ..Usage Maximum (5)
+        0x15, 0x00,         # ..Logical Minimum (0)
+        0x25, 0x01,         # ..Logical Maximum (1)
+        0x81, 0x02,         # ..Input (Data,Var,Abs)
+        0x95, 0x01,         # ..Report Count (1)
+        0x75, 0x03,         # ..Report Size (3)
+        0x81, 0x01,         # ..Input (Cnst,Arr,Abs)
+        0x75, 0x08,         # ..Report Size (8)
+        0x95, 0x01,         # ..Report Count (1)
+        0x05, 0x01,         # ..Usage Page (Generic Desktop)
+        0x09, 0x38,         # ..Usage (Wheel)
+        0x15, 0x81,         # ..Logical Minimum (-127)
+        0x25, 0x7f,         # ..Logical Maximum (127)
+        0x81, 0x06,         # ..Input (Data,Var,Rel)
+        0x05, 0x0c,         # ..Usage Page (Consumer Devices)
+        0x0a, 0x38, 0x02,   # ..Usage (AC Pan)
+        0x95, 0x01,         # ..Report Count (1)
+        0x81, 0x06,         # ..Input (Data,Var,Rel)
+        0xc0,               # .End Collection
+        0x85, 0x02,         # .Report ID (2)
+        0x09, 0x01,         # .Usage (Consumer Control)
+        0xa1, 0x00,         # .Collection (Physical)
+        0x75, 0x0c,         # ..Report Size (12)
+        0x95, 0x02,         # ..Report Count (2)
+        0x05, 0x01,         # ..Usage Page (Generic Desktop)
+        0x09, 0x30,         # ..Usage (X)
+        0x09, 0x31,         # ..Usage (Y)
+        0x16, 0x01, 0xf8,   # ..Logical Minimum (-2047)
+        0x26, 0xff, 0x07,   # ..Logical Maximum (2047)
+        0x81, 0x06,         # ..Input (Data,Var,Rel)
+        0xc0,               # .End Collection
+        0xc0,               # End Collection
+        0x05, 0x0c,         # Usage Page (Consumer Devices)
+        0x09, 0x01,         # Usage (Consumer Control)
+        0xa1, 0x01,         # Collection (Application)
+        0x85, 0x03,         # .Report ID (3)
+        0x15, 0x00,         # .Logical Minimum (0)
+        0x25, 0x01,         # .Logical Maximum (1)
+        0x75, 0x01,         # .Report Size (1)
+        0x95, 0x01,         # .Report Count (1)
+        0x09, 0xcd,         # .Usage (Play/Pause)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x0a, 0x83, 0x01,   # .Usage (AL Consumer Control Config)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x09, 0xb5,         # .Usage (Scan Next Track)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x09, 0xb6,         # .Usage (Scan Previous Track)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x09, 0xea,         # .Usage (Volume Down)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x09, 0xe9,         # .Usage (Volume Up)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x0a, 0x25, 0x02,   # .Usage (AC Forward)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x0a, 0x24, 0x02,   # .Usage (AC Back)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0xc0,               # End Collection
+    ]
+    # fmt: on
+    device_input_info = (BusType.USB, 0x2717, 0x003B)
+    device_name = "uhid test MI Dongle MI Wireless Mouse"
+
+    def __init__(
+        self, rdesc=report_descriptor, name=device_name, input_info=device_input_info
+    ):
+        super().__init__(rdesc, name, input_info)
+
+    def event(self, x, y, buttons=None, wheels=None):
+        # this mouse spreads the relative pointer and the mouse buttons
+        # onto 2 distinct reports
+        rs = []
+        r = self.create_report(x, y, buttons, wheels, reportID=1)
+        self.call_input_event(r)
+        rs.append(r)
+        r = self.create_report(x, y, buttons, reportID=2)
+        self.call_input_event(r)
+        rs.append(r)
+        return rs
+
+
+class ResolutionMultiplierMouse(TwoWheelMouse):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,        # Usage Page (Generic Desktop)        83
+        0x09, 0x02,        # Usage (Mouse)                       85
+        0xa1, 0x01,        # Collection (Application)            87
+        0x05, 0x01,        # .Usage Page (Generic Desktop)       89
+        0x09, 0x02,        # .Usage (Mouse)                      91
+        0xa1, 0x02,        # .Collection (Logical)               93
+        0x85, 0x11,        # ..Report ID (17)                    95
+        0x09, 0x01,        # ..Usage (Pointer)                   97
+        0xa1, 0x00,        # ..Collection (Physical)             99
+        0x05, 0x09,        # ...Usage Page (Button)              101
+        0x19, 0x01,        # ...Usage Minimum (1)                103
+        0x29, 0x03,        # ...Usage Maximum (3)                105
+        0x95, 0x03,        # ...Report Count (3)                 107
+        0x75, 0x01,        # ...Report Size (1)                  109
+        0x25, 0x01,        # ...Logical Maximum (1)              111
+        0x81, 0x02,        # ...Input (Data,Var,Abs)             113
+        0x95, 0x01,        # ...Report Count (1)                 115
+        0x81, 0x01,        # ...Input (Cnst,Arr,Abs)             117
+        0x09, 0x05,        # ...Usage (Vendor Usage 0x05)        119
+        0x81, 0x02,        # ...Input (Data,Var,Abs)             121
+        0x95, 0x03,        # ...Report Count (3)                 123
+        0x81, 0x01,        # ...Input (Cnst,Arr,Abs)             125
+        0x05, 0x01,        # ...Usage Page (Generic Desktop)     127
+        0x09, 0x30,        # ...Usage (X)                        129
+        0x09, 0x31,        # ...Usage (Y)                        131
+        0x95, 0x02,        # ...Report Count (2)                 133
+        0x75, 0x08,        # ...Report Size (8)                  135
+        0x15, 0x81,        # ...Logical Minimum (-127)           137
+        0x25, 0x7f,        # ...Logical Maximum (127)            139
+        0x81, 0x06,        # ...Input (Data,Var,Rel)             141
+        0xa1, 0x02,        # ...Collection (Logical)             143
+        0x85, 0x12,        # ....Report ID (18)                  145
+        0x09, 0x48,        # ....Usage (Resolution Multiplier)   147
+        0x95, 0x01,        # ....Report Count (1)                149
+        0x75, 0x02,        # ....Report Size (2)                 151
+        0x15, 0x00,        # ....Logical Minimum (0)             153
+        0x25, 0x01,        # ....Logical Maximum (1)             155
+        0x35, 0x01,        # ....Physical Minimum (1)            157
+        0x45, 0x04,        # ....Physical Maximum (4)            159
+        0xb1, 0x02,        # ....Feature (Data,Var,Abs)          161
+        0x35, 0x00,        # ....Physical Minimum (0)            163
+        0x45, 0x00,        # ....Physical Maximum (0)            165
+        0x75, 0x06,        # ....Report Size (6)                 167
+        0xb1, 0x01,        # ....Feature (Cnst,Arr,Abs)          169
+        0x85, 0x11,        # ....Report ID (17)                  171
+        0x09, 0x38,        # ....Usage (Wheel)                   173
+        0x15, 0x81,        # ....Logical Minimum (-127)          175
+        0x25, 0x7f,        # ....Logical Maximum (127)           177
+        0x75, 0x08,        # ....Report Size (8)                 179
+        0x81, 0x06,        # ....Input (Data,Var,Rel)            181
+        0xc0,              # ...End Collection                   183
+        0x05, 0x0c,        # ...Usage Page (Consumer Devices)    184
+        0x75, 0x08,        # ...Report Size (8)                  186
+        0x0a, 0x38, 0x02,  # ...Usage (AC Pan)                   188
+        0x81, 0x06,        # ...Input (Data,Var,Rel)             191
+        0xc0,              # ..End Collection                    193
+        0xc0,              # .End Collection                     194
+        0xc0,              # End Collection                      195
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+        self.default_reportID = 0x11
+
+        # Feature Report 12, multiplier Feature value must be set to 0b01,
+        # i.e. 1. We should extract that from the descriptor instead
+        # of hardcoding it here, but meanwhile this will do.
+        self.set_feature_report = [0x12, 0x1]
+
+    def set_report(self, req, rnum, rtype, data):
+        if rtype != self.UHID_FEATURE_REPORT:
+            raise InvalidHIDCommunication(f"Unexpected report type: {rtype}")
+        if rnum != 0x12:
+            raise InvalidHIDCommunication(f"Unexpected report number: {rnum}")
+
+        if data != self.set_feature_report:
+            raise InvalidHIDCommunication(
+                f"Unexpected data: {data}, expected {self.set_feature_report}"
+            )
+
+        self.wheel_multiplier = 4
+
+        return 0
+
+
+class BadResolutionMultiplierMouse(ResolutionMultiplierMouse):
+    def set_report(self, req, rnum, rtype, data):
+        super().set_report(req, rnum, rtype, data)
+
+        self.wheel_multiplier = 1
+        self.hwheel_multiplier = 1
+        return 32  # EPIPE
+
+
+class ResolutionMultiplierHWheelMouse(TwoWheelMouse):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,         # Usage Page (Generic Desktop)        0
+        0x09, 0x02,         # Usage (Mouse)                       2
+        0xa1, 0x01,         # Collection (Application)            4
+        0x05, 0x01,         # .Usage Page (Generic Desktop)       6
+        0x09, 0x02,         # .Usage (Mouse)                      8
+        0xa1, 0x02,         # .Collection (Logical)               10
+        0x85, 0x1a,         # ..Report ID (26)                    12
+        0x09, 0x01,         # ..Usage (Pointer)                   14
+        0xa1, 0x00,         # ..Collection (Physical)             16
+        0x05, 0x09,         # ...Usage Page (Button)              18
+        0x19, 0x01,         # ...Usage Minimum (1)                20
+        0x29, 0x05,         # ...Usage Maximum (5)                22
+        0x95, 0x05,         # ...Report Count (5)                 24
+        0x75, 0x01,         # ...Report Size (1)                  26
+        0x15, 0x00,         # ...Logical Minimum (0)              28
+        0x25, 0x01,         # ...Logical Maximum (1)              30
+        0x81, 0x02,         # ...Input (Data,Var,Abs)             32
+        0x75, 0x03,         # ...Report Size (3)                  34
+        0x95, 0x01,         # ...Report Count (1)                 36
+        0x81, 0x01,         # ...Input (Cnst,Arr,Abs)             38
+        0x05, 0x01,         # ...Usage Page (Generic Desktop)     40
+        0x09, 0x30,         # ...Usage (X)                        42
+        0x09, 0x31,         # ...Usage (Y)                        44
+        0x95, 0x02,         # ...Report Count (2)                 46
+        0x75, 0x10,         # ...Report Size (16)                 48
+        0x16, 0x01, 0x80,   # ...Logical Minimum (-32767)         50
+        0x26, 0xff, 0x7f,   # ...Logical Maximum (32767)          53
+        0x81, 0x06,         # ...Input (Data,Var,Rel)             56
+        0xa1, 0x02,         # ...Collection (Logical)             58
+        0x85, 0x12,         # ....Report ID (18)                  60
+        0x09, 0x48,         # ....Usage (Resolution Multiplier)   62
+        0x95, 0x01,         # ....Report Count (1)                64
+        0x75, 0x02,         # ....Report Size (2)                 66
+        0x15, 0x00,         # ....Logical Minimum (0)             68
+        0x25, 0x01,         # ....Logical Maximum (1)             70
+        0x35, 0x01,         # ....Physical Minimum (1)            72
+        0x45, 0x0c,         # ....Physical Maximum (12)           74
+        0xb1, 0x02,         # ....Feature (Data,Var,Abs)          76
+        0x85, 0x1a,         # ....Report ID (26)                  78
+        0x09, 0x38,         # ....Usage (Wheel)                   80
+        0x35, 0x00,         # ....Physical Minimum (0)            82
+        0x45, 0x00,         # ....Physical Maximum (0)            84
+        0x95, 0x01,         # ....Report Count (1)                86
+        0x75, 0x10,         # ....Report Size (16)                88
+        0x16, 0x01, 0x80,   # ....Logical Minimum (-32767)        90
+        0x26, 0xff, 0x7f,   # ....Logical Maximum (32767)         93
+        0x81, 0x06,         # ....Input (Data,Var,Rel)            96
+        0xc0,               # ...End Collection                   98
+        0xa1, 0x02,         # ...Collection (Logical)             99
+        0x85, 0x12,         # ....Report ID (18)                  101
+        0x09, 0x48,         # ....Usage (Resolution Multiplier)   103
+        0x75, 0x02,         # ....Report Size (2)                 105
+        0x15, 0x00,         # ....Logical Minimum (0)             107
+        0x25, 0x01,         # ....Logical Maximum (1)             109
+        0x35, 0x01,         # ....Physical Minimum (1)            111
+        0x45, 0x0c,         # ....Physical Maximum (12)           113
+        0xb1, 0x02,         # ....Feature (Data,Var,Abs)          115
+        0x35, 0x00,         # ....Physical Minimum (0)            117
+        0x45, 0x00,         # ....Physical Maximum (0)            119
+        0x75, 0x04,         # ....Report Size (4)                 121
+        0xb1, 0x01,         # ....Feature (Cnst,Arr,Abs)          123
+        0x85, 0x1a,         # ....Report ID (26)                  125
+        0x05, 0x0c,         # ....Usage Page (Consumer Devices)   127
+        0x95, 0x01,         # ....Report Count (1)                129
+        0x75, 0x10,         # ....Report Size (16)                131
+        0x16, 0x01, 0x80,   # ....Logical Minimum (-32767)        133
+        0x26, 0xff, 0x7f,   # ....Logical Maximum (32767)         136
+        0x0a, 0x38, 0x02,   # ....Usage (AC Pan)                  139
+        0x81, 0x06,         # ....Input (Data,Var,Rel)            142
+        0xc0,               # ...End Collection                   144
+        0xc0,               # ..End Collection                    145
+        0xc0,               # .End Collection                     146
+        0xc0,               # End Collection                      147
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+        self.default_reportID = 0x1A
+
+        # Feature Report 12, multiplier Feature value must be set to 0b0101,
+        # i.e. 5. We should extract that from the descriptor instead
+        # of hardcoding it here, but meanwhile this will do.
+        self.set_feature_report = [0x12, 0x5]
+
+    def set_report(self, req, rnum, rtype, data):
+        super().set_report(req, rnum, rtype, data)
+
+        self.wheel_multiplier = 12
+        self.hwheel_multiplier = 12
+
+        return 0
+
+
+class BaseTest:
+    class TestMouse(base.BaseTestCase.TestUhid):
+        def test_buttons(self):
+            """check for button reliability."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+            syn_event = self.syn_event
+
+            r = uhdev.event(0, 0, (None, True, None))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
+
+            r = uhdev.event(0, 0, (None, False, None))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
+
+            r = uhdev.event(0, 0, (None, None, True))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 1
+
+            r = uhdev.event(0, 0, (None, None, False))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 0
+
+            r = uhdev.event(0, 0, (True, None, None))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
+
+            r = uhdev.event(0, 0, (False, None, None))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+
+            r = uhdev.event(0, 0, (True, True, None))
+            expected_event0 = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1)
+            expected_event1 = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(
+                (syn_event, expected_event0, expected_event1), events
+            )
+            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
+            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
+
+            r = uhdev.event(0, 0, (False, None, None))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
+            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+
+            r = uhdev.event(0, 0, (None, False, None))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
+            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+
+        def test_relative(self):
+            """Check for relative events."""
+            uhdev = self.uhdev
+
+            syn_event = self.syn_event
+
+            r = uhdev.event(0, -1)
+            expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_Y, -1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEvents((syn_event, expected_event), events)
+
+            r = uhdev.event(1, 0)
+            expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_X, 1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEvents((syn_event, expected_event), events)
+
+            r = uhdev.event(-1, 2)
+            expected_event0 = libevdev.InputEvent(libevdev.EV_REL.REL_X, -1)
+            expected_event1 = libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEvents(
+                (syn_event, expected_event0, expected_event1), events
+            )
+
+
+class TestSimpleMouse(BaseTest.TestMouse):
+    def create_device(self):
+        return ButtonMouse()
+
+    def test_rdesc(self):
+        """Check that the testsuite actually manages to format the
+        reports according to the report descriptors.
+        No kernel device is used here"""
+        uhdev = self.uhdev
+
+        event = (0, 0, (None, None, None))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (0, 0, (None, True, None))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (0, 0, (True, True, None))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (0, 0, (False, False, False))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (1, 0, (True, False, True))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (-1, 0, (True, False, True))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (-5, 5, (True, False, True))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (-127, 127, (True, False, True))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (0, -128, (True, False, True))
+        with pytest.raises(hidtools.hid.RangeError):
+            uhdev.create_report(*event)
+
+
+class TestWheelMouse(BaseTest.TestMouse):
+    def create_device(self):
+        return WheelMouse()
+
+    def is_wheel_highres(self, uhdev):
+        evdev = uhdev.get_evdev()
+        assert evdev.has(libevdev.EV_REL.REL_WHEEL)
+        return evdev.has(libevdev.EV_REL.REL_WHEEL_HI_RES)
+
+    def test_wheel(self):
+        uhdev = self.uhdev
+
+        # check if the kernel is high res wheel compatible
+        high_res_wheel = self.is_wheel_highres(uhdev)
+
+        syn_event = self.syn_event
+        # The Resolution Multiplier is applied to the HID reports, so we
+        # need to pre-multiply too.
+        mult = uhdev.wheel_multiplier
+
+        r = uhdev.event(0, 0, wheels=1 * mult)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1))
+        if high_res_wheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(0, 0, wheels=-1 * mult)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -1))
+        if high_res_wheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(-1, 2, wheels=3 * mult)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 3))
+        if high_res_wheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 360))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+
+class TestTwoWheelMouse(TestWheelMouse):
+    def create_device(self):
+        return TwoWheelMouse()
+
+    def is_hwheel_highres(self, uhdev):
+        evdev = uhdev.get_evdev()
+        assert evdev.has(libevdev.EV_REL.REL_HWHEEL)
+        return evdev.has(libevdev.EV_REL.REL_HWHEEL_HI_RES)
+
+    def test_ac_pan(self):
+        uhdev = self.uhdev
+
+        # check if the kernel is high res wheel compatible
+        high_res_wheel = self.is_wheel_highres(uhdev)
+        high_res_hwheel = self.is_hwheel_highres(uhdev)
+        assert high_res_wheel == high_res_hwheel
+
+        syn_event = self.syn_event
+        # The Resolution Multiplier is applied to the HID reports, so we
+        # need to pre-multiply too.
+        hmult = uhdev.hwheel_multiplier
+        vmult = uhdev.wheel_multiplier
+
+        r = uhdev.event(0, 0, wheels=(0, 1 * hmult))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1))
+        if high_res_hwheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(0, 0, wheels=(0, -1 * hmult))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, -1))
+        if high_res_hwheel:
+            expected.append(
+                libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120)
+            )
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(-1, 2, wheels=(0, 3 * hmult))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 3))
+        if high_res_hwheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 360))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(-1, 2, wheels=(-3 * vmult, 4 * hmult))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -3))
+        if high_res_wheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -360))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 4))
+        if high_res_wheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 480))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+
+class TestResolutionMultiplierMouse(TestTwoWheelMouse):
+    def create_device(self):
+        return ResolutionMultiplierMouse()
+
+    def is_wheel_highres(self, uhdev):
+        high_res = super().is_wheel_highres(uhdev)
+
+        if not high_res:
+            # the kernel doesn't seem to support the high res wheel mice,
+            # make sure we haven't triggered the feature
+            assert uhdev.wheel_multiplier == 1
+
+        return high_res
+
+    def test_resolution_multiplier_wheel(self):
+        uhdev = self.uhdev
+
+        if not self.is_wheel_highres(uhdev):
+            pytest.skip("Kernel not compatible, we can not trigger the conditions")
+
+        assert uhdev.wheel_multiplier > 1
+        assert 120 % uhdev.wheel_multiplier == 0
+
+    def test_wheel_with_multiplier(self):
+        uhdev = self.uhdev
+
+        if not self.is_wheel_highres(uhdev):
+            pytest.skip("Kernel not compatible, we can not trigger the conditions")
+
+        assert uhdev.wheel_multiplier > 1
+
+        syn_event = self.syn_event
+        mult = uhdev.wheel_multiplier
+
+        r = uhdev.event(0, 0, wheels=1)
+        expected = [syn_event]
+        expected.append(
+            libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult)
+        )
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(0, 0, wheels=-1)
+        expected = [syn_event]
+        expected.append(
+            libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120 / mult)
+        )
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2))
+        expected.append(
+            libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult)
+        )
+
+        for _ in range(mult - 1):
+            r = uhdev.event(1, -2, wheels=1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEvents(expected, events)
+
+        r = uhdev.event(1, -2, wheels=1)
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+
+class TestBadResolutionMultiplierMouse(TestTwoWheelMouse):
+    def create_device(self):
+        return BadResolutionMultiplierMouse()
+
+    def is_wheel_highres(self, uhdev):
+        high_res = super().is_wheel_highres(uhdev)
+
+        assert uhdev.wheel_multiplier == 1
+
+        return high_res
+
+    def test_resolution_multiplier_wheel(self):
+        uhdev = self.uhdev
+
+        assert uhdev.wheel_multiplier == 1
+
+
+class TestResolutionMultiplierHWheelMouse(TestResolutionMultiplierMouse):
+    def create_device(self):
+        return ResolutionMultiplierHWheelMouse()
+
+    def is_hwheel_highres(self, uhdev):
+        high_res = super().is_hwheel_highres(uhdev)
+
+        if not high_res:
+            # the kernel doesn't seem to support the high res wheel mice,
+            # make sure we haven't triggered the feature
+            assert uhdev.hwheel_multiplier == 1
+
+        return high_res
+
+    def test_resolution_multiplier_ac_pan(self):
+        uhdev = self.uhdev
+
+        if not self.is_hwheel_highres(uhdev):
+            pytest.skip("Kernel not compatible, we can not trigger the conditions")
+
+        assert uhdev.hwheel_multiplier > 1
+        assert 120 % uhdev.hwheel_multiplier == 0
+
+    def test_ac_pan_with_multiplier(self):
+        uhdev = self.uhdev
+
+        if not self.is_hwheel_highres(uhdev):
+            pytest.skip("Kernel not compatible, we can not trigger the conditions")
+
+        assert uhdev.hwheel_multiplier > 1
+
+        syn_event = self.syn_event
+        hmult = uhdev.hwheel_multiplier
+
+        r = uhdev.event(0, 0, wheels=(0, 1))
+        expected = [syn_event]
+        expected.append(
+            libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult)
+        )
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(0, 0, wheels=(0, -1))
+        expected = [syn_event]
+        expected.append(
+            libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120 / hmult)
+        )
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2))
+        expected.append(
+            libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult)
+        )
+
+        for _ in range(hmult - 1):
+            r = uhdev.event(1, -2, wheels=(0, 1))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEvents(expected, events)
+
+        r = uhdev.event(1, -2, wheels=(0, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+
+class TestMiMouse(TestWheelMouse):
+    def create_device(self):
+        return MIDongleMIWirelessMouse()
+
+    def assertInputEvents(self, expected_events, effective_events):
+        # Buttons and x/y are spread over two HID reports, so we can get two
+        # event frames for this device.
+        remaining = self.assertInputEventsIn(expected_events, effective_events)
+        try:
+            remaining.remove(libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0))
+        except ValueError:
+            # If there's no SYN_REPORT in the list, continue and let the
+            # assert below print out the real error
+            pass
+        assert remaining == []
diff --git a/tools/testing/selftests/hid/tests/test_multitouch.py b/tools/testing/selftests/hid/tests/test_multitouch.py
new file mode 100644 (file)
index 0000000..4265012
--- /dev/null
@@ -0,0 +1,2088 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+#
+
+from . import base
+from hidtools.hut import HUT
+from hidtools.util import BusType
+import libevdev
+import logging
+import pytest
+import sys
+import time
+
+logger = logging.getLogger("hidtools.test.multitouch")
+
+KERNEL_MODULE = ("hid-multitouch", "hid_multitouch")
+
+
+def BIT(x):
+    return 1 << x
+
+
+mt_quirks = {
+    "NOT_SEEN_MEANS_UP": BIT(0),
+    "SLOT_IS_CONTACTID": BIT(1),
+    "CYPRESS": BIT(2),
+    "SLOT_IS_CONTACTNUMBER": BIT(3),
+    "ALWAYS_VALID": BIT(4),
+    "VALID_IS_INRANGE": BIT(5),
+    "VALID_IS_CONFIDENCE": BIT(6),
+    "CONFIDENCE": BIT(7),
+    "SLOT_IS_CONTACTID_MINUS_ONE": BIT(8),
+    "NO_AREA": BIT(9),
+    "IGNORE_DUPLICATES": BIT(10),
+    "HOVERING": BIT(11),
+    "CONTACT_CNT_ACCURATE": BIT(12),
+    "FORCE_GET_FEATURE": BIT(13),
+    "FIX_CONST_CONTACT_ID": BIT(14),
+    "TOUCH_SIZE_SCALING": BIT(15),
+    "STICKY_FINGERS": BIT(16),
+    "ASUS_CUSTOM_UP": BIT(17),
+    "WIN8_PTP_BUTTONS": BIT(18),
+    "SEPARATE_APP_REPORT": BIT(19),
+    "MT_QUIRK_FORCE_MULTI_INPUT": BIT(20),
+}
+
+
+class Data(object):
+    pass
+
+
+class Touch(object):
+    def __init__(self, id, x, y):
+        self.contactid = id
+        self.x = x
+        self.y = y
+        self.cx = x
+        self.cy = y
+        self.tipswitch = True
+        self.confidence = True
+        self.tippressure = 15
+        self.azimuth = 0
+        self.inrange = True
+        self.width = 10
+        self.height = 10
+
+
+class Pen(Touch):
+    def __init__(self, x, y):
+        super().__init__(0, x, y)
+        self.barrel = False
+        self.invert = False
+        self.eraser = False
+        self.x_tilt = False
+        self.y_tilt = False
+        self.twist = 0
+
+
+class Digitizer(base.UHIDTestDevice):
+    @classmethod
+    def msCertificationBlob(cls, reportID):
+        return f"""
+        Usage Page (Digitizers)
+        Usage (Touch Screen)
+        Collection (Application)
+         Report ID ({reportID})
+         Usage Page (0xff00)
+         Usage (0xc5)
+         Logical Minimum (0)
+         Logical Maximum (255)
+         Report Size (8)
+         Report Count (256)
+         Feature (Data,Var,Abs)
+        End Collection
+    """
+
+    def __init__(
+        self,
+        name,
+        rdesc_str=None,
+        rdesc=None,
+        application="Touch Screen",
+        physical="Finger",
+        max_contacts=None,
+        input_info=(BusType.USB, 1, 2),
+        quirks=None,
+    ):
+        super().__init__(name, application, rdesc_str, rdesc, input_info)
+        self.scantime = 0
+        self.quirks = quirks
+        if max_contacts is None:
+            self.max_contacts = sys.maxsize
+            for features in self.parsed_rdesc.feature_reports.values():
+                for feature in features:
+                    if feature.usage_name in ["Contact Max"]:
+                        self.max_contacts = feature.logical_max
+            for inputs in self.parsed_rdesc.input_reports.values():
+                for i in inputs:
+                    if (
+                        i.usage_name in ["Contact Count"]
+                        and i.logical_max > 0
+                        and self.max_contacts > i.logical_max
+                    ):
+                        self.max_contacts = i.logical_max
+            if self.max_contacts == sys.maxsize:
+                self.max_contacts = 1
+        else:
+            self.max_contacts = max_contacts
+        self.physical = physical
+        self.cur_application = application
+
+        for features in self.parsed_rdesc.feature_reports.values():
+            for feature in features:
+                if feature.usage_name == "Inputmode":
+                    self.cur_application = "Mouse"
+
+        self.fields = []
+        for r in self.parsed_rdesc.input_reports.values():
+            if r.application_name == self.application:
+                physicals = [f.physical_name for f in r]
+                if self.physical not in physicals and None not in physicals:
+                    continue
+                self.fields = [f.usage_name for f in r]
+
+    @property
+    def touches_in_a_report(self):
+        return self.fields.count("Contact Id")
+
+    def event(self, slots, global_data=None, contact_count=None, incr_scantime=True):
+        if incr_scantime:
+            self.scantime += 1
+        rs = []
+        # make sure we have only the required number of available slots
+        slots = slots[: self.max_contacts]
+
+        if global_data is None:
+            global_data = Data()
+        if contact_count is None:
+            global_data.contactcount = len(slots)
+        else:
+            global_data.contactcount = contact_count
+        global_data.scantime = self.scantime
+
+        while len(slots):
+            r = self.create_report(
+                application=self.cur_application, data=slots, global_data=global_data
+            )
+            self.call_input_event(r)
+            rs.append(r)
+            global_data.contactcount = 0
+        return rs
+
+    def get_report(self, req, rnum, rtype):
+        if rtype != self.UHID_FEATURE_REPORT:
+            return (1, [])
+
+        rdesc = None
+        for v in self.parsed_rdesc.feature_reports.values():
+            if v.report_ID == rnum:
+                rdesc = v
+
+        if rdesc is None:
+            return (1, [])
+
+        if "Contact Max" not in [f.usage_name for f in rdesc]:
+            return (1, [])
+
+        self.contactmax = self.max_contacts
+        r = rdesc.create_report([self], None)
+        return (0, r)
+
+    def set_report(self, req, rnum, rtype, data):
+        if rtype != self.UHID_FEATURE_REPORT:
+            return 1
+
+        rdesc = None
+        for v in self.parsed_rdesc.feature_reports.values():
+            if v.report_ID == rnum:
+                rdesc = v
+
+        if rdesc is None:
+            return 1
+
+        if "Inputmode" not in [f.usage_name for f in rdesc]:
+            return 0
+
+        Inputmode_seen = False
+        for f in rdesc:
+            if "Inputmode" == f.usage_name:
+                values = f.get_values(data)
+                assert len(values) == 1
+                value = values[0]
+
+                if not Inputmode_seen:
+                    Inputmode_seen = True
+                    if value == 0:
+                        self.cur_application = "Mouse"
+                    elif value == 2:
+                        self.cur_application = "Touch Screen"
+                    elif value == 3:
+                        self.cur_application = "Touch Pad"
+                else:
+                    if value != 0:
+                        # Elan bug where the device doesn't work properly
+                        # if we set twice an Input Mode in the same Feature
+                        self.cur_application = "Mouse"
+
+        return 0
+
+
+class PTP(Digitizer):
+    def __init__(
+        self,
+        name,
+        type="Click Pad",
+        rdesc_str=None,
+        rdesc=None,
+        application="Touch Pad",
+        physical="Pointer",
+        max_contacts=None,
+        input_info=None,
+    ):
+        self.type = type.lower().replace(" ", "")
+        if self.type == "clickpad":
+            self.buttontype = 0
+        else:  # pressurepad
+            self.buttontype = 1
+        self.clickpad_state = False
+        self.left_state = False
+        self.right_state = False
+        super().__init__(
+            name, rdesc_str, rdesc, application, physical, max_contacts, input_info
+        )
+
+    def event(
+        self,
+        slots=None,
+        click=None,
+        left=None,
+        right=None,
+        contact_count=None,
+        incr_scantime=True,
+    ):
+        # update our internal state
+        if click is not None:
+            self.clickpad_state = click
+        if left is not None:
+            self.left_state = left
+        if right is not None:
+            self.right_state = right
+
+        # now create the global data
+        global_data = Data()
+        global_data.b1 = 1 if self.clickpad_state else 0
+        global_data.b2 = 1 if self.left_state else 0
+        global_data.b3 = 1 if self.right_state else 0
+
+        if slots is None:
+            slots = [Data()]
+
+        return super().event(slots, global_data, contact_count, incr_scantime)
+
+
+class MinWin8TSParallel(Digitizer):
+    def __init__(self, max_slots):
+        self.max_slots = max_slots
+        self.phys_max = 120, 90
+        rdesc_finger_str = f"""
+            Usage Page (Digitizers)
+            Usage (Finger)
+            Collection (Logical)
+             Report Size (1)
+             Report Count (1)
+             Logical Minimum (0)
+             Logical Maximum (1)
+             Usage (Tip Switch)
+             Input (Data,Var,Abs)
+             Report Size (7)
+             Logical Maximum (127)
+             Input (Cnst,Var,Abs)
+             Report Size (8)
+             Logical Maximum (255)
+             Usage (Contact Id)
+             Input (Data,Var,Abs)
+             Report Size (16)
+             Unit Exponent (-1)
+             Unit (SILinear: cm)
+             Logical Maximum (4095)
+             Physical Minimum (0)
+             Physical Maximum ({self.phys_max[0]})
+             Usage Page (Generic Desktop)
+             Usage (X)
+             Input (Data,Var,Abs)
+             Physical Maximum ({self.phys_max[1]})
+             Usage (Y)
+             Input (Data,Var,Abs)
+             Usage Page (Digitizers)
+             Usage (Azimuth)
+             Logical Maximum (360)
+             Unit (SILinear: deg)
+             Report Size (16)
+             Input (Data,Var,Abs)
+            End Collection
+"""
+        rdesc_str = f"""
+           Usage Page (Digitizers)
+           Usage (Touch Screen)
+           Collection (Application)
+            Report ID (1)
+            {rdesc_finger_str * self.max_slots}
+            Unit Exponent (-4)
+            Unit (SILinear: s)
+            Logical Maximum (65535)
+            Physical Maximum (65535)
+            Usage Page (Digitizers)
+            Usage (Scan Time)
+            Input (Data,Var,Abs)
+            Report Size (8)
+            Logical Maximum (255)
+            Usage (Contact Count)
+            Input (Data,Var,Abs)
+            Report ID (2)
+            Logical Maximum ({self.max_slots})
+            Usage (Contact Max)
+            Feature (Data,Var,Abs)
+          End Collection
+          {Digitizer.msCertificationBlob(68)}
+"""
+        super().__init__(f"uhid test parallel {self.max_slots}", rdesc_str)
+
+
+class MinWin8TSHybrid(Digitizer):
+    def __init__(self):
+        self.max_slots = 10
+        self.phys_max = 120, 90
+        rdesc_finger_str = f"""
+            Usage Page (Digitizers)
+            Usage (Finger)
+            Collection (Logical)
+             Report Size (1)
+             Report Count (1)
+             Logical Minimum (0)
+             Logical Maximum (1)
+             Usage (Tip Switch)
+             Input (Data,Var,Abs)
+             Report Size (7)
+             Logical Maximum (127)
+             Input (Cnst,Var,Abs)
+             Report Size (8)
+             Logical Maximum (255)
+             Usage (Contact Id)
+             Input (Data,Var,Abs)
+             Report Size (16)
+             Unit Exponent (-1)
+             Unit (SILinear: cm)
+             Logical Maximum (4095)
+             Physical Minimum (0)
+             Physical Maximum ({self.phys_max[0]})
+             Usage Page (Generic Desktop)
+             Usage (X)
+             Input (Data,Var,Abs)
+             Physical Maximum ({self.phys_max[1]})
+             Usage (Y)
+             Input (Data,Var,Abs)
+            End Collection
+"""
+        rdesc_str = f"""
+           Usage Page (Digitizers)
+           Usage (Touch Screen)
+           Collection (Application)
+            Report ID (1)
+            {rdesc_finger_str * 2}
+            Unit Exponent (-4)
+            Unit (SILinear: s)
+            Logical Maximum (65535)
+            Physical Maximum (65535)
+            Usage Page (Digitizers)
+            Usage (Scan Time)
+            Input (Data,Var,Abs)
+            Report Size (8)
+            Logical Maximum (255)
+            Usage (Contact Count)
+            Input (Data,Var,Abs)
+            Report ID (2)
+            Logical Maximum ({self.max_slots})
+            Usage (Contact Max)
+            Feature (Data,Var,Abs)
+          End Collection
+          {Digitizer.msCertificationBlob(68)}
+"""
+        super().__init__("uhid test hybrid", rdesc_str)
+
+
+class Win8TSConfidence(Digitizer):
+    def __init__(self, max_slots):
+        self.max_slots = max_slots
+        self.phys_max = 120, 90
+        rdesc_finger_str = f"""
+            Usage Page (Digitizers)
+            Usage (Finger)
+            Collection (Logical)
+             Report Size (1)
+             Report Count (1)
+             Logical Minimum (0)
+             Logical Maximum (1)
+             Usage (Tip Switch)
+             Input (Data,Var,Abs)
+             Usage (Confidence)
+             Input (Data,Var,Abs)
+             Report Size (6)
+             Logical Maximum (127)
+             Input (Cnst,Var,Abs)
+             Report Size (8)
+             Logical Maximum (255)
+             Usage (Contact Id)
+             Input (Data,Var,Abs)
+             Report Size (16)
+             Unit Exponent (-1)
+             Unit (SILinear: cm)
+             Logical Maximum (4095)
+             Physical Minimum (0)
+             Physical Maximum ({self.phys_max[0]})
+             Usage Page (Generic Desktop)
+             Usage (X)
+             Input (Data,Var,Abs)
+             Physical Maximum ({self.phys_max[1]})
+             Usage (Y)
+             Input (Data,Var,Abs)
+             Usage Page (Digitizers)
+             Usage (Azimuth)
+             Logical Maximum (360)
+             Unit (SILinear: deg)
+             Report Size (16)
+             Input (Data,Var,Abs)
+            End Collection
+"""
+        rdesc_str = f"""
+           Usage Page (Digitizers)
+           Usage (Touch Screen)
+           Collection (Application)
+            Report ID (1)
+            {rdesc_finger_str * self.max_slots}
+            Unit Exponent (-4)
+            Unit (SILinear: s)
+            Logical Maximum (65535)
+            Physical Maximum (65535)
+            Usage Page (Digitizers)
+            Usage (Scan Time)
+            Input (Data,Var,Abs)
+            Report Size (8)
+            Logical Maximum (255)
+            Usage (Contact Count)
+            Input (Data,Var,Abs)
+            Report ID (2)
+            Logical Maximum ({self.max_slots})
+            Usage (Contact Max)
+            Feature (Data,Var,Abs)
+          End Collection
+          {Digitizer.msCertificationBlob(68)}
+"""
+        super().__init__(f"uhid test confidence {self.max_slots}", rdesc_str)
+
+
+class SmartTechDigitizer(Digitizer):
+    def __init__(self, name, input_info):
+        super().__init__(
+            name,
+            rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 05 81 03 05 01 15 00 26 ff 0f 55 0e 65 11 75 10 95 01 35 00 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 06 15 00 26 ff 00 a1 01 85 02 75 08 95 3f 09 00 82 02 01 95 3f 09 00 92 02 01 c0 05 0d 09 04 a1 01 85 05 05 0d 09 20 a1 00 25 01 75 01 95 02 09 42 09 45 81 02 75 06 95 01 09 30 81 02 26 ff 00 75 08 09 51 81 02 75 10 09 38 81 02 95 02 26 ff 0f 09 48 09 49 81 02 05 01 09 30 09 31 81 02 c0 05 0d 09 20 a1 00 25 01 75 01 95 02 09 42 09 45 81 02 75 06 95 01 09 30 81 02 26 ff 00 75 08 09 51 81 02 75 10 09 38 81 02 95 02 26 ff 0f 09 48 09 49 81 02 05 01 09 30 09 31 81 02 c0 05 0d 09 20 a1 00 25 01 75 01 95 02 09 42 09 45 81 02 75 06 95 01 09 30 81 02 26 ff 00 75 08 09 51 81 02 75 10 09 38 81 02 95 02 26 ff 0f 09 48 09 49 81 02 05 01 09 30 09 31 81 02 c0 05 0d 09 20 a1 00 25 01 75 01 95 02 09 42 09 45 81 02 75 06 95 01 09 30 81 02 26 ff 00 75 08 09 51 81 02 75 10 09 38 81 02 95 02 26 ff 0f 09 48 09 49 81 02 05 01 09 30 09 31 81 02 c0 05 0d 75 08 95 01 15 00 25 0a 09 54 81 02 09 55 b1 02 c0 05 0d 09 0e a1 01 85 04 09 23 a1 02 15 00 25 02 75 08 95 02 09 52 09 53 b1 02 c0 c0 05 0d 09 04 a1 01 85 03 05 0d 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 02 81 03 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 95 01 09 51 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 02 81 03 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 95 01 09 51 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 02 81 03 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 95 01 09 51 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 02 81 03 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 95 01 09 51 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 05 0d 75 08 95 01 15 00 25 0a 09 54 81 02 09 55 b1 02 c0 05 0d 09 04 a1 01 85 06 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 06 81 03 95 01 75 10 65 11 55 0e 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 02 a1 01 85 07 09 20 a1 02 25 01 75 01 95 04 09 42 09 44 09 3c 09 45 81 02 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 09 38 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 02 a1 01 85 08 09 20 a1 02 25 01 75 01 95 04 09 42 09 44 09 3c 09 45 81 02 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 09 38 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 02 a1 01 85 09 09 20 a1 02 25 01 75 01 95 04 09 42 09 44 09 3c 09 45 81 02 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 09 38 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 02 a1 01 85 0a 09 20 a1 02 25 01 75 01 95 04 09 42 09 44 09 3c 09 45 81 02 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 09 38 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0",
+            input_info=input_info,
+        )
+
+    def create_report(self, data, global_data=None, reportID=None, application=None):
+        # this device has *a lot* of different reports, and most of them
+        # have the Touch Screen application. But the first one is a stylus
+        # report (as stated in the physical type), so we simply override
+        # the report ID to use what the device sends
+        return super().create_report(data, global_data=global_data, reportID=3)
+
+    def match_evdev_rule(self, application, evdev):
+        # we need to select the correct evdev node, as the device has multiple
+        # Touch Screen application collections
+        if application != "Touch Screen":
+            return True
+        absinfo = evdev.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X]
+        return absinfo is not None and absinfo.resolution == 3
+
+
+class BaseTest:
+    class TestMultitouch(base.BaseTestCase.TestUhid):
+        kernel_modules = [KERNEL_MODULE]
+
+        def create_device(self):
+            raise Exception("please reimplement me in subclasses")
+
+        def get_slot(self, uhdev, t, default):
+            if uhdev.quirks is None:
+                return default
+
+            if "SLOT_IS_CONTACTID" in uhdev.quirks:
+                return t.contactid
+
+            if "SLOT_IS_CONTACTID_MINUS_ONE" in uhdev.quirks:
+                return t.contactid - 1
+
+            return default
+
+        def test_creation(self):
+            """Make sure the device gets processed by the kernel and creates
+            the expected application input node.
+
+            If this fail, there is something wrong in the device report
+            descriptors."""
+            super().test_creation()
+
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            # some sanity checking for the quirks
+            if uhdev.quirks is not None:
+                for q in uhdev.quirks:
+                    assert q in mt_quirks
+
+            assert evdev.num_slots == uhdev.max_contacts
+
+            if uhdev.max_contacts > 1:
+                assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+                assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+            if uhdev.max_contacts > 2:
+                assert evdev.slots[2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+        def test_required_usages(self):
+            """Make sure the device exports the correct required features and
+            inputs."""
+            uhdev = self.uhdev
+            rdesc = uhdev.parsed_rdesc
+            for feature in rdesc.feature_reports.values():
+                for field in feature:
+                    page_id = field.usage >> 16
+                    value = field.usage & 0xFF
+                    try:
+                        if HUT[page_id][value] == "Contact Max":
+                            assert HUT[page_id][field.application] in [
+                                "Touch Screen",
+                                "Touch Pad",
+                                "System Multi-Axis Controller",
+                            ]
+                    except KeyError:
+                        pass
+
+                    try:
+                        if HUT[page_id][value] == "Inputmode":
+                            assert HUT[page_id][field.application] in [
+                                "Touch Screen",
+                                "Touch Pad",
+                                "Device Configuration",
+                            ]
+                    except KeyError:
+                        pass
+
+        def test_mt_single_touch(self):
+            """send a single touch in the first slot of the device,
+            and release it."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            t0 = Touch(1, 50, 100)
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+
+            slot = self.get_slot(uhdev, t0, 0)
+
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+            assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+            assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+            assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+
+            t0.tipswitch = False
+            if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
+                t0.inrange = False
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+            assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+        def test_mt_dual_touch(self):
+            """Send 2 touches in the first 2 slots.
+            Make sure the kernel sees this as a dual touch.
+            Release and check
+
+            Note: PTP will send here BTN_DOUBLETAP emulation"""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            t0 = Touch(1, 50, 100)
+            t1 = Touch(2, 150, 200)
+
+            if uhdev.quirks is not None and (
+                "SLOT_IS_CONTACTID" in uhdev.quirks
+                or "SLOT_IS_CONTACTNUMBER" in uhdev.quirks
+            ):
+                t1.contactid = 0
+
+            slot0 = self.get_slot(uhdev, t0, 0)
+            slot1 = self.get_slot(uhdev, t1, 1)
+
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+            assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+            assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+            r = uhdev.event([t0, t1])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH) not in events
+            assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+            assert (
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 5) not in events
+            )
+            assert (
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 10) not in events
+            )
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+            assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+            assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150
+            assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200
+
+            t0.tipswitch = False
+            if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
+                t0.inrange = False
+            r = uhdev.event([t0, t1])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+            assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X) not in events
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y) not in events
+
+            t1.tipswitch = False
+            if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
+                t1.inrange = False
+
+            if uhdev.quirks is not None and "SLOT_IS_CONTACTNUMBER" in uhdev.quirks:
+                r = uhdev.event([t0, t1])
+            else:
+                r = uhdev.event([t1])
+
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+            assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: uhdev.max_contacts <= 2, "Device not compatible"
+        )
+        def test_mt_triple_tap(self):
+            """Send 3 touches in the first 3 slots.
+            Make sure the kernel sees this as a triple touch.
+            Release and check
+
+            Note: PTP will send here BTN_TRIPLETAP emulation"""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            t0 = Touch(1, 50, 100)
+            t1 = Touch(2, 150, 200)
+            t2 = Touch(3, 250, 300)
+            r = uhdev.event([t0, t1, t2])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+
+            slot0 = self.get_slot(uhdev, t0, 0)
+            slot1 = self.get_slot(uhdev, t1, 1)
+            slot2 = self.get_slot(uhdev, t2, 2)
+
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+            assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+            assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150
+            assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200
+            assert evdev.slots[slot2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 2
+            assert evdev.slots[slot2][libevdev.EV_ABS.ABS_MT_POSITION_X] == 250
+            assert evdev.slots[slot2][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 300
+
+            t0.tipswitch = False
+            t1.tipswitch = False
+            t2.tipswitch = False
+            if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
+                t0.inrange = False
+                t1.inrange = False
+                t2.inrange = False
+            r = uhdev.event([t0, t1, t2])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+            assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+            assert evdev.slots[slot2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: uhdev.max_contacts <= 2, "Device not compatible"
+        )
+        def test_mt_max_contact(self):
+            """send the maximum number of contact as reported by the device.
+            Make sure all contacts are forwarded and that there is no miss.
+            Release and check."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            touches = [
+                Touch(i, (i + 3) * 20, (i + 3) * 20 + 5)
+                for i in range(uhdev.max_contacts)
+            ]
+            if (
+                uhdev.quirks is not None
+                and "SLOT_IS_CONTACTID_MINUS_ONE" in uhdev.quirks
+            ):
+                for t in touches:
+                    t.contactid += 1
+            r = uhdev.event(touches)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            for i, t in enumerate(touches):
+                slot = self.get_slot(uhdev, t, i)
+
+                assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == i
+                assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_X] == t.x
+                assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_Y] == t.y
+
+            for t in touches:
+                t.tipswitch = False
+                if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
+                    t.inrange = False
+
+            r = uhdev.event(touches)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            for i, t in enumerate(touches):
+                slot = self.get_slot(uhdev, t, i)
+
+                assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: (
+                uhdev.touches_in_a_report == 1
+                or uhdev.quirks is not None
+                and "CONTACT_CNT_ACCURATE" not in uhdev.quirks
+            ),
+            "Device not compatible, we can not trigger the conditions",
+        )
+        def test_mt_contact_count_accurate(self):
+            """Test the MT_QUIRK_CONTACT_CNT_ACCURATE from the kernel.
+            A report should forward an accurate contact count and the kernel
+            should ignore any data provided after we have reached this
+            contact count."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            t0 = Touch(1, 50, 100)
+            t1 = Touch(2, 150, 200)
+
+            slot0 = self.get_slot(uhdev, t0, 0)
+            slot1 = self.get_slot(uhdev, t1, 1)
+
+            r = uhdev.event([t0, t1], contact_count=1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+            assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 0) in events
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+            assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+            assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+    class TestWin8Multitouch(TestMultitouch):
+        def test_required_usages8(self):
+            """Make sure the device exports the correct required features and
+            inputs."""
+            uhdev = self.uhdev
+            rdesc = uhdev.parsed_rdesc
+            for feature in rdesc.feature_reports.values():
+                for field in feature:
+                    page_id = field.usage >> 16
+                    value = field.usage & 0xFF
+                    try:
+                        if HUT[page_id][value] == "Inputmode":
+                            assert HUT[field.application] not in ["Touch Screen"]
+                    except KeyError:
+                        pass
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: uhdev.fields.count("X") == uhdev.touches_in_a_report,
+            "Device not compatible, we can not trigger the conditions",
+        )
+        def test_mt_tx_cx(self):
+            """send a single touch in the first slot of the device, with
+            different values of Tx and Cx. Make sure the kernel reports Tx."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            t0 = Touch(1, 5, 10)
+            t0.cx = 50
+            t0.cy = 100
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 5
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TOOL_X] == 50
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 10
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TOOL_Y] == 100
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: "In Range" not in uhdev.fields,
+            "Device not compatible, missing In Range usage",
+        )
+        def test_mt_inrange(self):
+            """Send one contact that has the InRange bit set before/after
+            tipswitch.
+            Kernel is supposed to mark the contact with a distance > 0
+            when inrange is set but not tipswitch.
+
+            This tests the hovering capability of devices (MT_QUIRK_HOVERING).
+
+            Make sure the contact is only released from the kernel POV
+            when the inrange bit is set to 0."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            t0 = Touch(1, 150, 200)
+            t0.tipswitch = False
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+            assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 0) in events
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_DISTANCE) in events
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_DISTANCE] > 0
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200
+            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+            t0.tipswitch = True
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_DISTANCE, 0) in events
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_DISTANCE] == 0
+
+            t0.tipswitch = False
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_DISTANCE) in events
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_DISTANCE] > 0
+
+            t0.inrange = False
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+        def test_mt_duplicates(self):
+            """Test the MT_QUIRK_IGNORE_DUPLICATES from the kernel.
+            If a touch is reported more than once with the same Contact ID,
+            we should only handle the first touch.
+
+            Note: this is not in MS spec, but the current kernel behaves
+            like that"""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            t0 = Touch(1, 5, 10)
+            t1 = Touch(1, 15, 20)
+            t2 = Touch(2, 50, 100)
+
+            r = uhdev.event([t0, t1, t2], contact_count=2)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+            assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 0) in events
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 5
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 10
+            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+
+        def test_mt_release_miss(self):
+            """send a single touch in the first slot of the device, and
+            forget to release it. The kernel is supposed to release by itself
+            the touch in 100ms.
+            Make sure that we are dealing with a new touch by resending the
+            same touch after the timeout expired, and check that the kernel
+            considers it as a separate touch (different tracking ID)"""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            t0 = Touch(1, 5, 10)
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+
+            time.sleep(0.2)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: "Azimuth" not in uhdev.fields,
+            "Device not compatible, missing Azimuth usage",
+        )
+        def test_mt_azimuth(self):
+            """Check for the azimtuh information bit.
+            When azimuth is presented by the device, it should be exported
+            as ABS_MT_ORIENTATION and the exported value should report a quarter
+            of circle."""
+            uhdev = self.uhdev
+
+            t0 = Touch(1, 5, 10)
+            t0.azimuth = 270
+
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+
+            # orientation is clockwise, while Azimuth is counter clockwise
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_ORIENTATION, 90) in events
+
+    class TestPTP(TestWin8Multitouch):
+        def test_ptp_buttons(self):
+            """check for button reliability.
+            There are 2 types of touchpads: the click pads and the pressure pads.
+            Each should reliably report the BTN_LEFT events.
+            """
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            if uhdev.type == "clickpad":
+                r = uhdev.event(click=True)
+                events = uhdev.next_sync_events()
+                self.debug_reports(r, uhdev, events)
+                assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) in events
+                assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
+
+                r = uhdev.event(click=False)
+                events = uhdev.next_sync_events()
+                self.debug_reports(r, uhdev, events)
+                assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) in events
+                assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+            else:
+                r = uhdev.event(left=True)
+                events = uhdev.next_sync_events()
+                self.debug_reports(r, uhdev, events)
+                assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) in events
+                assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
+
+                r = uhdev.event(left=False)
+                events = uhdev.next_sync_events()
+                self.debug_reports(r, uhdev, events)
+                assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) in events
+                assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+
+                r = uhdev.event(right=True)
+                events = uhdev.next_sync_events()
+                self.debug_reports(r, uhdev, events)
+                assert libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1) in events
+                assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
+
+                r = uhdev.event(right=False)
+                events = uhdev.next_sync_events()
+                self.debug_reports(r, uhdev, events)
+                assert libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0) in events
+                assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: "Confidence" not in uhdev.fields,
+            "Device not compatible, missing Confidence usage",
+        )
+        def test_ptp_confidence(self):
+            """Check for the validity of the confidence bit.
+            When a contact is marked as not confident, it should be detected
+            as a palm from the kernel POV and released.
+
+            Note: if the kernel exports ABS_MT_TOOL_TYPE, it shouldn't release
+            the touch but instead convert it to ABS_MT_TOOL_PALM."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            t0 = Touch(1, 150, 200)
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+
+            t0.confidence = False
+            r = uhdev.event([t0])
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+
+            if evdev.absinfo[libevdev.EV_ABS.ABS_MT_TOOL_TYPE] is not None:
+                # the kernel exports MT_TOOL_PALM
+                assert (
+                    libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TOOL_TYPE, 2) in events
+                )
+                assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] != -1
+
+                t0.tipswitch = False
+                r = uhdev.event([t0])
+                events = uhdev.next_sync_events()
+                self.debug_reports(r, uhdev, events)
+
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: uhdev.touches_in_a_report >= uhdev.max_contacts,
+            "Device not compatible, we can not trigger the conditions",
+        )
+        def test_ptp_non_touch_data(self):
+            """Some single finger hybrid touchpads might not provide the
+            button information in subsequent reports (only in the first report).
+
+            Emulate this and make sure we do not release the buttons in the
+            middle of the event."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            touches = [Touch(i, i * 10, i * 10 + 5) for i in range(uhdev.max_contacts)]
+            contact_count = uhdev.max_contacts
+            incr_scantime = True
+            btn_state = True
+            events = None
+            while touches:
+                t = touches[: uhdev.touches_in_a_report]
+                touches = touches[uhdev.touches_in_a_report :]
+                r = uhdev.event(
+                    t,
+                    click=btn_state,
+                    left=btn_state,
+                    contact_count=contact_count,
+                    incr_scantime=incr_scantime,
+                )
+                contact_count = 0
+                incr_scantime = False
+                btn_state = False
+                events = uhdev.next_sync_events()
+                self.debug_reports(r, uhdev, events)
+                if touches:
+                    assert len(events) == 0
+
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) in events
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) not in events
+            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
+
+
+################################################################################
+#
+# Windows 7 compatible devices
+#
+################################################################################
+class Test3m_0596_0500(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test 3m_0596_0500",
+            rdesc="05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 09 01 95 01 75 01 15 00 25 01 81 02 95 07 75 01 81 03 95 01 75 08 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 00 00 95 02 75 10 81 02 c0 a1 02 15 00 26 ff 00 09 01 95 39 75 08 81 01 c0 c0 05 0d 09 0e a1 01 85 11 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 09 04 a1 01 85 10 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 85 12 09 55 95 01 75 08 15 00 25 0a b1 02 06 00 ff 15 00 26 ff 00 85 03 09 01 75 08 95 07 b1 02 85 04 09 01 75 08 95 17 b1 02 85 05 09 01 75 08 95 47 b1 02 85 06 09 01 75 08 95 07 b1 02 85 07 09 01 75 08 95 07 b1 02 85 08 09 01 75 08 95 07 b1 02 85 09 09 01 75 08 95 3f b1 02 c0",
+            input_info=(BusType.USB, 0x0596, 0x0500),
+            max_contacts=60,
+            quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID", "TOUCH_SIZE_SCALING"),
+        )
+
+
+class Test3m_0596_0506(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test 3m_0596_0506",
+            rdesc="05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 09 01 95 01 75 01 15 00 25 01 81 02 95 07 75 01 81 03 95 01 75 08 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 00 00 95 02 75 10 81 02 c0 a1 02 15 00 26 ff 00 09 01 95 39 75 08 81 03 c0 c0 05 0d 09 0e a1 01 85 11 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 09 04 a1 01 85 13 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 3c 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 02 81 03 05 0d 85 12 09 55 95 01 75 08 15 00 25 3c b1 02 06 00 ff 15 00 26 ff 00 85 03 09 01 75 08 95 07 b1 02 85 04 09 01 75 08 95 17 b1 02 85 05 09 01 75 08 95 47 b1 02 85 06 09 01 75 08 95 07 b1 02 85 73 09 01 75 08 95 07 b1 02 85 08 09 01 75 08 95 07 b1 02 85 09 09 01 75 08 95 3f b1 02 85 0f 09 01 75 08 96 07 02 b1 02 c0",
+            input_info=(BusType.USB, 0x0596, 0x0506),
+            max_contacts=60,
+            quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID", "TOUCH_SIZE_SCALING"),
+        )
+
+
+class TestActionStar_2101_1011(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test ActionStar_2101_1011",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4d 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4d 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 02 09 55 25 02 75 08 95 01 b1 02 c0",
+            input_info=(BusType.USB, 0x2101, 0x1011),
+        )
+
+    def test_mt_actionstar_inrange(self):
+        """Special sequence that might not be handled properly"""
+        uhdev = self.uhdev
+        evdev = uhdev.get_evdev()
+
+        # fmt: off
+        sequence = [
+            # t0 = Touch(1, 6999, 2441) | t1 = Touch(2, 15227, 2026)
+            '01 ff 01 57 1b 89 09 ff 02 7b 3b ea 07 02',
+            # t0.xy = (6996, 2450)      | t1.y = 2028
+            '01 ff 01 54 1b 92 09 ff 02 7b 3b ec 07 02',
+            # t1.xy = (15233, 2040)     | t0.tipswitch = False
+            '01 ff 02 81 3b f8 07 fe 01 54 1b 92 09 02',
+            # t1                        | t0.inrange = False
+            '01 ff 02 81 3b f8 07 fc 01 54 1b 92 09 02',
+        ]
+        # fmt: on
+
+        for num, r_str in enumerate(sequence):
+            r = [int(i, 16) for i in r_str.split()]
+            uhdev.call_input_event(r)
+            events = uhdev.next_sync_events()
+            self.debug_reports([r], uhdev)
+            for e in events:
+                print(e)
+            if num == 2:
+                assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+
+class TestAsus_computers_0486_0185(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test asus-computers_0486_0185",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 95 01 75 01 81 02 09 32 81 02 09 47 81 02 75 05 81 03 09 30 26 ff 00 75 08 81 02 09 51 25 02 81 02 26 96 0d 05 01 75 10 55 0d 65 33 09 30 35 00 46 fd 1d 81 02 09 31 46 60 11 81 02 c0 09 22 a1 02 05 0d 35 00 45 00 55 00 65 00 09 42 25 01 75 01 81 02 09 32 81 02 09 47 81 02 75 05 81 03 09 30 26 ff 00 75 08 81 02 09 51 25 02 81 02 26 96 0d 05 01 75 10 55 0d 65 33 09 30 46 fd 1d 81 02 09 31 46 60 11 81 02 c0 35 00 45 00 55 00 65 00 05 0d 09 54 75 08 25 02 81 02 85 08 09 55 b1 02 c0 09 0e a1 01 85 07 09 22 a1 00 09 52 25 0a b1 02 c0 05 0c 09 01 a1 01 85 06 09 01 26 ff 00 95 08 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 02 25 01 75 01 95 02 81 02 95 06 81 03 26 96 0d 05 01 75 10 95 01 55 0d 65 33 09 30 46 fd 1d 81 02 09 31 46 60 11 81 02 c0 c0 06 ff 01 09 01 a1 01 26 ff 00 35 00 45 00 55 00 65 00 85 05 75 08 95 3f 09 00 81 02 c0",
+            input_info=(BusType.USB, 0x0486, 0x0185),
+            quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID_MINUS_ONE"),
+        )
+
+
+class TestAtmel_03eb_201c(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test atmel_03eb_201c",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4b 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4b 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 02 09 55 25 02 75 08 95 01 b1 02 c0",
+            input_info=(BusType.USB, 0x03EB, 0x201C),
+        )
+
+
+class TestAtmel_03eb_211c(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test atmel_03eb_211c",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 37 81 02 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 46 56 0a 26 ff 0f 09 30 81 02 46 b2 05 26 ff 0f 09 31 81 02 05 0d 75 08 85 02 09 55 25 10 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x03EB, 0x211C),
+        )
+
+
+class TestCando_2087_0a02(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test cando_2087_0a02",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 0f 75 10 55 0e 65 33 09 30 35 00 46 6d 03 81 02 46 ec 01 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 0f 75 10 55 0e 65 33 09 30 35 00 46 6d 03 81 02 46 ec 01 09 31 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 02 81 02 85 02 09 55 b1 02 c0 06 00 ff 09 01 a1 01 85 a6 95 22 75 08 26 ff 00 15 00 09 01 81 02 85 a5 95 06 75 08 26 ff 00 15 00 09 01 91 02 c0",
+            input_info=(BusType.USB, 0x2087, 0x0A02),
+        )
+
+
+class TestCando_2087_0b03(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test cando_2087_0b03",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 49 46 f2 03 81 02 09 31 26 ff 29 46 39 02 81 02 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 49 46 f2 03 81 02 09 31 26 ff 29 46 39 02 81 02 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 02 09 55 25 02 75 08 95 01 b1 02 c0",
+            input_info=(BusType.USB, 0x2087, 0x0B03),
+        )
+
+
+class TestCVTouch_1ff7_0013(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test cvtouch_1ff7_0013",
+            rdesc="06 00 ff 09 00 a1 01 85 fd 06 00 ff 09 01 09 02 09 03 09 04 09 05 09 06 15 00 26 ff 00 75 08 95 06 81 02 85 fe 06 00 ff 09 01 09 02 09 03 09 04 15 00 26 ff 00 75 08 95 04 b1 02 c0 05 01 09 02 a1 01 09 01 a1 00 85 01 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 ff 7f 75 10 95 02 81 02 05 0d 09 33 15 00 26 ff 00 35 00 46 ff 00 75 08 95 01 81 02 05 01 09 38 15 81 25 7f 35 81 45 7f 95 01 81 06 c0 c0 06 00 ff 09 00 a1 01 85 fc 15 00 26 ff 00 19 01 29 3f 75 08 95 3f 81 02 19 01 29 3f 91 02 c0 06 00 ff 09 00 a1 01 85 fb 15 00 26 ff 00 19 01 29 3f 75 08 95 3f 81 02 19 01 29 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 85 03 09 55 15 00 25 02 b1 02 c0 09 0e a1 01 85 04 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x1FF7, 0x0013),
+            quirks=("NOT_SEEN_MEANS_UP",),
+        )
+
+
+class TestCvtouch_1ff7_0017(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test cvtouch_1ff7_0017",
+            rdesc="06 00 ff 09 00 a1 01 85 fd 06 00 ff 09 01 09 02 09 03 09 04 09 05 09 06 15 00 26 ff 00 75 08 95 06 81 02 85 fe 06 00 ff 09 01 09 02 09 03 09 04 15 00 26 ff 00 75 08 95 04 b1 02 c0 05 01 09 02 a1 01 09 01 a1 00 85 01 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 03 05 01 09 30 09 31 15 00 26 ff 0f 35 00 46 ff 0f 75 10 95 02 81 02 09 00 15 00 25 ff 35 00 45 ff 75 08 95 01 81 02 09 38 15 81 25 7f 95 01 81 06 c0 c0 06 00 ff 09 00 a1 01 85 fc 15 00 25 ff 19 01 29 3f 75 08 95 3f 81 02 19 01 29 3f 91 02 c0 06 00 ff 09 00 a1 01 85 fb 15 00 25 ff 19 01 29 3f 75 08 95 3f 81 02 19 01 29 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 09 31 81 02 c0 05 0d 09 54 95 01 75 08 81 02 85 03 09 55 25 02 b1 02 c0 09 0e a1 01 85 04 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x1FF7, 0x0017),
+        )
+
+
+class TestCypress_04b4_c001(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test cypress_04b4_c001",
+            rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 04 a1 01 85 02 09 22 09 53 95 01 75 08 81 02 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 09 55 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x04B4, 0xC001),
+        )
+
+
+class TestData_modul_7374_1232(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test data-modul_7374_1232",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 37 81 02 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 46 d0 07 26 ff 0f 09 30 81 02 46 40 06 09 31 81 02 05 0d 75 08 85 02 09 55 25 10 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x7374, 0x1232),
+        )
+
+
+class TestData_modul_7374_1252(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test data-modul_7374_1252",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 37 81 02 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 46 d0 07 26 ff 0f 09 30 81 02 46 40 06 09 31 81 02 05 0d 75 08 85 02 09 55 25 10 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x7374, 0x1252),
+        )
+
+
+class TestE4_2219_044c(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test e4_2219_044c",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 09 55 b1 02 c0 09 0e a1 01 85 02 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 15 00 26 ff 7f 75 10 95 02 81 02 05 01 09 38 15 81 25 7f 75 08 95 01 81 06 c0 c0",
+            input_info=(BusType.USB, 0x2219, 0x044C),
+        )
+
+
+class TestEgalax_capacitive_0eef_7224(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test egalax-capacitive_0eef_7224",
+            rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x0EEF, 0x7224),
+            quirks=("SLOT_IS_CONTACTID", "ALWAYS_VALID"),
+        )
+
+
+class TestEgalax_capacitive_0eef_72fa(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test egalax-capacitive_0eef_72fa",
+            rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 72 22 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 87 13 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 72 22 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 87 13 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x0EEF, 0x72FA),
+            quirks=("SLOT_IS_CONTACTID", "VALID_IS_INRANGE"),
+        )
+
+
+class TestEgalax_capacitive_0eef_7336(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test egalax-capacitive_0eef_7336",
+            rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 c1 20 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c2 18 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 c1 20 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c2 18 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x0EEF, 0x7336),
+        )
+
+
+class TestEgalax_capacitive_0eef_7337(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test egalax-capacitive_0eef_7337",
+            rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 ae 17 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c3 0e 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 ae 17 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c3 0e 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x0EEF, 0x7337),
+        )
+
+
+class TestEgalax_capacitive_0eef_7349(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test egalax-capacitive_0eef_7349",
+            rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x0EEF, 0x7349),
+            quirks=("SLOT_IS_CONTACTID", "ALWAYS_VALID"),
+        )
+
+
+class TestEgalax_capacitive_0eef_73f4(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test egalax-capacitive_0eef_73f4",
+            rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 96 4e 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 23 2c 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 96 4e 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 23 2c 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x0EEF, 0x73F4),
+        )
+
+
+class TestEgalax_capacitive_0eef_a001(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test egalax-capacitive_0eef_a001",
+            rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 23 28 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 11 19 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x0EEF, 0xA001),
+            quirks=("SLOT_IS_CONTACTID", "VALID_IS_INRANGE"),
+        )
+
+
+class TestElo_touchsystems_04e7_0022(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test elo-touchsystems_04e7_0022",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 0f 75 10 55 0e 65 33 09 30 35 00 46 ff 0f 81 02 46 ff 0f 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 46 ff 0f 09 31 81 02 c0 05 0d 09 54 25 10 95 01 75 08 81 02 85 08 09 55 25 02 b1 02 c0 09 0e a1 01 85 07 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 06 00 ff 09 55 85 80 15 00 26 ff 00 75 08 95 01 b1 82 c0 05 01 09 02 a1 01 85 54 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 15 00 26 ff 0f 75 10 95 01 81 02 09 31 75 10 95 01 81 02 09 3b 16 00 00 26 00 01 36 00 00 46 00 01 66 00 00 75 10 95 01 81 62 c0 c0",
+            input_info=(BusType.USB, 0x04E7, 0x0022),
+        )
+
+
+class TestElo_touchsystems_04e7_0080(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test elo-touchsystems_04e7_0080",
+            rdesc="05 0d 09 04 a1 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 7c 24 75 10 95 01 09 30 81 02 09 31 46 96 14 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 7c 24 75 10 95 01 09 30 81 02 09 31 46 96 14 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 7c 24 75 10 95 01 09 30 81 02 09 31 46 96 14 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 7c 24 75 10 95 01 09 30 81 02 09 31 46 96 14 81 02 c0 05 0d 09 54 75 08 95 01 15 00 25 08 81 02 09 55 b1 02 c0",
+            input_info=(BusType.USB, 0x04E7, 0x0080),
+        )
+
+
+class TestFlatfrog_25b5_0002(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test flatfrog_25b5_0002",
+            rdesc="05 0d 09 04 a1 01 85 05 09 22 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 65 00 55 00 05 0d 55 0c 66 01 10 75 20 95 01 27 ff ff ff 7f 45 00 09 56 81 02 75 08 95 01 15 00 25 28 09 54 81 02 09 55 85 06 25 28 b1 02 c0 65 00 55 00 45 00 09 0e a1 01 85 03 09 23 a1 02 09 52 15 02 25 02 75 08 95 01 b1 02 09 53 15 00 25 0a 75 08 95 01 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x25B5, 0x0002),
+            quirks=("NOT_SEEN_MEANS_UP", "NO_AREA"),
+            max_contacts=40,
+        )
+
+
+class TestFocaltech_10c4_81b9(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test focaltech_10c4_81b9",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 85 02 09 55 75 08 95 01 b1 02 c0",
+            input_info=(BusType.USB, 0x10C4, 0x81B9),
+            quirks=("ALWAYS_VALID",),
+            max_contacts=5,
+        )
+
+
+class TestHanvon_20b3_0a18(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test hanvon_20b3_0a18",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4b 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4b 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 02 09 55 25 02 75 08 95 01 b1 02 c0",
+            input_info=(BusType.USB, 0x20B3, 0x0A18),
+        )
+
+
+class TestHuitoo_03f7_0003(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test huitoo_03f7_0003",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 09 55 b1 02 c0 09 0e a1 01 85 02 09 23 a1 02 09 52 09 53 15 00 25 10 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 15 00 26 ff 0f 35 00 46 ff 0f 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 3f 09 02 81 02 95 3f 09 02 91 02 c0",
+            input_info=(BusType.USB, 0x03F7, 0x0003),
+        )
+
+
+class TestIdeacom_1cb6_6650(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test ideacom_1cb6_6650",
+            rdesc="05 0d 09 04 a1 01 85 0a 09 22 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 26 ff 1f 75 10 95 01 55 0d 65 33 09 31 35 00 46 61 13 81 02 09 30 46 73 22 81 02 05 0d 75 08 95 01 09 30 26 ff 00 81 02 09 51 81 02 85 0c 09 55 25 02 95 01 b1 02 c0 06 00 ff 85 02 09 01 75 08 95 07 b1 02 85 03 09 02 75 08 95 07 b1 02 85 04 09 03 75 08 95 07 b1 02 85 05 09 04 75 08 95 07 b1 02 85 06 09 05 75 08 96 27 00 b1 02 85 07 09 06 75 08 96 27 00 b1 02 85 08 09 07 75 08 95 07 b1 02 85 09 09 08 75 08 95 07 b1 02 85 0b 09 09 75 08 96 07 00 b1 02 85 0d 09 0a 75 08 96 27 00 b1 02 c0 09 0e a1 01 85 0e 09 52 09 53 95 07 b1 02 c0 05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 75 06 95 01 81 01 05 01 09 31 09 30 15 00 27 ff 1f 00 00 75 10 95 02 81 02 c0 09 01 a1 02 15 00 26 ff 00 95 02 75 08 81 03 c0 c0",
+            input_info=(BusType.USB, 0x1CB6, 0x6650),
+        )
+
+
+class TestIdeacom_1cb6_6651(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test ideacom_1cb6_6651",
+            rdesc="05 0d 09 04 a1 01 85 0a 09 22 a1 02 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 26 ff 1f 75 10 95 01 55 0d 65 33 09 31 35 00 46 39 13 81 02 09 30 46 24 22 81 02 05 0d 75 08 95 01 09 30 26 ff 00 81 02 09 51 81 02 85 0c 09 55 25 02 95 01 b1 02 c0 06 00 ff 85 02 09 01 75 08 95 07 b1 02 85 03 09 02 75 08 95 07 b1 02 85 04 09 03 75 08 95 07 b1 02 85 05 09 04 75 08 95 07 b1 02 85 06 09 05 75 08 95 1f b1 02 85 07 09 06 75 08 96 1f 00 b1 02 85 08 09 07 75 08 95 07 b1 02 85 09 09 08 75 08 95 07 b1 02 85 0b 09 09 75 08 95 07 b1 02 85 0d 09 0a 75 08 96 1f 00 b1 02 c0 09 0e a1 01 85 0e 09 52 09 53 95 07 b1 02 c0 05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 75 06 95 01 81 01 05 01 09 31 09 30 15 00 27 ff 1f 00 00 75 10 95 02 81 02 c0 09 01 a1 02 15 00 26 ff 00 95 02 75 08 81 03 c0 c0",
+            input_info=(BusType.USB, 0x1CB6, 0x6651),
+        )
+
+
+class TestIkaist_2793_0001(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test ikaist_2793_0001",
+            rdesc="05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 09 01 95 01 75 01 15 00 25 01 81 02 95 07 75 01 81 03 95 01 75 08 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 00 00 95 02 75 10 81 02 c0 a1 02 15 00 26 ff 00 09 01 95 39 75 08 81 03 c0 c0 05 0d 09 0e a1 01 85 11 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 09 04 a1 01 85 13 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 3c 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 02 81 03 05 0d 85 12 09 55 95 01 75 08 15 00 25 3c b1 02 06 00 ff 15 00 26 ff 00 85 1e 09 01 75 08 95 80 b1 02 85 1f 09 01 75 08 96 3f 01 b1 02 c0",
+            input_info=(BusType.USB, 0x2793, 0x0001),
+        )
+
+
+class TestIrmtouch_23c9_5666(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test irmtouch_23c9_5666",
+            rdesc="05 0d 09 04 a1 01 85 0a 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 0c 09 23 a1 02 09 52 15 00 25 06 75 08 95 01 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x23C9, 0x5666),
+        )
+
+
+class TestIrtouch_6615_0070(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test irtouch_6615_0070",
+            rdesc="05 01 09 02 a1 01 85 10 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 09 30 09 31 15 00 26 ff 7f 75 10 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 30 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 05 0d 09 54 15 00 26 02 00 75 08 95 01 81 02 85 03 09 55 15 00 26 ff 00 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 02 09 52 09 53 15 00 26 ff 00 75 08 95 02 b1 02 c0 05 0d 09 02 a1 01 85 20 09 20 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 85 01 06 00 ff 09 01 75 08 95 01 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x6615, 0x0070),
+        )
+
+
+class TestIrtouch_6615_0081(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test irtouch_6615_0081",
+            rdesc="05 0d 09 04 a1 01 85 30 09 22 09 00 15 00 26 ff 00 75 08 95 05 81 02 a1 00 05 0d 09 51 15 00 26 ff 00 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0e 65 13 35 00 46 b5 04 75 10 95 01 81 02 09 31 35 00 46 8a 03 81 02 09 32 35 00 46 8a 03 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 00 15 00 26 ff 00 75 08 95 01 81 02 c0 a1 00 05 0d 09 51 15 00 26 ff 00 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0e 65 13 35 00 46 b5 04 75 10 95 01 81 02 09 31 35 00 46 8a 03 81 02 09 32 35 00 46 8a 03 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 00 15 00 26 ff 00 75 08 95 01 81 02 c0 a1 00 05 0d 09 51 15 00 26 ff 00 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0e 65 13 35 00 46 b5 04 75 10 95 01 81 02 09 31 35 00 46 8a 03 81 02 09 32 35 00 46 8a 03 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 00 15 00 26 ff 00 75 08 95 01 81 02 c0 a1 00 05 0d 09 54 15 00 25 1f 75 05 95 01 81 02 09 00 15 00 25 07 75 03 95 01 81 02 09 00 15 00 26 ff 00 75 08 95 01 81 02 c0 09 55 85 03 15 00 26 ff 00 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 02 09 52 09 53 15 00 26 ff 00 75 08 95 02 b1 02 c0 06 00 ff 09 00 a1 01 09 02 a1 00 85 aa 09 06 15 00 26 ff 00 35 00 46 ff 00 75 08 95 3f b1 02 c0 c0 05 01 09 02 a1 01 85 10 09 01 a1 00 05 01 09 00 15 00 26 ff 00 75 08 95 05 81 02 09 30 09 31 09 32 15 00 26 ff 7f 75 10 95 03 81 02 05 09 19 01 29 08 15 00 25 01 95 08 75 01 81 02 09 00 15 00 26 ff 00 75 08 95 02 81 02 c0 c0 06 00 ff 09 00 a1 01 85 40 09 00 15 00 26 ff 00 75 08 95 2e 81 02 c0",
+            input_info=(BusType.USB, 0x6615, 0x0081),
+        )
+
+
+class TestLG_043e_9aa1(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test lg_043e_9aa1",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 09 31 46 78 0a 26 38 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 25 0a 09 55 b1 02 c0 09 0e a1 01 85 03 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 04 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 75 10 95 01 15 00 26 7f 07 81 02 09 31 26 37 04 81 02 c0 c0 06 00 ff 09 01 a1 01 85 05 15 00 26 ff 00 75 08 95 19 09 01 b1 02 c0 05 14 09 2b a1 02 85 07 09 2b 15 00 25 0a 75 08 95 40 b1 02 09 4b 15 00 25 0a 75 08 95 02 91 02 c0 05 14 09 2c a1 02 85 08 09 2b 15 00 25 0a 75 08 95 05 81 02 09 4b 15 00 25 0a 75 08 95 47 91 02 c0",
+            input_info=(BusType.USB, 0x043E, 0x9AA1),
+        )
+
+
+class TestLG_043e_9aa3(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test lg_043e_9aa3",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 09 31 46 78 0a 26 38 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 25 0a 09 55 b1 02 c0 09 0e a1 01 85 03 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 04 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 75 10 95 01 15 00 26 7f 07 81 02 09 31 26 37 04 81 02 c0 c0 06 00 ff 09 01 a1 01 85 05 15 00 26 ff 00 75 08 95 19 09 01 b1 02 c0 05 14 09 2b a1 02 85 07 09 2b 15 00 25 0a 75 08 95 40 b1 02 09 4b 15 00 25 0a 75 08 95 02 91 02 c0 05 14 09 2c a1 02 85 08 09 2b 15 00 25 0a 75 08 95 05 81 02 09 4b 15 00 25 0a 75 08 95 47 91 02 c0",
+            input_info=(BusType.USB, 0x043E, 0x9AA3),
+        )
+
+
+class TestLG_1fd2_0064(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test lg_1fd2_0064",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 a1 00 05 01 26 80 07 75 10 55 0e 65 33 09 30 35 00 46 53 07 81 02 26 38 04 46 20 04 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 a1 00 05 01 26 80 07 75 10 55 0e 65 33 09 30 35 00 46 53 07 81 02 26 38 04 46 20 04 09 31 81 02 45 00 c0 c0 05 0d 09 54 95 01 75 08 81 02 85 08 09 55 95 01 25 02 b1 02 c0 09 0e a1 01 85 07 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 75 10 95 02 15 00 26 ff 7f 81 02 c0 c0",
+            input_info=(BusType.USB, 0x1FD2, 0x0064),
+        )
+
+
+class TestLumio_202e_0006(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test lumio_202e_0006",
+            rdesc="05 0d 09 04 a1 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 b0 0e 75 10 95 01 09 30 81 02 09 31 46 c2 0b 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 b0 0e 75 10 95 01 09 30 81 02 09 31 46 c2 0b 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 b0 0e 75 10 95 01 09 30 81 02 09 31 46 c2 0b 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 b0 0e 75 10 95 01 09 30 81 02 09 31 46 c2 0b 81 02 c0 05 0d 09 54 75 08 95 01 15 00 25 08 81 02 09 55 b1 02 c0",
+            input_info=(BusType.USB, 0x202E, 0x0006),
+            quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID_MINUS_ONE"),
+        )
+
+
+class TestLumio_202e_0007(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test lumio_202e_0007",
+            rdesc="05 0d 09 04 a1 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 0a 81 03 05 01 26 ff 7f 65 11 55 0e 46 ba 0e 75 10 95 01 09 30 81 02 09 31 46 ea 0b 81 02 05 0d 09 51 75 10 95 01 81 02 09 55 15 00 25 08 75 08 95 01 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x202E, 0x0007),
+            quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID_MINUS_ONE"),
+        )
+
+
+class TestNexio_1870_0100(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test nexio_1870_0100",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 02 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 40 81 00 19 01 29 40 91 00 c0",
+            input_info=(BusType.USB, 0x1870, 0x0100),
+        )
+
+
+class TestNexio_1870_010d(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test nexio_1870_010d",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0",
+            input_info=(BusType.USB, 0x1870, 0x010D),
+        )
+
+
+class TestNexio_1870_0119(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test nexio_1870_0119",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0",
+            input_info=(BusType.USB, 0x1870, 0x0119),
+        )
+
+
+class TestPenmount_14e1_3500(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test penmount_14e1_3500",
+            rdesc="05 0d 09 04 a1 01 09 22 a1 00 09 51 15 00 25 0f 75 04 95 01 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 81 01 05 01 75 10 95 01 09 30 26 ff 07 81 02 09 31 26 ff 07 81 02 05 0d 09 55 75 08 95 05 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x14E1, 0x3500),
+            quirks=("VALID_IS_CONFIDENCE",),
+            max_contacts=10,
+        )
+
+
+class TestPixart_093a_8002(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test pixart_093a_8002",
+            rdesc="05 01 09 02 a1 01 85 0d 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 03 05 01 55 0e 65 11 75 10 95 01 35 00 46 5a 14 26 ff 7f 09 30 81 22 46 72 0b 26 ff 7f 09 31 81 22 95 08 75 08 81 03 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 5a 14 26 ff 7f 81 02 09 31 46 72 0b 26 ff 7f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 5a 14 26 ff 7f 81 02 46 72 0b 26 ff 7f 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 c0 05 0d 09 0e a1 01 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 3f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 09 01 96 c3 03 85 0e b1 02 09 01 96 ff 03 85 0f b1 02 09 01 96 83 03 85 10 b1 02 09 01 96 93 00 85 11 b1 02 09 01 96 ff 03 85 12 b1 02 05 0d 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 85 0c b1 02 c0 c0",
+            input_info=(BusType.USB, 0x093A, 0x8002),
+            quirks=("VALID_IS_INRANGE", "SLOT_IS_CONTACTNUMBER"),
+        )
+
+
+class TestPqlabs_1ef1_0001(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test pqlabs_1ef1_0001",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 02 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 3f 81 02 c0 c0 05 8c 09 07 a1 01 85 11 09 02 15 00 26 ff 00 75 08 95 3f 81 02 85 10 09 10 91 02 c0",
+            input_info=(BusType.USB, 0x1EF1, 0x0001),
+        )
+
+
+class TestQuanta_0408_3000(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test quanta_0408_3000",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 e3 13 26 7f 07 81 02 09 31 46 2f 0b 26 37 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 e3 13 26 7f 07 81 02 46 2f 0b 26 37 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 06 00 ff 09 01 26 ff 00 75 08 95 2f 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 c0",
+            input_info=(BusType.USB, 0x0408, 0x3000),
+        )
+
+
+class TestQuanta_0408_3001(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test quanta_0408_3001",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 09 31 46 78 0a 26 38 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 0f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 c0",
+            input_info=(BusType.USB, 0x0408, 0x3001),
+            quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID"),
+        )
+
+
+class TestQuanta_0408_3008_1(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test quanta_0408_3008_1",
+            rdesc="05 01 09 02 a1 01 85 0d 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 03 05 01 55 0e 65 11 75 10 95 01 35 00 46 4c 11 26 7f 07 09 30 81 22 46 bb 09 26 37 04 09 31 81 22 95 08 75 08 81 03 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 4c 11 26 7f 07 81 02 09 31 46 bb 09 26 37 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 4c 11 26 7f 07 81 02 46 bb 09 26 37 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 c0 05 0d 09 0e a1 01 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 3f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 09 01 96 c3 03 85 0e b1 02 09 01 96 ff 03 85 0f b1 02 09 01 96 83 03 85 10 b1 02 09 01 96 93 00 85 11 b1 02 09 01 96 ff 03 85 12 b1 02 05 0d 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 85 0c b1 02 c0 c0",
+            input_info=(BusType.USB, 0x0408, 0x3008),
+        )
+
+
+class TestQuanta_0408_3008(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test quanta_0408_3008",
+            rdesc="05 01 09 02 a1 01 85 0d 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 03 05 01 55 0e 65 11 75 10 95 01 35 00 46 98 12 26 7f 07 09 30 81 22 46 78 0a 26 37 04 09 31 81 22 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 7f 07 81 02 09 31 46 78 0a 26 37 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 7f 07 81 02 46 78 0a 26 37 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 c0 05 0d 09 0e a1 01 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 3f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 09 01 96 c3 03 85 0e b1 02 09 01 96 ff 03 85 0f b1 02 09 01 96 83 03 85 10 b1 02 09 01 96 93 00 85 11 b1 02 09 01 96 ff 03 85 12 b1 02 05 0d 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 85 0c b1 02 c0 c0",
+            input_info=(BusType.USB, 0x0408, 0x3008),
+        )
+
+
+class TestRafi_05bd_0107(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test rafi_05bd_0107",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 65 00 55 00 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 09 81 02 05 0d 85 02 95 01 75 08 09 55 25 0a b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 05 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 65 11 55 0f 09 30 26 ff 03 35 00 46 9c 01 75 10 95 01 81 02 09 31 26 ff 03 35 00 46 e7 00 81 02 c0 c0",
+            input_info=(BusType.USB, 0x05BD, 0x0107),
+        )
+
+
+class TestRndplus_2512_5003(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test rndplus_2512_5003",
+            rdesc="05 0d 09 04 a1 01 85 02 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 85 08 09 55 b1 02 c0 09 0e a1 01 85 07 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 16 00 00 26 ff 3f 36 00 00 46 ff 3f 66 00 00 75 10 95 02 81 62 c0 c0",
+            input_info=(BusType.USB, 0x2512, 0x5003),
+        )
+
+
+class TestRndplus_2512_5004(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test rndplus_2512_5004",
+            rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 85 05 09 55 b1 02 c0 09 0e a1 01 85 06 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 16 00 00 26 ff 3f 36 00 00 46 ff 3f 66 00 00 75 10 95 02 81 62 c0 c0 06 00 ff 09 01 a1 01 85 01 09 01 15 00 26 ff 00 75 08 95 3f 82 00 01 85 02 09 01 15 00 26 ff 00 75 08 95 3f 92 00 01 c0",
+            input_info=(BusType.USB, 0x2512, 0x5004),
+        )
+
+
+class TestSitronix_1403_5001(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test sitronix_1403_5001",
+            rdesc="05 0d 09 04 a1 01 85 01 09 54 95 01 75 08 81 02 09 22 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 05 01 26 90 04 75 0c 95 01 55 0f 65 11 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 04 09 48 09 49 81 02 c0 85 02 09 55 26 ff 00 75 08 95 01 b1 02 09 04 15 00 25 ff 75 08 95 07 91 02 c0 09 0e a1 01 85 03 09 23 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x1403, 0x5001),
+            max_contacts=10,
+        )
+
+
+class TestSmart_0b8c_0092(BaseTest.TestMultitouch):
+    def create_device(self):
+        return SmartTechDigitizer(
+            "uhid test smart_0b8c_0092", input_info=(BusType.USB, 0x0B8C, 0x0092)
+        )
+
+
+class TestStantum_1f87_0002(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test stantum_1f87_0002",
+            rdesc="05 0d 09 04 a1 01 85 03 05 0d 09 54 95 01 75 08 81 02 06 00 ff 75 02 09 01 81 01 75 0e 09 02 81 02 05 0d 09 22 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 85 08 05 0d 09 55 95 01 75 08 25 0a b1 02 c0",
+            input_info=(BusType.USB, 0x1F87, 0x0002),
+        )
+
+
+class TestTopseed_1784_0016(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test topseed_1784_0016",
+            rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 04 75 10 55 00 65 00 09 30 35 00 46 ff 04 81 02 09 31 46 ff 04 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 09 55 b1 02 c0 05 0c 09 01 a1 01 85 03 a1 02 09 b5 15 00 25 01 75 01 95 01 81 02 09 b6 81 02 09 b7 81 02 09 cd 81 02 09 e2 81 02 09 e9 81 02 09 ea 81 02 05 01 09 82 81 02 c0 c0",
+            input_info=(BusType.USB, 0x1784, 0x0016),
+            max_contacts=2,
+        )
+
+
+class TestTpv_25aa_8883(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test tpv_25aa_8883",
+            rdesc="05 01 09 02 a1 01 85 0d 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 05 0d 09 32 95 01 75 01 81 02 95 01 75 05 81 03 05 01 55 0e 65 11 75 10 95 01 35 00 46 98 12 26 7f 07 09 30 81 22 46 78 0a 26 37 04 09 31 81 22 35 00 45 00 15 81 25 7f 75 08 95 01 09 38 81 06 09 00 75 08 95 07 81 03 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 7f 07 81 02 09 31 46 78 0a 26 37 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 7f 07 81 02 46 78 0a 26 37 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 c0 05 0d 09 0e a1 01 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 3f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 09 01 96 c3 03 85 0e b1 02 09 01 96 ff 03 85 0f b1 02 09 01 96 83 03 85 10 b1 02 09 01 96 93 00 85 11 b1 02 09 01 96 ff 03 85 12 b1 02 05 0d 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 85 0c b1 02 c0 c0",
+            input_info=(BusType.USB, 0x25AA, 0x8883),
+        )
+
+
+class TestTrs_star_238f_0001(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test trs-star_238f_0001",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 95 01 81 03 09 37 95 01 81 03 95 01 81 03 15 00 25 0f 75 04 09 51 95 01 81 02 09 54 95 01 81 02 09 55 95 01 81 02 05 01 26 ff 03 15 00 75 10 65 00 09 30 95 01 81 02 09 31 81 02 c0 05 0d 09 0e 85 02 09 23 a1 02 15 00 25 0a 09 52 75 08 95 01 b1 02 09 53 95 01 b1 02 09 55 95 01 b1 02 c0 c0",
+            input_info=(BusType.USB, 0x238F, 0x0001),
+        )
+
+
+class TestUnitec_227d_0103(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test unitec_227d_0103",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 03 09 55 25 05 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 53 15 00 25 05 75 08 95 01 b1 02 c0",
+            input_info=(BusType.USB, 0x227D, 0x0103),
+        )
+
+
+class TestZytronic_14c8_0005(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test zytronic_14c8_0005",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 95 01 81 02 95 06 81 01 05 01 26 00 10 75 10 95 01 65 00 09 30 81 02 09 31 46 00 10 81 02 05 0d 09 51 26 ff 00 75 08 95 01 81 02 c0 85 02 09 55 15 00 25 08 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 03 a1 02 09 23 09 52 09 53 15 00 25 08 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 15 00 26 00 10 35 00 46 00 10 65 00 75 10 95 02 81 62 c0 c0 06 00 ff 09 01 a1 01 85 05 09 00 15 00 26 ff 00 75 08 95 3f b1 02 c0 06 00 ff 09 01 a1 01 85 06 09 00 15 00 26 ff 00 75 08 95 3f 81 02 c0",
+            input_info=(BusType.USB, 0x14C8, 0x0005),
+        )
+
+
+class TestZytronic_14c8_0006(BaseTest.TestMultitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test zytronic_14c8_0006",
+            rdesc="05 0d 09 04 a1 01 85 01 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 54 95 01 75 08 15 00 25 3c 81 02 05 0d 85 02 09 55 95 01 75 08 15 00 25 3c b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 15 00 26 00 10 35 00 46 00 10 65 00 75 10 95 02 81 62 c0 c0 06 00 ff 09 01 a1 01 85 05 09 00 15 00 26 ff 00 75 08 95 3f b1 02 c0 06 00 ff 09 01 a1 01 85 06 09 00 15 00 26 ff 00 75 08 95 3f 81 02 c0",
+            input_info=(BusType.USB, 0x14C8, 0x0006),
+        )
+
+
+################################################################################
+#
+# Windows 8 compatible devices
+#
+################################################################################
+
+
+class TestMinWin8TSParallelTriple(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return MinWin8TSParallel(3)
+
+
+class TestMinWin8TSParallel(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return MinWin8TSParallel(10)
+
+
+class TestMinWin8TSHybrid(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return MinWin8TSHybrid()
+
+
+class TestWin8TSConfidence(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Win8TSConfidence(5)
+
+    @pytest.mark.skip_if_uhdev(
+        lambda uhdev: "Confidence" not in uhdev.fields,
+        "Device not compatible, missing Confidence usage",
+    )
+    def test_mt_confidence_bad_release(self):
+        """Check for the validity of the confidence bit.
+        When a contact is marked as not confident, it should be detected
+        as a palm from the kernel POV and released.
+
+        Note: if the kernel exports ABS_MT_TOOL_TYPE, it shouldn't release
+        the touch but instead convert it to ABS_MT_TOOL_PALM."""
+        uhdev = self.uhdev
+        evdev = uhdev.get_evdev()
+
+        t0 = Touch(1, 150, 200)
+        r = uhdev.event([t0])
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+
+        t0.confidence = False
+        t0.tipswitch = False
+        r = uhdev.event([t0])
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+
+        if evdev.absinfo[libevdev.EV_ABS.ABS_MT_TOOL_TYPE] is not None:
+            # the kernel exports MT_TOOL_PALM
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TOOL_TYPE, 2) in events
+
+        assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+        assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+
+class TestElanXPS9360(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test ElanXPS9360",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0",
+        )
+
+
+class TestTouchpadXPS9360(BaseTest.TestPTP):
+    def create_device(self):
+        return PTP(
+            "uhid test TouchpadXPS9360",
+            max_contacts=5,
+            rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 03 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 3d 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 3d 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 03 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0",
+        )
+
+
+class TestSurfaceBook2(BaseTest.TestPTP):
+    def create_device(self):
+        return PTP(
+            "uhid test SurfaceBook2",
+            max_contacts=5,
+            rdesc="05 01 09 06 A1 01 85 01 14 25 01 75 01 95 08 05 07 19 E0 29 E7 81 02 75 08 95 0A 18 29 91 26 FF 00 80 05 0C 0A C0 02 A1 02 1A C1 02 2A C6 02 95 06 B1 03 C0 05 08 19 01 29 03 75 01 95 03 25 01 91 02 95 05 91 01 C0 05 01 09 02 A1 01 85 02 05 09 19 01 29 05 81 02 95 01 75 03 81 03 15 81 25 7F 75 08 95 02 05 01 09 30 09 31 81 06 A1 02 09 48 14 25 01 35 01 45 10 75 02 95 01 A4 B1 02 09 38 15 81 25 7F 34 44 75 08 81 06 C0 A1 02 09 48 B4 B1 02 34 44 75 04 B1 03 05 0C 0A 38 02 15 81 25 7F 75 08 81 06 C0 C0 05 0C 09 01 A1 01 85 03 75 10 14 26 FF 03 18 2A FF 03 80 C0 06 05 FF 09 01 A1 01 85 0D 25 FF 95 02 75 08 09 20 81 02 09 22 91 02 15 81 25 7F 95 20 75 08 09 21 81 02 09 23 91 02 C0 09 02 A1 01 85 0C 14 25 FF 95 01 08 91 02 C0 05 0D 09 05 A1 01 85 04 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 44 54 64 C0 05 0D 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 44 54 64 C0 05 0D 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 C0 05 0D 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 44 54 64 C0 05 0D 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 C0 05 0D 55 0C 66 01 10 47 FF FF 00 00 27 FF FF 00 00 09 56 81 02 09 54 25 7F 75 08 81 02 05 09 09 01 25 01 75 01 81 02 95 07 81 03 05 0D 85 04 09 55 09 59 75 04 95 02 25 0F B1 02 06 00 FF 09 C6 85 05 14 25 08 75 08 95 01 B1 02 09 C7 26 FF 00 75 08 95 20 B1 02 C0 05 0D 09 0E A1 01 85 07 09 22 A1 02 09 52 14 25 0A 75 08 95 01 B1 02 C0 09 22 A0 85 08 09 57 09 58 75 01 95 02 25 01 B1 02 95 06 B1 03 C0 C0 06 07 FF 09 01 A1 01 85 0A 09 02 26 FF 00 75 08 95 14 91 02 85 09 09 03 91 02 85 0A 09 04 95 26 81 02 85 09 09 05 81 02 85 09 09 06 95 01 B1 02 85 0B 09 07 B1 02 C0 06 05 FF 09 04 A1 01 85 0E 09 31 91 02 09 31 81 03 09 30 91 02 09 30 81 02 95 39 09 32 92 02 01 09 32 82 02 01 C0 06 05 FF 09 50 A1 01 85 20 14 25 FF 75 08 95 3C 09 60 82 02 01 09 61 92 02 01 09 62 B2 02 01 85 21 09 63 82 02 01 09 64 92 02 01 09 65 B2 02 01 85 22 25 FF 75 20 95 04 19 66 29 69 81 02 19 6A 29 6D 91 02 19 6E 29 71 B1 02 85 23 19 72 29 75 81 02 19 76 29 79 91 02 19 7A 29 7D B1 02 85 24 19 7E 29 81 81 02 19 82 29 85 91 02 19 86 29 89 B1 02 85 25 19 8A 29 8D 81 02 19 8E 29 91 91 02 19 92 29 95 B1 02 85 26 19 96 29 99 81 02 19 9A 29 9D 91 02 19 9E 29 A1 B1 02 85 27 19 A2 29 A5 81 02 19 A6 29 A9 91 02 19 AA 29 AD B1 02 85 28 19 AE 29 B1 81 02 19 B2 29 B5 91 02 19 B6 29 B9 B1 02 85 29 19 BA 29 BD 81 02 19 BE 29 C1 91 02 19 C2 29 C5 B1 02 C0 06 00 FF 0A 00 F9 A1 01 85 32 75 10 95 02 14 27 FF FF 00 00 0A 01 F9 B1 02 75 20 95 01 25 FF 0A 02 F9 B1 02 75 08 95 08 26 FF 00 0A 03 F9 B2 02 01 95 10 0A 04 F9 B2 02 01 0A 05 F9 B2 02 01 95 01 75 10 27 FF FF 00 00 0A 06 F9 81 02 C0",
+        )
+
+
+class Test3m_0596_051c(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test 3m_0596_051c",
+            rdesc="05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 09 01 95 01 75 01 15 00 25 01 81 02 95 07 75 01 81 03 95 01 75 08 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 ff 7f 95 02 75 10 81 02 c0 a1 02 15 00 26 ff 00 09 01 95 39 75 08 81 03 c0 c0 05 0d 09 0e a1 01 85 11 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 09 04 a1 01 85 13 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 14 81 02 05 0d 55 0c 66 01 10 35 00 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 05 0d 09 55 85 12 15 00 25 14 75 08 95 01 b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 06 00 ff 15 00 26 ff 00 85 03 09 01 75 08 95 07 b1 02 85 04 09 01 75 08 95 17 b1 02 85 05 09 01 75 08 95 47 b1 02 85 06 09 01 75 08 95 07 b1 02 85 73 09 01 75 08 95 07 b1 02 85 08 09 01 75 08 95 07 b1 02 85 09 09 01 75 08 95 3f b1 02 85 0f 09 01 75 08 96 07 02 b1 02 c0",
+        )
+
+
+class Testadvanced_silicon_04e8_2084(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test advanced_silicon_04e8_2084",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 b1 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0",
+        )
+
+
+class Testadvanced_silicon_2149_2306(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test advanced_silicon_2149_2306",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 81 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0",
+        )
+
+
+class Testadvanced_silicon_2149_230a(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test advanced_silicon_2149_230a",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 81 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0",
+        )
+
+
+class Testadvanced_silicon_2149_231c(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test advanced_silicon_2149_231c",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 b1 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0",
+        )
+
+
+class Testadvanced_silicon_2149_2703(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test advanced_silicon_2149_2703",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 81 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0",
+        )
+
+
+class Testadvanced_silicon_2149_270b(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test advanced_silicon_2149_270b",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 b1 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0",
+        )
+
+
+class Testadvanced_silicon_2575_0204(BaseTest.TestWin8Multitouch):
+    """found on the Dell Canvas 27"""
+
+    def create_device(self):
+        return Digitizer(
+            "uhid test advanced_silicon_2575_0204",
+            rdesc="05 0d 09 04 a1 01 85 01 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 42 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 c0 05 01 09 0e a1 01 85 05 05 01 09 08 a1 00 09 30 55 0e 65 11 15 00 26 ff 7f 35 00 46 4f 17 75 10 95 01 81 42 09 31 46 1d 0d 81 42 06 00 ff 09 01 75 20 81 03 05 01 09 37 55 00 65 14 16 98 fe 26 68 01 36 98 fe 46 68 01 75 0f 81 06 05 09 09 01 65 00 15 00 25 01 35 00 45 00 75 01 81 02 05 0d 09 42 81 02 09 51 75 07 25 7f 81 02 05 0d 09 48 55 0e 65 11 15 00 26 ff 7f 35 00 46 ff 7f 75 10 81 02 09 49 81 02 09 3f 55 00 65 14 15 00 26 67 01 35 00 46 67 01 81 0a c0 65 00 35 00 45 00 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 05 75 08 09 54 81 02 85 47 09 55 25 05 b1 02 c0 06 00 ff 09 04 a1 01 85 f0 09 01 75 08 95 04 b1 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 85 c0 09 01 95 03 b1 02 85 c2 09 01 95 0f b1 02 85 c4 09 01 95 3e b1 02 85 c5 09 01 95 7e b1 02 85 c6 09 01 95 fe b1 02 85 c8 09 01 96 fe 03 b1 02 85 0a 09 01 95 3f b1 02 c0",
+        )
+
+
+class Testadvanced_silicon_2619_5610(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test advanced_silicon_2619_5610",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 81 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 c0",
+        )
+
+
+class Testatmel_03eb_8409(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test atmel_03eb_8409",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 00 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 46 18 06 26 77 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0",
+        )
+
+
+class Testatmel_03eb_840b(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test atmel_03eb_840b",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 00 0a 26 ff 0f 09 30 81 02 46 a0 05 26 ff 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0",
+        )
+
+
+class Testdell_044e_1220(BaseTest.TestPTP):
+    def create_device(self):
+        return PTP(
+            "uhid test dell_044e_1220",
+            type="pressurepad",
+            rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 95 05 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 09 38 95 01 81 06 05 0c 0a 38 02 81 06 c0 c0 05 0d 09 05 a1 01 85 08 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 af 04 75 10 55 0e 65 11 09 30 35 00 46 e8 03 95 01 81 02 26 7b 02 46 12 02 09 31 81 02 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 05 0d 09 56 81 02 09 54 25 05 95 01 75 08 81 02 05 09 19 01 29 03 25 01 75 01 95 03 81 02 95 05 81 03 05 0d 85 09 09 55 75 08 95 01 25 05 b1 02 06 00 ff 85 0a 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 01 ff 09 01 a1 01 85 03 09 01 15 00 26 ff 00 95 1b 81 02 85 04 09 02 95 50 81 02 85 05 09 03 95 07 b1 02 85 06 09 04 81 02 c0 06 02 ff 09 01 a1 01 85 07 09 02 95 86 75 08 b1 02 c0 05 0d 09 0e a1 01 85 0b 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 0c 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0",
+        )
+
+
+class Testdell_06cb_75db(BaseTest.TestPTP):
+    def create_device(self):
+        return PTP(
+            "uhid test dell_06cb_75db",
+            max_contacts=3,
+            rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 03 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c8 04 75 10 55 0e 65 11 09 30 35 00 46 fb 03 95 01 81 02 46 6c 02 26 e8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c8 04 75 10 55 0e 65 11 09 30 35 00 46 fb 03 95 01 81 02 46 6c 02 26 e8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c8 04 75 10 55 0e 65 11 09 30 35 00 46 fb 03 95 01 81 02 46 6c 02 26 e8 02 09 31 81 02 05 0d c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 1a 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 1a 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 01 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0",
+        )
+
+
+class Testegalax_capacitive_0eef_790a(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test egalax_capacitive_0eef_790a",
+            max_contacts=10,
+            rdesc="05 0d 09 04 a1 01 85 06 05 0d 09 54 75 08 15 00 25 0c 95 01 81 02 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 17 00 00 00 00 27 ff ff ff 7f 75 20 95 01 55 00 65 00 09 56 81 02 09 55 09 53 75 08 95 02 26 ff 00 b1 02 06 00 ff 09 c5 85 07 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 01 a1 01 85 01 09 01 a1 02 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 0e a1 01 85 05 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+        )
+
+
+class Testelan_04f3_000a(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test elan_04f3_000a",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0",
+        )
+
+
+class Testelan_04f3_000c(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test elan_04f3_000c",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0",
+        )
+
+
+class Testelan_04f3_010c(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test elan_04f3_010c",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0",
+        )
+
+
+class Testelan_04f3_0125(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test elan_04f3_0125",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0",
+        )
+
+
+class Testelan_04f3_016f(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test elan_04f3_016f",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0",
+        )
+
+
+class Testelan_04f3_0732(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test elan_04f3_0732",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff 00 00 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 25 ff 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0",
+        )
+
+
+class Testelan_04f3_200a(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test elan_04f3_200a",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff 00 00 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 0e 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0",
+        )
+
+
+class Testelan_04f3_300b(BaseTest.TestPTP):
+    def create_device(self):
+        return PTP(
+            "uhid test elan_04f3_300b",
+            max_contacts=3,
+            rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 81 06 05 0c 0a 38 02 95 01 81 06 75 08 95 03 81 03 c0 06 00 ff 85 0d 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0c 09 c6 96 76 02 75 08 b1 02 85 0b 09 c7 95 42 75 08 b1 02 09 01 85 5d 95 1f 75 08 81 06 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 02 25 02 09 51 81 02 75 01 95 04 81 03 05 01 15 00 26 a7 0c 75 10 55 0e 65 13 09 30 35 00 46 9d 01 95 01 81 02 46 25 01 26 2b 09 26 2b 09 09 31 81 02 05 0d 15 00 25 64 95 03 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 02 09 55 09 59 75 04 95 02 25 0f b1 02 85 07 09 60 75 01 95 01 15 00 25 01 b1 02 95 0f b1 03 06 00 ff 06 00 ff 85 06 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 03 09 22 a1 00 09 52 15 00 25 0a 75 08 95 02 b1 02 c0 09 22 a1 00 85 05 09 57 09 58 15 00 75 01 95 02 25 03 b1 02 95 0e b1 03 c0 c0",
+        )
+
+
+class Testelan_04f3_3045(BaseTest.TestPTP):
+    def create_device(self):
+        return PTP(
+            "uhid test elan_04f3_3045",
+            rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 81 06 05 0c 0a 38 02 95 01 81 06 75 08 95 03 81 03 c0 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 75 01 95 02 81 03 95 01 75 04 25 0f 09 51 81 02 05 01 15 00 26 80 0c 75 10 55 0e 65 13 09 30 35 00 46 90 01 95 01 81 02 46 13 01 26 96 08 26 96 08 09 31 81 02 05 0d 15 00 25 64 95 03 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 09 c5 75 08 95 04 81 03 05 0d 85 02 09 55 09 59 75 04 95 02 25 0f b1 02 85 07 09 60 75 01 95 01 15 00 25 01 b1 02 95 0f b1 03 06 00 ff 06 00 ff 85 06 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 85 0d 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0c 09 c6 96 8a 02 75 08 b1 02 85 0b 09 c7 95 80 75 08 b1 02 c0 05 0d 09 0e a1 01 85 03 09 22 a1 00 09 52 15 00 25 0a 75 08 95 02 b1 02 c0 09 22 a1 00 85 05 09 57 09 58 15 00 75 01 95 02 25 03 b1 02 95 0e b1 03 c0 c0",
+        )
+
+
+class Testelan_04f3_313a(BaseTest.TestPTP):
+    def create_device(self):
+        return PTP(
+            "uhid test elan_04f3_313a",
+            type="touchpad",
+            input_info=(BusType.I2C, 0x04F3, 0x313A),
+            rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 95 05 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 75 08 95 05 81 03 c0 06 00 ff 09 01 85 0e 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0a 09 c6 15 00 26 ff 00 75 08 95 04 b1 02 c0 06 00 ff 09 01 a1 01 85 5c 09 01 95 0b 75 08 81 06 85 0d 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0c 09 c6 96 80 03 75 08 b1 02 85 0b 09 c7 95 82 75 08 b1 02 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 05 09 09 02 09 03 15 00 25 01 75 01 95 02 81 02 05 0d 95 01 75 04 25 0f 09 51 81 02 05 01 15 00 26 d7 0e 75 10 55 0d 65 11 09 30 35 00 46 44 2f 95 01 81 02 46 12 16 26 eb 06 26 eb 06 09 31 81 02 05 0d 15 00 25 64 95 03 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 25 01 75 01 95 08 81 03 09 c5 75 08 95 02 81 03 05 0d 85 02 09 55 09 59 75 04 95 02 25 0f b1 02 85 07 09 60 75 01 95 01 15 00 25 01 b1 02 95 0f b1 03 06 00 ff 06 00 ff 85 06 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 03 09 22 a1 00 09 52 15 00 25 0a 75 10 95 01 b1 02 c0 09 22 a1 00 85 05 09 57 09 58 75 01 95 02 25 01 b1 02 95 0e b1 03 c0 c0 05 01 09 02 a1 01 85 2a 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 95 05 81 03 05 01 09 30 09 31 15 81 25 7f 35 81 45 7f 55 00 65 13 75 08 95 02 81 06 75 08 95 05 81 03 c0 c0",
+        )
+
+
+class Testelo_04e7_0080(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test elo_04e7_0080",
+            rdesc="05 0d 09 04 a1 01 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 75 10 09 30 46 7c 24 81 02 09 31 46 96 14 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 75 10 09 30 46 7c 24 81 02 09 31 46 96 14 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 75 10 09 30 46 7c 24 81 02 09 31 46 96 14 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 75 10 09 30 46 7c 24 81 02 09 31 46 96 14 81 02 c0 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 75 08 95 01 15 00 25 08 81 02 09 55 b1 02 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0",
+        )
+
+
+class Testilitek_222a_0015(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test ilitek_222a_0015",
+            rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 02 09 55 25 0a b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 00 ff 09 01 a1 01 09 01 85 03 15 00 26 ff 00 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0",
+        )
+
+
+class Testilitek_222a_001c(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test ilitek_222a_001c",
+            rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 02 09 55 25 0a b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 00 ff 09 01 a1 01 09 01 85 03 15 00 26 ff 00 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0",
+        )
+
+
+class Testite_06cb_2968(BaseTest.TestPTP):
+    def create_device(self):
+        return PTP(
+            "uhid test ite_06cb_2968",
+            rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 03 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 1b 04 75 10 55 0e 65 11 09 30 35 00 46 6c 03 95 01 81 02 46 db 01 26 3b 02 09 31 81 02 05 0d c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 1a 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 1a 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 01 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0",
+            max_contacts=5,
+            input_info=(0x3, 0x06CB, 0x2968),
+        )
+
+
+class Testn_trig_1b96_0c01(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test n_trig_1b96_0c01",
+            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+        )
+
+
+class Testn_trig_1b96_0c03(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test n_trig_1b96_0c03",
+            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+        )
+
+
+class Testn_trig_1b96_0f00(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test n_trig_1b96_0f00",
+            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+        )
+
+
+class Testn_trig_1b96_0f04(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test n_trig_1b96_0f04",
+            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+        )
+
+
+class Testn_trig_1b96_1000(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test n_trig_1b96_1000",
+            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+        )
+
+
+class Testsharp_04dd_9681(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test sharp_04dd_9681",
+            rdesc="06 00 ff 09 01 a1 01 75 08 26 ff 00 15 00 85 06 95 3f 09 01 91 02 85 05 95 3f 09 01 81 02 c0 05 0d 09 04 a1 01 85 81 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 56 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 81 02 09 54 95 01 75 08 15 00 25 0a 81 02 85 84 09 55 b1 02 85 87 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 09 0e a1 01 85 83 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 80 05 09 19 01 29 01 15 00 25 01 95 01 75 01 81 02 95 01 75 07 81 01 05 01 65 11 55 0f 09 30 26 80 07 35 00 46 66 00 75 10 95 01 81 02 09 31 26 38 04 35 00 46 4d 00 81 02 c0 c0",
+        )
+
+
+class Testsipodev_0603_0002(BaseTest.TestPTP):
+    def create_device(self):
+        return PTP(
+            "uhid test sipodev_0603_0002",
+            type="clickpad",
+            rdesc="05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 02 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 80 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 75 01 95 02 81 03 95 01 75 04 25 05 09 51 81 02 05 01 15 00 26 44 0a 75 0c 55 0e 65 11 09 30 35 00 46 ac 03 95 01 81 02 46 fe 01 26 34 05 75 0c 09 31 81 02 05 0d c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 0a 95 01 75 04 81 02 75 01 95 03 81 03 05 09 09 01 25 01 75 01 95 01 81 02 05 0d 85 0a 09 55 09 59 75 04 95 02 25 0f b1 02 85 0b 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 09 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 06 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 07 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 05 01 09 0c a1 01 85 08 15 00 25 01 09 c6 75 01 95 01 81 06 75 07 81 03 c0 05 01 09 80 a1 01 85 01 15 00 25 01 75 01 0a 81 00 0a 82 00 0a 83 00 95 03 81 06 95 05 81 01 c0 06 0c 00 09 01 a1 01 85 02 25 01 15 00 75 01 0a b5 00 0a b6 00 0a b7 00 0a cd 00 0a e2 00 0a a2 00 0a e9 00 0a ea 00 95 08 81 02 0a 83 01 0a 6f 00 0a 70 00 0a 88 01 0a 8a 01 0a 92 01 0a a8 02 0a 24 02 95 08 81 02 0a 21 02 0a 23 02 0a 96 01 0a 25 02 0a 26 02 0a 27 02 0a 23 02 0a b1 02 95 08 81 02 c0 06 00 ff 09 01 a1 01 85 05 15 00 26 ff 00 19 01 29 02 75 08 95 05 b1 02 c0",
+        )
+
+
+class Testsynaptics_06cb_1d10(BaseTest.TestWin8Multitouch):
+    def create_device(self):
+        return Digitizer(
+            "uhid test synaptics_06cb_1d10",
+            rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 75 08 95 02 15 81 25 7f 35 81 45 7f 55 0e 65 11 81 06 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 95 01 75 08 15 00 25 0f 81 02 85 08 09 55 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 3f 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 05 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 3d 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 01 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 01 b1 02 c0",
+        )
+
+
+class Testsynaptics_06cb_ce08(BaseTest.TestPTP):
+    def create_device(self):
+        return PTP(
+            "uhid test synaptics_06cb_ce08",
+            max_contacts=5,
+            physical="Vendor Usage 1",
+            input_info=(BusType.I2C, 0x06CB, 0xCE08),
+            rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 01 09 02 a1 01 85 18 09 01 a1 00 05 09 19 01 29 03 46 00 00 15 00 25 01 75 01 95 03 81 02 95 05 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 06 00 ff 09 02 a1 01 85 20 09 01 a1 00 09 03 15 00 26 ff 00 35 00 46 ff 00 75 08 95 05 81 02 c0 c0 05 0d 09 05 a1 01 85 03 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 45 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 45 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 03 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0",
+        )
diff --git a/tools/testing/selftests/hid/tests/test_sony.py b/tools/testing/selftests/hid/tests/test_sony.py
new file mode 100644 (file)
index 0000000..7e52c28
--- /dev/null
@@ -0,0 +1,342 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2020 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2020 Red Hat, Inc.
+#
+
+from .base import application_matches
+from .test_gamepad import BaseTest
+from hidtools.device.sony_gamepad import (
+    PS3Controller,
+    PS4ControllerBluetooth,
+    PS4ControllerUSB,
+    PS5ControllerBluetooth,
+    PS5ControllerUSB,
+    PSTouchPoint,
+)
+from hidtools.util import BusType
+
+import libevdev
+import logging
+import pytest
+
+logger = logging.getLogger("hidtools.test.sony")
+
+PS3_MODULE = ("sony", "hid_sony")
+PS4_MODULE = ("playstation", "hid_playstation")
+PS5_MODULE = ("playstation", "hid_playstation")
+
+
+class SonyBaseTest:
+    class SonyTest(BaseTest.TestGamepad):
+        pass
+
+    class SonyPS4ControllerTest(SonyTest):
+        kernel_modules = [PS4_MODULE]
+
+        def test_accelerometer(self):
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev("Accelerometer")
+
+            for x in range(-32000, 32000, 4000):
+                r = uhdev.event(accel=(x, None, None))
+                events = uhdev.next_sync_events("Accelerometer")
+                self.debug_reports(r, uhdev, events)
+
+                assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X) in events
+                value = evdev.value[libevdev.EV_ABS.ABS_X]
+                # Check against range due to small loss in precision due
+                # to inverse calibration, followed by calibration by hid-sony.
+                assert x - 1 <= value <= x + 1
+
+            for y in range(-32000, 32000, 4000):
+                r = uhdev.event(accel=(None, y, None))
+                events = uhdev.next_sync_events("Accelerometer")
+                self.debug_reports(r, uhdev, events)
+
+                assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y) in events
+                value = evdev.value[libevdev.EV_ABS.ABS_Y]
+                assert y - 1 <= value <= y + 1
+
+            for z in range(-32000, 32000, 4000):
+                r = uhdev.event(accel=(None, None, z))
+                events = uhdev.next_sync_events("Accelerometer")
+                self.debug_reports(r, uhdev, events)
+
+                assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Z) in events
+                value = evdev.value[libevdev.EV_ABS.ABS_Z]
+                assert z - 1 <= value <= z + 1
+
+        def test_gyroscope(self):
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev("Accelerometer")
+
+            for rx in range(-2000000, 2000000, 200000):
+                r = uhdev.event(gyro=(rx, None, None))
+                events = uhdev.next_sync_events("Accelerometer")
+                self.debug_reports(r, uhdev, events)
+
+                assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RX) in events
+                value = evdev.value[libevdev.EV_ABS.ABS_RX]
+                # Sensor internal value is 16-bit, but calibrated is 22-bit, so
+                # 6-bit (64) difference, so allow a range of +/- 64.
+                assert rx - 64 <= value <= rx + 64
+
+            for ry in range(-2000000, 2000000, 200000):
+                r = uhdev.event(gyro=(None, ry, None))
+                events = uhdev.next_sync_events("Accelerometer")
+                self.debug_reports(r, uhdev, events)
+
+                assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RY) in events
+                value = evdev.value[libevdev.EV_ABS.ABS_RY]
+                assert ry - 64 <= value <= ry + 64
+
+            for rz in range(-2000000, 2000000, 200000):
+                r = uhdev.event(gyro=(None, None, rz))
+                events = uhdev.next_sync_events("Accelerometer")
+                self.debug_reports(r, uhdev, events)
+
+                assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RZ) in events
+                value = evdev.value[libevdev.EV_ABS.ABS_RZ]
+                assert rz - 64 <= value <= rz + 64
+
+        def test_battery(self):
+            uhdev = self.uhdev
+
+            assert uhdev.power_supply_class is not None
+
+            # DS4 capacity levels are in increments of 10.
+            # Battery is never below 5%.
+            for i in range(5, 105, 10):
+                uhdev.battery.capacity = i
+                uhdev.event()
+                assert uhdev.power_supply_class.capacity == i
+
+            # Discharging tests only make sense for BlueTooth.
+            if uhdev.bus == BusType.BLUETOOTH:
+                uhdev.battery.cable_connected = False
+                uhdev.battery.capacity = 45
+                uhdev.event()
+                assert uhdev.power_supply_class.status == "Discharging"
+
+            uhdev.battery.cable_connected = True
+            uhdev.battery.capacity = 5
+            uhdev.event()
+            assert uhdev.power_supply_class.status == "Charging"
+
+            uhdev.battery.capacity = 100
+            uhdev.event()
+            assert uhdev.power_supply_class.status == "Charging"
+
+            uhdev.battery.full = True
+            uhdev.event()
+            assert uhdev.power_supply_class.status == "Full"
+
+        def test_mt_single_touch(self):
+            """send a single touch in the first slot of the device,
+            and release it."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev("Touch Pad")
+
+            t0 = PSTouchPoint(1, 50, 100)
+            r = uhdev.event(touch=[t0])
+            events = uhdev.next_sync_events("Touch Pad")
+            self.debug_reports(r, uhdev, events)
+
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+
+            t0.tipswitch = False
+            r = uhdev.event(touch=[t0])
+            events = uhdev.next_sync_events("Touch Pad")
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+        def test_mt_dual_touch(self):
+            """Send 2 touches in the first 2 slots.
+            Make sure the kernel sees this as a dual touch.
+            Release and check
+
+            Note: PTP will send here BTN_DOUBLETAP emulation"""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev("Touch Pad")
+
+            t0 = PSTouchPoint(1, 50, 100)
+            t1 = PSTouchPoint(2, 150, 200)
+
+            r = uhdev.event(touch=[t0])
+            events = uhdev.next_sync_events("Touch Pad")
+            self.debug_reports(r, uhdev, events)
+
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+            assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+            r = uhdev.event(touch=[t0, t1])
+            events = uhdev.next_sync_events("Touch Pad")
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH) not in events
+            assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+            assert (
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 5) not in events
+            )
+            assert (
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 10) not in events
+            )
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150
+            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200
+
+            t0.tipswitch = False
+            r = uhdev.event(touch=[t0, t1])
+            events = uhdev.next_sync_events("Touch Pad")
+            self.debug_reports(r, uhdev, events)
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X) not in events
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y) not in events
+
+            t1.tipswitch = False
+            r = uhdev.event(touch=[t1])
+
+            events = uhdev.next_sync_events("Touch Pad")
+            self.debug_reports(r, uhdev, events)
+            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+
+class TestPS3Controller(SonyBaseTest.SonyTest):
+    kernel_modules = [PS3_MODULE]
+
+    def create_device(self):
+        controller = PS3Controller()
+        controller.application_matches = application_matches
+        return controller
+
+    @pytest.fixture(autouse=True)
+    def start_controller(self):
+        # emulate a 'PS' button press to tell the kernel we are ready to accept events
+        self.assert_button(17)
+
+        # drain any remaining udev events
+        while self.uhdev.dispatch(10):
+            pass
+
+        def test_led(self):
+            for k, v in self.uhdev.led_classes.items():
+                # the kernel might have set a LED for us
+                logger.info(f"{k}: {v.brightness}")
+
+                idx = int(k[-1]) - 1
+                assert self.uhdev.hw_leds.get_led(idx)[0] == bool(v.brightness)
+
+                v.brightness = 0
+                self.uhdev.dispatch(10)
+                assert self.uhdev.hw_leds.get_led(idx)[0] is False
+
+                v.brightness = v.max_brightness
+                self.uhdev.dispatch(10)
+                assert self.uhdev.hw_leds.get_led(idx)[0]
+
+
+class CalibratedPS4Controller(object):
+    # DS4 reports uncalibrated sensor data. Calibration coefficients
+    # can be retrieved using a feature report (0x2 USB / 0x5 BT).
+    # The values below are the processed calibration values for the
+    # DS4s matching the feature reports of PS4ControllerBluetooth/USB
+    # as dumped from hid-sony 'ds4_get_calibration_data'.
+    #
+    # Note we duplicate those values here in case the kernel changes them
+    # so we can have tests passing even if hid-tools doesn't have the
+    # correct values.
+    accelerometer_calibration_data = {
+        "x": {"bias": -73, "numer": 16384, "denom": 16472},
+        "y": {"bias": -352, "numer": 16384, "denom": 16344},
+        "z": {"bias": 81, "numer": 16384, "denom": 16319},
+    }
+    gyroscope_calibration_data = {
+        "x": {"bias": 0, "numer": 1105920, "denom": 17827},
+        "y": {"bias": 0, "numer": 1105920, "denom": 17777},
+        "z": {"bias": 0, "numer": 1105920, "denom": 17748},
+    }
+
+
+class CalibratedPS4ControllerBluetooth(CalibratedPS4Controller, PS4ControllerBluetooth):
+    pass
+
+
+class TestPS4ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):
+    def create_device(self):
+        controller = CalibratedPS4ControllerBluetooth()
+        controller.application_matches = application_matches
+        return controller
+
+
+class CalibratedPS4ControllerUSB(CalibratedPS4Controller, PS4ControllerUSB):
+    pass
+
+
+class TestPS4ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):
+    def create_device(self):
+        controller = CalibratedPS4ControllerUSB()
+        controller.application_matches = application_matches
+        return controller
+
+
+class CalibratedPS5Controller(object):
+    # DualSense reports uncalibrated sensor data. Calibration coefficients
+    # can be retrieved using feature report 0x09.
+    # The values below are the processed calibration values for the
+    # DualSene matching the feature reports of PS5ControllerBluetooth/USB
+    # as dumped from hid-playstation 'dualsense_get_calibration_data'.
+    #
+    # Note we duplicate those values here in case the kernel changes them
+    # so we can have tests passing even if hid-tools doesn't have the
+    # correct values.
+    accelerometer_calibration_data = {
+        "x": {"bias": 0, "numer": 16384, "denom": 16374},
+        "y": {"bias": -114, "numer": 16384, "denom": 16362},
+        "z": {"bias": 2, "numer": 16384, "denom": 16395},
+    }
+    gyroscope_calibration_data = {
+        "x": {"bias": 0, "numer": 1105920, "denom": 17727},
+        "y": {"bias": 0, "numer": 1105920, "denom": 17728},
+        "z": {"bias": 0, "numer": 1105920, "denom": 17769},
+    }
+
+
+class CalibratedPS5ControllerBluetooth(CalibratedPS5Controller, PS5ControllerBluetooth):
+    pass
+
+
+class TestPS5ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):
+    kernel_modules = [PS5_MODULE]
+
+    def create_device(self):
+        controller = CalibratedPS5ControllerBluetooth()
+        controller.application_matches = application_matches
+        return controller
+
+
+class CalibratedPS5ControllerUSB(CalibratedPS5Controller, PS5ControllerUSB):
+    pass
+
+
+class TestPS5ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):
+    kernel_modules = [PS5_MODULE]
+
+    def create_device(self):
+        controller = CalibratedPS5ControllerUSB()
+        controller.application_matches = application_matches
+        return controller
diff --git a/tools/testing/selftests/hid/tests/test_tablet.py b/tools/testing/selftests/hid/tests/test_tablet.py
new file mode 100644 (file)
index 0000000..303ffff
--- /dev/null
@@ -0,0 +1,872 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2021 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2021 Red Hat, Inc.
+#
+
+from . import base
+import copy
+from enum import Enum
+from hidtools.util import BusType
+import libevdev
+import logging
+import pytest
+from typing import Dict, Tuple
+
+logger = logging.getLogger("hidtools.test.tablet")
+
+
+class PenState(Enum):
+    """Pen states according to Microsoft reference:
+    https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+    """
+
+    PEN_IS_OUT_OF_RANGE = (False, None)
+    PEN_IS_IN_RANGE = (False, libevdev.EV_KEY.BTN_TOOL_PEN)
+    PEN_IS_IN_CONTACT = (True, libevdev.EV_KEY.BTN_TOOL_PEN)
+    PEN_IS_IN_RANGE_WITH_ERASING_INTENT = (False, libevdev.EV_KEY.BTN_TOOL_RUBBER)
+    PEN_IS_ERASING = (True, libevdev.EV_KEY.BTN_TOOL_RUBBER)
+
+    def __init__(self, touch, tool):
+        self.touch = touch
+        self.tool = tool
+
+    @classmethod
+    def from_evdev(cls, evdev) -> "PenState":
+        touch = bool(evdev.value[libevdev.EV_KEY.BTN_TOUCH])
+        tool = None
+        if (
+            evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
+            and not evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
+        ):
+            tool = libevdev.EV_KEY.BTN_TOOL_RUBBER
+        elif (
+            evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
+            and not evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
+        ):
+            tool = libevdev.EV_KEY.BTN_TOOL_PEN
+        elif (
+            evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
+            or evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
+        ):
+            raise ValueError("2 tools are not allowed")
+
+        return cls((touch, tool))
+
+    def apply(self, events) -> "PenState":
+        if libevdev.EV_SYN.SYN_REPORT in events:
+            raise ValueError("EV_SYN is in the event sequence")
+        touch = self.touch
+        touch_found = False
+        tool = self.tool
+        tool_found = False
+
+        for ev in events:
+            if ev == libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH):
+                if touch_found:
+                    raise ValueError(f"duplicated BTN_TOUCH in {events}")
+                touch_found = True
+                touch = bool(ev.value)
+            elif ev in (
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN),
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_RUBBER),
+            ):
+                if tool_found:
+                    raise ValueError(f"duplicated BTN_TOOL_* in {events}")
+                tool_found = True
+                if ev.value:
+                    tool = ev.code
+                else:
+                    tool = None
+
+        new_state = PenState((touch, tool))
+        assert (
+            new_state in self.valid_transitions()
+        ), f"moving from {self} to {new_state} is forbidden"
+
+        return new_state
+
+    def valid_transitions(self) -> Tuple["PenState", ...]:
+        """Following the state machine in the URL above, with a couple of addition
+        for skipping the in-range state, due to historical reasons.
+
+        Note that those transitions are from the evdev point of view, not HID"""
+        if self == PenState.PEN_IS_OUT_OF_RANGE:
+            return (
+                PenState.PEN_IS_OUT_OF_RANGE,
+                PenState.PEN_IS_IN_RANGE,
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+                PenState.PEN_IS_IN_CONTACT,
+                PenState.PEN_IS_ERASING,
+            )
+
+        if self == PenState.PEN_IS_IN_RANGE:
+            return (
+                PenState.PEN_IS_IN_RANGE,
+                PenState.PEN_IS_OUT_OF_RANGE,
+                PenState.PEN_IS_IN_CONTACT,
+            )
+
+        if self == PenState.PEN_IS_IN_CONTACT:
+            return (
+                PenState.PEN_IS_IN_CONTACT,
+                PenState.PEN_IS_IN_RANGE,
+                PenState.PEN_IS_OUT_OF_RANGE,
+            )
+
+        if self == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
+            return (
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+                PenState.PEN_IS_OUT_OF_RANGE,
+                PenState.PEN_IS_ERASING,
+            )
+
+        if self == PenState.PEN_IS_ERASING:
+            return (
+                PenState.PEN_IS_ERASING,
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+                PenState.PEN_IS_OUT_OF_RANGE,
+            )
+
+        return tuple()
+
+
+class Data(object):
+    pass
+
+
+class Pen(object):
+    def __init__(self, x, y):
+        self.x = x
+        self.y = y
+        self.tipswitch = False
+        self.tippressure = 15
+        self.azimuth = 0
+        self.inrange = False
+        self.width = 10
+        self.height = 10
+        self.barrelswitch = False
+        self.invert = False
+        self.eraser = False
+        self.x_tilt = 0
+        self.y_tilt = 0
+        self.twist = 0
+        self._old_values = None
+        self.current_state = None
+
+    def _restore(self):
+        if self._old_values is not None:
+            for i in [
+                "x",
+                "y",
+                "tippressure",
+                "azimuth",
+                "width",
+                "height",
+                "twist",
+                "x_tilt",
+                "y_tilt",
+            ]:
+                setattr(self, i, getattr(self._old_values, i))
+
+    def move_to(self, state):
+        # fill in the previous values
+        if self.current_state == PenState.PEN_IS_OUT_OF_RANGE:
+            self._restore()
+
+        print(f"\n  *** pen is moving to {state} ***")
+
+        if state == PenState.PEN_IS_OUT_OF_RANGE:
+            self._old_values = copy.copy(self)
+            self.x = 0
+            self.y = 0
+            self.tipswitch = False
+            self.tippressure = 0
+            self.azimuth = 0
+            self.inrange = False
+            self.width = 0
+            self.height = 0
+            self.invert = False
+            self.eraser = False
+            self.x_tilt = 0
+            self.y_tilt = 0
+            self.twist = 0
+        elif state == PenState.PEN_IS_IN_RANGE:
+            self.tipswitch = False
+            self.inrange = True
+            self.invert = False
+            self.eraser = False
+        elif state == PenState.PEN_IS_IN_CONTACT:
+            self.tipswitch = True
+            self.inrange = True
+            self.invert = False
+            self.eraser = False
+        elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
+            self.tipswitch = False
+            self.inrange = True
+            self.invert = True
+            self.eraser = False
+        elif state == PenState.PEN_IS_ERASING:
+            self.tipswitch = False
+            self.inrange = True
+            self.invert = True
+            self.eraser = True
+
+        self.current_state = state
+
+    def __assert_axis(self, evdev, axis, value):
+        if (
+            axis == libevdev.EV_KEY.BTN_TOOL_RUBBER
+            and evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER] is None
+        ):
+            return
+
+        assert (
+            evdev.value[axis] == value
+        ), f"assert evdev.value[{axis}] ({evdev.value[axis]}) != {value}"
+
+    def assert_expected_input_events(self, evdev):
+        assert evdev.value[libevdev.EV_ABS.ABS_X] == self.x
+        assert evdev.value[libevdev.EV_ABS.ABS_Y] == self.y
+        assert self.current_state == PenState.from_evdev(evdev)
+
+    @staticmethod
+    def legal_transitions() -> Dict[str, Tuple[PenState, ...]]:
+        """This is the first half of the Windows Pen Implementation state machine:
+        we don't have Invert nor Erase bits, so just move in/out-of-range or proximity.
+        https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+        """
+        return {
+            "in-range": (PenState.PEN_IS_IN_RANGE,),
+            "in-range -> out-of-range": (
+                PenState.PEN_IS_IN_RANGE,
+                PenState.PEN_IS_OUT_OF_RANGE,
+            ),
+            "in-range -> touch": (PenState.PEN_IS_IN_RANGE, PenState.PEN_IS_IN_CONTACT),
+            "in-range -> touch -> release": (
+                PenState.PEN_IS_IN_RANGE,
+                PenState.PEN_IS_IN_CONTACT,
+                PenState.PEN_IS_IN_RANGE,
+            ),
+            "in-range -> touch -> release -> out-of-range": (
+                PenState.PEN_IS_IN_RANGE,
+                PenState.PEN_IS_IN_CONTACT,
+                PenState.PEN_IS_IN_RANGE,
+                PenState.PEN_IS_OUT_OF_RANGE,
+            ),
+        }
+
+    @staticmethod
+    def legal_transitions_with_invert() -> Dict[str, Tuple[PenState, ...]]:
+        """This is the second half of the Windows Pen Implementation state machine:
+        we now have Invert and Erase bits, so move in/out or proximity with the intend
+        to erase.
+        https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+        """
+        return {
+            "hover-erasing": (PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,),
+            "hover-erasing -> out-of-range": (
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+                PenState.PEN_IS_OUT_OF_RANGE,
+            ),
+            "hover-erasing -> erase": (
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+                PenState.PEN_IS_ERASING,
+            ),
+            "hover-erasing -> erase -> release": (
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+                PenState.PEN_IS_ERASING,
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+            ),
+            "hover-erasing -> erase -> release -> out-of-range": (
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+                PenState.PEN_IS_ERASING,
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+                PenState.PEN_IS_OUT_OF_RANGE,
+            ),
+            "hover-erasing -> in-range": (
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+                PenState.PEN_IS_IN_RANGE,
+            ),
+            "in-range -> hover-erasing": (
+                PenState.PEN_IS_IN_RANGE,
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+            ),
+        }
+
+    @staticmethod
+    def tolerated_transitions() -> Dict[str, Tuple[PenState, ...]]:
+        """This is not adhering to the Windows Pen Implementation state machine
+        but we should expect the kernel to behave properly, mostly for historical
+        reasons."""
+        return {
+            "direct-in-contact": (PenState.PEN_IS_IN_CONTACT,),
+            "direct-in-contact -> out-of-range": (
+                PenState.PEN_IS_IN_CONTACT,
+                PenState.PEN_IS_OUT_OF_RANGE,
+            ),
+        }
+
+    @staticmethod
+    def tolerated_transitions_with_invert() -> Dict[str, Tuple[PenState, ...]]:
+        """This is the second half of the Windows Pen Implementation state machine:
+        we now have Invert and Erase bits, so move in/out or proximity with the intend
+        to erase.
+        https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+        """
+        return {
+            "direct-erase": (PenState.PEN_IS_ERASING,),
+            "direct-erase -> out-of-range": (
+                PenState.PEN_IS_ERASING,
+                PenState.PEN_IS_OUT_OF_RANGE,
+            ),
+        }
+
+    @staticmethod
+    def broken_transitions() -> Dict[str, Tuple[PenState, ...]]:
+        """Those tests are definitely not part of the Windows specification.
+        However, a half broken device might export those transitions.
+        For example, a pen that has the eraser button might wobble between
+        touching and erasing if the tablet doesn't enforce the Windows
+        state machine."""
+        return {
+            "in-range -> touch -> erase -> hover-erase": (
+                PenState.PEN_IS_IN_RANGE,
+                PenState.PEN_IS_IN_CONTACT,
+                PenState.PEN_IS_ERASING,
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+            ),
+            "in-range -> erase -> hover-erase": (
+                PenState.PEN_IS_IN_RANGE,
+                PenState.PEN_IS_ERASING,
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+            ),
+            "hover-erase -> erase -> touch -> in-range": (
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+                PenState.PEN_IS_ERASING,
+                PenState.PEN_IS_IN_CONTACT,
+                PenState.PEN_IS_IN_RANGE,
+            ),
+            "hover-erase -> touch -> in-range": (
+                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+                PenState.PEN_IS_IN_CONTACT,
+                PenState.PEN_IS_IN_RANGE,
+            ),
+            "touch -> erase -> touch -> erase": (
+                PenState.PEN_IS_IN_CONTACT,
+                PenState.PEN_IS_ERASING,
+                PenState.PEN_IS_IN_CONTACT,
+                PenState.PEN_IS_ERASING,
+            ),
+        }
+
+
+class PenDigitizer(base.UHIDTestDevice):
+    def __init__(
+        self,
+        name,
+        rdesc_str=None,
+        rdesc=None,
+        application="Pen",
+        physical="Stylus",
+        input_info=(BusType.USB, 1, 2),
+        evdev_name_suffix=None,
+    ):
+        super().__init__(name, application, rdesc_str, rdesc, input_info)
+        self.physical = physical
+        self.cur_application = application
+        if evdev_name_suffix is not None:
+            self.name += evdev_name_suffix
+
+        self.fields = []
+        for r in self.parsed_rdesc.input_reports.values():
+            if r.application_name == self.application:
+                physicals = [f.physical_name for f in r]
+                if self.physical not in physicals and None not in physicals:
+                    continue
+                self.fields = [f.usage_name for f in r]
+
+    def event(self, pen):
+        rs = []
+        r = self.create_report(application=self.cur_application, data=pen)
+        self.call_input_event(r)
+        rs.append(r)
+        return rs
+
+    def get_report(self, req, rnum, rtype):
+        if rtype != self.UHID_FEATURE_REPORT:
+            return (1, [])
+
+        rdesc = None
+        for v in self.parsed_rdesc.feature_reports.values():
+            if v.report_ID == rnum:
+                rdesc = v
+
+        if rdesc is None:
+            return (1, [])
+
+        return (1, [])
+
+    def set_report(self, req, rnum, rtype, data):
+        if rtype != self.UHID_FEATURE_REPORT:
+            return 1
+
+        rdesc = None
+        for v in self.parsed_rdesc.feature_reports.values():
+            if v.report_ID == rnum:
+                rdesc = v
+
+        if rdesc is None:
+            return 1
+
+        return 1
+
+
+class BaseTest:
+    class TestTablet(base.BaseTestCase.TestUhid):
+        def create_device(self):
+            raise Exception("please reimplement me in subclasses")
+
+        def post(self, uhdev, pen):
+            r = uhdev.event(pen)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            return events
+
+        def validate_transitions(self, from_state, pen, evdev, events):
+            # check that the final state is correct
+            pen.assert_expected_input_events(evdev)
+
+            # check that the transitions are valid
+            sync_events = []
+            while libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT) in events:
+                # split the first EV_SYN from the list
+                idx = events.index(libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT))
+                sync_events = events[:idx]
+                events = events[idx + 1 :]
+
+                # now check for a valid transition
+                from_state = from_state.apply(sync_events)
+
+            if events:
+                from_state = from_state.apply(sync_events)
+
+        def _test_states(self, state_list, scribble):
+            """Internal method to test against a list of
+            transition between states.
+            state_list is a list of PenState objects
+            scribble is a boolean which tells if we need
+            to wobble a little the X,Y coordinates of the pen
+            between each state transition."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            cur_state = PenState.PEN_IS_OUT_OF_RANGE
+
+            p = Pen(50, 60)
+            p.move_to(PenState.PEN_IS_OUT_OF_RANGE)
+            events = self.post(uhdev, p)
+            self.validate_transitions(cur_state, p, evdev, events)
+
+            cur_state = p.current_state
+
+            for state in state_list:
+                if scribble and cur_state != PenState.PEN_IS_OUT_OF_RANGE:
+                    p.x += 1
+                    p.y -= 1
+                    events = self.post(uhdev, p)
+                    self.validate_transitions(cur_state, p, evdev, events)
+                    assert len(events) >= 3  # X, Y, SYN
+                p.move_to(state)
+                if scribble and state != PenState.PEN_IS_OUT_OF_RANGE:
+                    p.x += 1
+                    p.y -= 1
+                events = self.post(uhdev, p)
+                self.validate_transitions(cur_state, p, evdev, events)
+                cur_state = p.current_state
+
+        @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+        @pytest.mark.parametrize(
+            "state_list",
+            [pytest.param(v, id=k) for k, v in Pen.legal_transitions().items()],
+        )
+        def test_valid_pen_states(self, state_list, scribble):
+            """This is the first half of the Windows Pen Implementation state machine:
+            we don't have Invert nor Erase bits, so just move in/out-of-range or proximity.
+            https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+            """
+            self._test_states(state_list, scribble)
+
+        @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+        @pytest.mark.parametrize(
+            "state_list",
+            [pytest.param(v, id=k) for k, v in Pen.tolerated_transitions().items()],
+        )
+        def test_tolerated_pen_states(self, state_list, scribble):
+            """This is not adhering to the Windows Pen Implementation state machine
+            but we should expect the kernel to behave properly, mostly for historical
+            reasons."""
+            self._test_states(state_list, scribble)
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: "Invert" not in uhdev.fields,
+            "Device not compatible, missing Invert usage",
+        )
+        @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+        @pytest.mark.parametrize(
+            "state_list",
+            [
+                pytest.param(v, id=k)
+                for k, v in Pen.legal_transitions_with_invert().items()
+            ],
+        )
+        def test_valid_invert_pen_states(self, state_list, scribble):
+            """This is the second half of the Windows Pen Implementation state machine:
+            we now have Invert and Erase bits, so move in/out or proximity with the intend
+            to erase.
+            https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+            """
+            self._test_states(state_list, scribble)
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: "Invert" not in uhdev.fields,
+            "Device not compatible, missing Invert usage",
+        )
+        @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+        @pytest.mark.parametrize(
+            "state_list",
+            [
+                pytest.param(v, id=k)
+                for k, v in Pen.tolerated_transitions_with_invert().items()
+            ],
+        )
+        def test_tolerated_invert_pen_states(self, state_list, scribble):
+            """This is the second half of the Windows Pen Implementation state machine:
+            we now have Invert and Erase bits, so move in/out or proximity with the intend
+            to erase.
+            https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+            """
+            self._test_states(state_list, scribble)
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: "Invert" not in uhdev.fields,
+            "Device not compatible, missing Invert usage",
+        )
+        @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+        @pytest.mark.parametrize(
+            "state_list",
+            [pytest.param(v, id=k) for k, v in Pen.broken_transitions().items()],
+        )
+        def test_tolerated_broken_pen_states(self, state_list, scribble):
+            """Those tests are definitely not part of the Windows specification.
+            However, a half broken device might export those transitions.
+            For example, a pen that has the eraser button might wobble between
+            touching and erasing if the tablet doesn't enforce the Windows
+            state machine."""
+            self._test_states(state_list, scribble)
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: "Barrel Switch" not in uhdev.fields,
+            "Device not compatible, missing Barrel Switch usage",
+        )
+        def test_primary_button(self):
+            """Primary button (stylus) pressed, reports as pressed even while hovering.
+            Actual reporting from the device: hid=TIPSWITCH,BARRELSWITCH,INRANGE (code=TOUCH,STYLUS,PEN):
+              { 0, 0, 1 } <- hover
+              { 0, 1, 1 } <- primary button pressed
+              { 0, 1, 1 } <- liftoff
+              { 0, 0, 0 } <- leaves
+            """
+
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            p = Pen(50, 60)
+            p.inrange = True
+            events = self.post(uhdev, p)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1) in events
+            assert evdev.value[libevdev.EV_ABS.ABS_X] == 50
+            assert evdev.value[libevdev.EV_ABS.ABS_Y] == 60
+            assert not evdev.value[libevdev.EV_KEY.BTN_STYLUS]
+
+            p.barrelswitch = True
+            events = self.post(uhdev, p)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1) in events
+
+            p.x += 1
+            p.y -= 1
+            events = self.post(uhdev, p)
+            assert len(events) == 3  # X, Y, SYN
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 51) in events
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 59) in events
+
+            p.barrelswitch = False
+            events = self.post(uhdev, p)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0) in events
+
+            p.inrange = False
+            events = self.post(uhdev, p)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0) in events
+
+        @pytest.mark.skip_if_uhdev(
+            lambda uhdev: "Barrel Switch" not in uhdev.fields,
+            "Device not compatible, missing Barrel Switch usage",
+        )
+        def test_contact_primary_button(self):
+            """Primary button (stylus) pressed, reports as pressed even while hovering.
+            Actual reporting from the device: hid=TIPSWITCH,BARRELSWITCH,INRANGE (code=TOUCH,STYLUS,PEN):
+              { 0, 0, 1 } <- hover
+              { 0, 1, 1 } <- primary button pressed
+              { 1, 1, 1 } <- touch-down
+              { 1, 1, 1 } <- still touch, scribble on the screen
+              { 0, 1, 1 } <- liftoff
+              { 0, 0, 0 } <- leaves
+            """
+
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+
+            p = Pen(50, 60)
+            p.inrange = True
+            events = self.post(uhdev, p)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1) in events
+            assert evdev.value[libevdev.EV_ABS.ABS_X] == 50
+            assert evdev.value[libevdev.EV_ABS.ABS_Y] == 60
+            assert not evdev.value[libevdev.EV_KEY.BTN_STYLUS]
+
+            p.barrelswitch = True
+            events = self.post(uhdev, p)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1) in events
+
+            p.tipswitch = True
+            events = self.post(uhdev, p)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+            assert evdev.value[libevdev.EV_KEY.BTN_STYLUS]
+
+            p.x += 1
+            p.y -= 1
+            events = self.post(uhdev, p)
+            assert len(events) == 3  # X, Y, SYN
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 51) in events
+            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 59) in events
+
+            p.tipswitch = False
+            events = self.post(uhdev, p)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+
+            p.barrelswitch = False
+            p.inrange = False
+            events = self.post(uhdev, p)
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0) in events
+            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0) in events
+
+
+class GXTP_pen(PenDigitizer):
+    def event(self, pen):
+        if not hasattr(self, "prev_tip_state"):
+            self.prev_tip_state = False
+
+        internal_pen = copy.copy(pen)
+
+        # bug in the controller: when the pen touches the
+        # surface, in-range stays to 1, but when
+        # the pen moves in-range gets reverted to 0
+        if pen.tipswitch and self.prev_tip_state:
+            internal_pen.inrange = False
+
+        self.prev_tip_state = pen.tipswitch
+
+        # another bug in the controller: when the pen is
+        # inverted, invert is set to 1, but as soon as
+        # the pen touches the surface, eraser is correctly
+        # set to 1 but invert is released
+        if pen.eraser:
+            internal_pen.invert = False
+
+        return super().event(internal_pen)
+
+
+class USIPen(PenDigitizer):
+    pass
+
+
+################################################################################
+#
+# Windows 7 compatible devices
+#
+################################################################################
+# class TestEgalax_capacitive_0eef_7224(BaseTest.TestTablet):
+#     def create_device(self):
+#         return PenDigitizer('uhid test egalax-capacitive_0eef_7224',
+#                             rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
+#                             input_info=(BusType.USB, 0x0eef, 0x7224),
+#                             evdev_name_suffix=' Touchscreen')
+#
+#
+# class TestEgalax_capacitive_0eef_72fa(BaseTest.TestTablet):
+#     def create_device(self):
+#         return PenDigitizer('uhid test egalax-capacitive_0eef_72fa',
+#                             rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 72 22 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 87 13 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 72 22 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 87 13 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
+#                             input_info=(BusType.USB, 0x0eef, 0x72fa),
+#                             evdev_name_suffix=' Touchscreen')
+#
+#
+# class TestEgalax_capacitive_0eef_7336(BaseTest.TestTablet):
+#     def create_device(self):
+#         return PenDigitizer('uhid test egalax-capacitive_0eef_7336',
+#                             rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 c1 20 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c2 18 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 c1 20 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c2 18 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
+#                             input_info=(BusType.USB, 0x0eef, 0x7336),
+#                             evdev_name_suffix=' Touchscreen')
+#
+#
+# class TestEgalax_capacitive_0eef_7337(BaseTest.TestTablet):
+#     def create_device(self):
+#         return PenDigitizer('uhid test egalax-capacitive_0eef_7337',
+#                             rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 ae 17 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c3 0e 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 ae 17 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c3 0e 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
+#                             input_info=(BusType.USB, 0x0eef, 0x7337),
+#                             evdev_name_suffix=' Touchscreen')
+#
+#
+# class TestEgalax_capacitive_0eef_7349(BaseTest.TestTablet):
+#     def create_device(self):
+#         return PenDigitizer('uhid test egalax-capacitive_0eef_7349',
+#                             rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
+#                             input_info=(BusType.USB, 0x0eef, 0x7349),
+#                             evdev_name_suffix=' Touchscreen')
+#
+#
+# class TestEgalax_capacitive_0eef_73f4(BaseTest.TestTablet):
+#     def create_device(self):
+#         return PenDigitizer('uhid test egalax-capacitive_0eef_73f4',
+#                             rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 96 4e 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 23 2c 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 96 4e 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 23 2c 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
+#                             input_info=(BusType.USB, 0x0eef, 0x73f4),
+#                             evdev_name_suffix=' Touchscreen')
+#
+#  bogus: BTN_TOOL_PEN is not emitted
+# class TestIrtouch_6615_0070(BaseTest.TestTablet):
+#     def create_device(self):
+#         return PenDigitizer('uhid test irtouch_6615_0070',
+#                             rdesc='05 01 09 02 a1 01 85 10 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 09 30 09 31 15 00 26 ff 7f 75 10 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 30 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 05 0d 09 54 15 00 26 02 00 75 08 95 01 81 02 85 03 09 55 15 00 26 ff 00 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 02 09 52 09 53 15 00 26 ff 00 75 08 95 02 b1 02 c0 05 0d 09 02 a1 01 85 20 09 20 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 85 01 06 00 ff 09 01 75 08 95 01 b1 02 c0 c0',
+#                             input_info=(BusType.USB, 0x6615, 0x0070))
+
+
+class TestNexio_1870_0100(BaseTest.TestTablet):
+    def create_device(self):
+        return PenDigitizer(
+            "uhid test nexio_1870_0100",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 02 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 40 81 00 19 01 29 40 91 00 c0",
+            input_info=(BusType.USB, 0x1870, 0x0100),
+        )
+
+
+class TestNexio_1870_010d(BaseTest.TestTablet):
+    def create_device(self):
+        return PenDigitizer(
+            "uhid test nexio_1870_010d",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0",
+            input_info=(BusType.USB, 0x1870, 0x010D),
+        )
+
+
+class TestNexio_1870_0119(BaseTest.TestTablet):
+    def create_device(self):
+        return PenDigitizer(
+            "uhid test nexio_1870_0119",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0",
+            input_info=(BusType.USB, 0x1870, 0x0119),
+        )
+
+
+################################################################################
+#
+# Windows 8 compatible devices
+#
+################################################################################
+
+# bogus: application is 'undefined'
+# class Testatmel_03eb_8409(BaseTest.TestTablet):
+#     def create_device(self):
+#         return PenDigitizer('uhid test atmel_03eb_8409', rdesc='05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 00 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 46 18 06 26 77 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0')
+
+
+class Testatmel_03eb_840b(BaseTest.TestTablet):
+    def create_device(self):
+        return PenDigitizer(
+            "uhid test atmel_03eb_840b",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 00 0a 26 ff 0f 09 30 81 02 46 a0 05 26 ff 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0",
+        )
+
+
+class Testn_trig_1b96_0c01(BaseTest.TestTablet):
+    def create_device(self):
+        return PenDigitizer(
+            "uhid test n_trig_1b96_0c01",
+            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+        )
+
+
+class Testn_trig_1b96_0c03(BaseTest.TestTablet):
+    def create_device(self):
+        return PenDigitizer(
+            "uhid test n_trig_1b96_0c03",
+            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+        )
+
+
+class Testn_trig_1b96_0f00(BaseTest.TestTablet):
+    def create_device(self):
+        return PenDigitizer(
+            "uhid test n_trig_1b96_0f00",
+            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+        )
+
+
+class Testn_trig_1b96_0f04(BaseTest.TestTablet):
+    def create_device(self):
+        return PenDigitizer(
+            "uhid test n_trig_1b96_0f04",
+            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+        )
+
+
+class Testn_trig_1b96_1000(BaseTest.TestTablet):
+    def create_device(self):
+        return PenDigitizer(
+            "uhid test n_trig_1b96_1000",
+            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+        )
+
+
+class TestGXTP_27c6_0113(BaseTest.TestTablet):
+    def create_device(self):
+        return GXTP_pen(
+            "uhid test GXTP_27c6_0113",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 55 0e 65 11 35 00 15 00 09 42 25 01 75 01 95 01 81 02 95 07 81 01 95 01 75 08 09 51 81 02 75 10 05 01 26 00 14 46 1f 07 09 30 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 95 07 81 01 95 01 75 08 09 51 81 02 75 10 05 01 26 00 14 46 1f 07 09 30 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 95 07 81 01 95 01 75 08 09 51 81 02 75 10 05 01 26 00 14 46 1f 07 09 30 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 14 75 10 55 0e 65 11 09 30 35 00 46 1f 07 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 14 75 10 55 0e 65 11 09 30 35 00 46 1f 07 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 54 15 00 25 7f 75 08 95 01 81 02 85 02 09 55 95 01 25 0a b1 02 85 03 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 85 08 09 20 a1 00 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 04 81 02 95 01 81 03 09 32 81 02 95 02 81 03 95 01 75 08 09 51 81 02 05 01 09 30 75 10 95 01 a4 55 0e 65 11 35 00 26 00 14 46 1f 07 81 42 09 31 26 80 0c 46 77 04 81 42 b4 05 0d 09 30 26 ff 0f 81 02 09 3d 65 14 55 0e 36 d8 dc 46 28 23 16 d8 dc 26 28 23 81 02 09 3e 81 02 c0 c0 06 f0 ff 09 01 a1 01 85 0e 09 01 15 00 25 ff 75 08 95 40 91 02 09 01 15 00 25 ff 75 08 95 40 81 02 c0 05 01 09 06 a1 01 85 04 05 07 09 e3 15 00 25 01 75 01 95 01 81 02 95 07 81 03 c0",
+        )
+
+
+################################################################################
+#
+# Windows 8 compatible devices with USI Pen
+#
+################################################################################
+
+
+class TestElan_04f3_2A49(BaseTest.TestTablet):
+    def create_device(self):
+        return USIPen(
+            "uhid test Elan_04f3_2A49",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 54 25 7f 96 01 00 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 16 00 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 16 00 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 20 09 01 91 02 c0 06 00 ff 09 01 a1 01 85 06 09 03 75 08 95 12 91 02 09 04 75 08 95 03 b1 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0 05 0d 09 02 a1 01 85 07 35 00 09 20 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0f 65 11 46 26 01 26 1c 48 81 42 09 31 46 a6 00 26 bc 2f 81 42 b4 05 0d 09 30 26 00 10 81 02 75 08 95 01 09 3b 25 64 81 42 09 38 15 00 25 02 81 02 09 5c 26 ff 00 81 02 09 5e 81 02 09 70 a1 02 15 01 25 06 09 72 09 73 09 74 09 75 09 76 09 77 81 20 09 5b 25 ff 75 40 81 02 c0 06 00 ff 75 08 95 02 09 01 81 02 c0 05 0d 85 60 09 81 a1 02 09 38 75 08 95 01 15 00 25 02 81 02 09 81 15 01 25 04 09 82 09 83 09 84 09 85 81 20 c0 85 61 09 5c a1 02 15 00 26 ff 00 75 08 95 01 09 38 b1 02 09 5c 26 ff 00 b1 02 09 5d 75 01 95 01 25 01 b1 02 95 07 b1 03 c0 85 62 09 5e a1 02 09 38 15 00 25 02 75 08 95 01 b1 02 09 5e 26 ff 00 b1 02 09 5f 75 01 25 01 b1 02 75 07 b1 03 c0 85 63 09 70 a1 02 75 08 95 01 15 00 25 02 09 38 b1 02 09 70 a1 02 25 06 09 72 09 73 09 74 09 75 09 76 09 77 b1 20 c0 09 71 75 01 25 01 b1 02 75 07 b1 03 c0 85 64 09 80 15 00 25 ff 75 40 95 01 b1 02 85 65 09 44 a1 02 09 38 75 08 95 01 25 02 b1 02 15 01 25 03 09 44 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 5a a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 45 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 c0 85 66 75 08 95 01 05 0d 09 90 a1 02 09 38 25 02 b1 02 09 91 75 10 26 ff 0f b1 02 09 92 75 40 25 ff b1 02 05 06 09 2a 75 08 26 ff 00 a1 02 09 2d b1 02 09 2e b1 02 c0 c0 85 67 05 06 09 2b a1 02 05 0d 25 02 09 38 b1 02 05 06 09 2b a1 02 09 2d 26 ff 00 b1 02 09 2e b1 02 c0 c0 85 68 06 00 ff 09 01 a1 02 05 0d 09 38 75 08 95 01 25 02 b1 02 06 00 ff 09 01 75 10 27 ff ff 00 00 b1 02 c0 85 69 05 0d 09 38 75 08 95 01 15 00 25 02 b1 02 c0 06 00 ff 09 81 a1 01 85 17 75 08 95 1f 09 05 81 02 c0",
+            input_info=(BusType.I2C, 0x04F3, 0x2A49),
+        )
+
+
+class TestGoodix_27c6_0e00(BaseTest.TestTablet):
+    def create_device(self):
+        return USIPen(
+            "uhid test Elan_04f3_2A49",
+            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 55 0e 65 11 35 00 15 00 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 75 08 09 51 95 01 81 02 05 01 26 04 20 75 10 55 0e 65 11 09 30 35 00 46 e6 09 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 75 08 09 51 95 01 81 02 05 01 26 04 20 75 10 55 0e 65 11 09 30 35 00 46 e6 09 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 54 15 00 25 7f 75 08 95 01 81 02 85 02 09 55 95 01 25 0a b1 02 85 03 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 09 20 a1 00 85 08 05 01 a4 09 30 35 00 46 e6 09 15 00 26 04 20 55 0d 65 13 75 10 95 01 81 02 09 31 46 9a 06 26 60 15 81 02 b4 05 0d 09 38 95 01 75 08 15 00 25 01 81 02 09 30 75 10 26 ff 0f 81 02 09 31 81 02 09 42 09 44 09 5a 09 3c 09 45 09 32 75 01 95 06 25 01 81 02 95 02 81 03 09 3d 55 0e 65 14 36 d8 dc 46 28 23 16 d8 dc 26 28 23 95 01 75 10 81 02 09 3e 81 02 09 41 15 00 27 a0 8c 00 00 35 00 47 a0 8c 00 00 81 02 05 20 0a 53 04 65 00 16 01 f8 26 ff 07 75 10 95 01 81 02 0a 54 04 81 02 0a 55 04 81 02 0a 57 04 81 02 0a 58 04 81 02 0a 59 04 81 02 0a 72 04 81 02 0a 73 04 81 02 0a 74 04 81 02 05 0d 09 3b 15 00 25 64 75 08 81 02 09 5b 25 ff 75 40 81 02 06 00 ff 09 5b 75 20 81 02 05 0d 09 5c 26 ff 00 75 08 81 02 09 5e 81 02 09 70 a1 02 15 01 25 06 09 72 09 73 09 74 09 75 09 76 09 77 81 20 c0 06 00 ff 09 01 15 00 27 ff ff 00 00 75 10 95 01 81 02 85 09 09 81 a1 02 09 81 15 01 25 04 09 82 09 83 09 84 09 85 81 20 c0 85 10 09 5c a1 02 15 00 25 01 75 08 95 01 09 38 b1 02 09 5c 26 ff 00 b1 02 09 5d 75 01 95 01 25 01 b1 02 95 07 b1 03 c0 85 11 09 5e a1 02 09 38 15 00 25 01 75 08 95 01 b1 02 09 5e 26 ff 00 b1 02 09 5f 75 01 25 01 b1 02 75 07 b1 03 c0 85 12 09 70 a1 02 75 08 95 01 15 00 25 01 09 38 b1 02 09 70 a1 02 25 06 09 72 09 73 09 74 09 75 09 76 09 77 b1 20 c0 09 71 75 01 25 01 b1 02 75 07 b1 03 c0 85 13 09 80 15 00 25 ff 75 40 95 01 b1 02 85 14 09 44 a1 02 09 38 75 08 95 01 25 01 b1 02 15 01 25 03 09 44 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 5a a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 45 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 c0 85 15 75 08 95 01 05 0d 09 90 a1 02 09 38 25 01 b1 02 09 91 75 10 26 ff 0f b1 02 09 92 75 40 25 ff b1 02 05 06 09 2a 75 08 26 ff 00 a1 02 09 2d b1 02 09 2e b1 02 c0 c0 85 16 05 06 09 2b a1 02 05 0d 25 01 09 38 b1 02 05 06 09 2b a1 02 09 2d 26 ff 00 b1 02 09 2e b1 02 c0 c0 85 17 06 00 ff 09 01 a1 02 05 0d 09 38 75 08 95 01 25 01 b1 02 06 00 ff 09 01 75 10 27 ff ff 00 00 b1 02 c0 85 18 05 0d 09 38 75 08 95 01 15 00 25 01 b1 02 c0 c0 06 f0 ff 09 01 a1 01 85 0e 09 01 15 00 25 ff 75 08 95 40 91 02 09 01 15 00 25 ff 75 08 95 40 81 02 c0",
+            input_info=(BusType.I2C, 0x27C6, 0x0E00),
+        )
diff --git a/tools/testing/selftests/hid/tests/test_usb_crash.py b/tools/testing/selftests/hid/tests/test_usb_crash.py
new file mode 100644 (file)
index 0000000..e98bff9
--- /dev/null
@@ -0,0 +1,103 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2021 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2021 Red Hat, Inc.
+#
+
+# This is to ensure we don't crash when emulating USB devices
+
+from . import base
+import pytest
+import logging
+
+logger = logging.getLogger("hidtools.test.usb")
+
+
+class USBDev(base.UHIDTestDevice):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,  # .Usage Page (Generic Desktop)        0
+        0x09, 0x02,  # .Usage (Mouse)                       2
+        0xa1, 0x01,  # .Collection (Application)            4
+        0x09, 0x02,  # ..Usage (Mouse)                      6
+        0xa1, 0x02,  # ..Collection (Logical)               8
+        0x09, 0x01,  # ...Usage (Pointer)                   10
+        0xa1, 0x00,  # ...Collection (Physical)             12
+        0x05, 0x09,  # ....Usage Page (Button)              14
+        0x19, 0x01,  # ....Usage Minimum (1)                16
+        0x29, 0x03,  # ....Usage Maximum (3)                18
+        0x15, 0x00,  # ....Logical Minimum (0)              20
+        0x25, 0x01,  # ....Logical Maximum (1)              22
+        0x75, 0x01,  # ....Report Size (1)                  24
+        0x95, 0x03,  # ....Report Count (3)                 26
+        0x81, 0x02,  # ....Input (Data,Var,Abs)             28
+        0x75, 0x05,  # ....Report Size (5)                  30
+        0x95, 0x01,  # ....Report Count (1)                 32
+        0x81, 0x03,  # ....Input (Cnst,Var,Abs)             34
+        0x05, 0x01,  # ....Usage Page (Generic Desktop)     36
+        0x09, 0x30,  # ....Usage (X)                        38
+        0x09, 0x31,  # ....Usage (Y)                        40
+        0x15, 0x81,  # ....Logical Minimum (-127)           42
+        0x25, 0x7f,  # ....Logical Maximum (127)            44
+        0x75, 0x08,  # ....Report Size (8)                  46
+        0x95, 0x02,  # ....Report Count (2)                 48
+        0x81, 0x06,  # ....Input (Data,Var,Rel)             50
+        0xc0,        # ...End Collection                    52
+        0xc0,        # ..End Collection                     53
+        0xc0,        # .End Collection                      54
+    ]
+    # fmt: on
+
+    def __init__(self, name=None, input_info=None):
+        super().__init__(
+            name, "Mouse", input_info=input_info, rdesc=USBDev.report_descriptor
+        )
+
+    # skip witing for udev events, it's likely that the report
+    # descriptor is wrong
+    def is_ready(self):
+        return True
+
+    # we don't have an evdev node here, so paper over
+    # the checks
+    def get_evdev(self, application=None):
+        return "OK"
+
+
+class TestUSBDevice(base.BaseTestCase.TestUhid):
+    """
+    Test class to test if an emulated USB device crashes
+    the kernel.
+    """
+
+    # conftest.py is generating the following fixture:
+    #
+    # @pytest.fixture(params=[('modulename', 1, 2)])
+    # def usbVidPid(self, request):
+    #     return request.param
+
+    @pytest.fixture()
+    def new_uhdev(self, usbVidPid, request):
+        self.module, self.vid, self.pid = usbVidPid
+        self._load_kernel_module(None, self.module)
+        return USBDev(input_info=(3, self.vid, self.pid))
+
+    def test_creation(self):
+        """
+        inject the USB dev through uhid and immediately see if there is a crash:
+
+        uhid can create a USB device with the BUS_USB bus, and some
+        drivers assume that they can then access USB related structures
+        when they are actually provided a uhid device. This leads to
+        a crash because those access result in a segmentation fault.
+
+        The kernel should not crash on any (random) user space correct
+        use of its API. So run through all available modules and declared
+        devices to see if we can generate a uhid device without a crash.
+
+        The test is empty as the fixture `check_taint` is doing the job (and
+        honestly, when the kernel crashes, the whole machine freezes).
+        """
+        assert True
diff --git a/tools/testing/selftests/hid/tests/test_wacom_generic.py b/tools/testing/selftests/hid/tests/test_wacom_generic.py
new file mode 100644 (file)
index 0000000..b1eb2bc
--- /dev/null
@@ -0,0 +1,844 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+# Copyright (c) 2020 Wacom Technology Corp.
+#
+# Authors:
+#     Jason Gerecke <jason.gerecke@wacom.com>
+
+"""
+Tests for the Wacom driver generic codepath.
+
+This module tests the function of the Wacom driver's generic codepath.
+The generic codepath is used by devices which are not explicitly listed
+in the driver's device table. It uses the device's HID descriptor to
+decode reports sent by the device.
+"""
+
+from .descriptors_wacom import (
+    wacom_pth660_v145,
+    wacom_pth660_v150,
+    wacom_pth860_v145,
+    wacom_pth860_v150,
+    wacom_pth460_v105,
+)
+
+import attr
+from enum import Enum
+from hidtools.hut import HUT
+from hidtools.hid import HidUnit
+from . import base
+import libevdev
+import pytest
+
+import logging
+
+logger = logging.getLogger("hidtools.test.wacom")
+
+KERNEL_MODULE = ("wacom", "wacom")
+
+
+class ProximityState(Enum):
+    """
+    Enumeration of allowed proximity states.
+    """
+
+    # Tool is not able to be sensed by the device
+    OUT = 0
+
+    # Tool is close enough to be sensed, but some data may be invalid
+    # or inaccurate
+    IN_PROXIMITY = 1
+
+    # Tool is close enough to be sensed with high accuracy. All data
+    # valid.
+    IN_RANGE = 2
+
+    def fill(self, reportdata):
+        """Fill a report with approrpiate HID properties/values."""
+        reportdata.inrange = self in [ProximityState.IN_RANGE]
+        reportdata.wacomsense = self in [
+            ProximityState.IN_PROXIMITY,
+            ProximityState.IN_RANGE,
+        ]
+
+
+class ReportData:
+    """
+    Placeholder for HID report values.
+    """
+
+    pass
+
+
+@attr.s
+class Buttons:
+    """
+    Stylus button state.
+
+    Describes the state of each of the buttons / "side switches" that
+    may be present on a stylus. Buttons set to 'None' indicate the
+    state is "unchanged" since the previous event.
+    """
+
+    primary = attr.ib(default=None)
+    secondary = attr.ib(default=None)
+    tertiary = attr.ib(default=None)
+
+    @staticmethod
+    def clear():
+        """Button object with all states cleared."""
+        return Buttons(False, False, False)
+
+    def fill(self, reportdata):
+        """Fill a report with approrpiate HID properties/values."""
+        reportdata.barrelswitch = int(self.primary or 0)
+        reportdata.secondarybarrelswitch = int(self.secondary or 0)
+        reportdata.b3 = int(self.tertiary or 0)
+
+
+@attr.s
+class ToolID:
+    """
+    Stylus tool identifiers.
+
+    Contains values used to identify a specific stylus, e.g. its serial
+    number and tool-type identifier. Values of ``0`` may sometimes be
+    used for the out-of-range condition.
+    """
+
+    serial = attr.ib()
+    tooltype = attr.ib()
+
+    @staticmethod
+    def clear():
+        """ToolID object with all fields cleared."""
+        return ToolID(0, 0)
+
+    def fill(self, reportdata):
+        """Fill a report with approrpiate HID properties/values."""
+        reportdata.transducerserialnumber = self.serial & 0xFFFFFFFF
+        reportdata.serialhi = (self.serial >> 32) & 0xFFFFFFFF
+        reportdata.tooltype = self.tooltype
+
+
+@attr.s
+class PhysRange:
+    """
+    Range of HID physical values, with units.
+    """
+
+    unit = attr.ib()
+    min_size = attr.ib()
+    max_size = attr.ib()
+
+    CENTIMETER = HidUnit.from_string("SILinear: cm")
+    DEGREE = HidUnit.from_string("EnglishRotation: deg")
+
+    def contains(self, field):
+        """
+        Check if the physical size of the provided field is in range.
+
+        Compare the physical size described by the provided HID field
+        against the range of sizes described by this object. This is
+        an exclusive range comparison (e.g. 0 cm is not within the
+        range 0 cm - 5 cm) and exact unit comparison (e.g. 1 inch is
+        not within the range 0 cm - 5 cm).
+        """
+        phys_size = (field.physical_max - field.physical_min) * 10 ** (field.unit_exp)
+        return (
+            field.unit == self.unit.value
+            and phys_size > self.min_size
+            and phys_size < self.max_size
+        )
+
+
+class BaseTablet(base.UHIDTestDevice):
+    """
+    Skeleton object for all kinds of tablet devices.
+    """
+
+    def __init__(self, rdesc, name=None, info=None):
+        assert rdesc is not None
+        super().__init__(name, "Pen", input_info=info, rdesc=rdesc)
+        self.buttons = Buttons.clear()
+        self.toolid = ToolID.clear()
+        self.proximity = ProximityState.OUT
+        self.offset = 0
+        self.ring = -1
+        self.ek0 = False
+
+    def match_evdev_rule(self, application, evdev):
+        """
+        Filter out evdev nodes based on the requested application.
+
+        The Wacom driver may create several device nodes for each USB
+        interface device. It is crucial that we run tests with the
+        expected device node or things will obviously go off the rails.
+        Use the Wacom driver's usual naming conventions to apply a
+        sensible default filter.
+        """
+        if application in ["Pen", "Pad"]:
+            return evdev.name.endswith(application)
+        else:
+            return True
+
+    def create_report(
+        self, x, y, pressure, buttons=None, toolid=None, proximity=None, reportID=None
+    ):
+        """
+        Return an input report for this device.
+
+        :param x: absolute x
+        :param y: absolute y
+        :param pressure: pressure
+        :param buttons: stylus button state. Use ``None`` for unchanged.
+        :param toolid: tool identifiers. Use ``None`` for unchanged.
+        :param proximity: a ProximityState indicating the sensor's ability
+             to detect and report attributes of this tool. Use ``None``
+             for unchanged.
+        :param reportID: the numeric report ID for this report, if needed
+        """
+        if buttons is not None:
+            self.buttons = buttons
+        buttons = self.buttons
+
+        if toolid is not None:
+            self.toolid = toolid
+        toolid = self.toolid
+
+        if proximity is not None:
+            self.proximity = proximity
+        proximity = self.proximity
+
+        reportID = reportID or self.default_reportID
+
+        report = ReportData()
+        report.x = x
+        report.y = y
+        report.tippressure = pressure
+        report.tipswitch = pressure > 0
+        buttons.fill(report)
+        proximity.fill(report)
+        toolid.fill(report)
+
+        return super().create_report(report, reportID=reportID)
+
+    def create_report_heartbeat(self, reportID):
+        """
+        Return a heartbeat input report for this device.
+
+        Heartbeat reports generally contain battery status information,
+        among other things.
+        """
+        report = ReportData()
+        report.wacombatterycharging = 1
+        return super().create_report(report, reportID=reportID)
+
+    def create_report_pad(self, reportID, ring, ek0):
+        report = ReportData()
+
+        if ring is not None:
+            self.ring = ring
+        ring = self.ring
+
+        if ek0 is not None:
+            self.ek0 = ek0
+        ek0 = self.ek0
+
+        if ring >= 0:
+            report.wacomtouchring = ring
+            report.wacomtouchringstatus = 1
+        else:
+            report.wacomtouchring = 0x7F
+            report.wacomtouchringstatus = 0
+
+        report.wacomexpresskey00 = ek0
+        return super().create_report(report, reportID=reportID)
+
+    def event(self, x, y, pressure, buttons=None, toolid=None, proximity=None):
+        """
+        Send an input event on the default report ID.
+
+        :param x: absolute x
+        :param y: absolute y
+        :param buttons: stylus button state. Use ``None`` for unchanged.
+        :param toolid: tool identifiers. Use ``None`` for unchanged.
+        :param proximity: a ProximityState indicating the sensor's ability
+             to detect and report attributes of this tool. Use ``None``
+             for unchanged.
+        """
+        r = self.create_report(x, y, pressure, buttons, toolid, proximity)
+        self.call_input_event(r)
+        return [r]
+
+    def event_heartbeat(self, reportID):
+        """
+        Send a heartbeat event on the requested report ID.
+        """
+        r = self.create_report_heartbeat(reportID)
+        self.call_input_event(r)
+        return [r]
+
+    def event_pad(self, reportID, ring=None, ek0=None):
+        """
+        Send a pad event on the requested report ID.
+        """
+        r = self.create_report_pad(reportID, ring, ek0)
+        self.call_input_event(r)
+        return [r]
+
+    def get_report(self, req, rnum, rtype):
+        if rtype != self.UHID_FEATURE_REPORT:
+            return (1, [])
+
+        rdesc = None
+        for v in self.parsed_rdesc.feature_reports.values():
+            if v.report_ID == rnum:
+                rdesc = v
+
+        if rdesc is None:
+            return (1, [])
+
+        result = (1, [])
+        result = self.create_report_offset(rdesc) or result
+        return result
+
+    def create_report_offset(self, rdesc):
+        require = [
+            "Wacom Offset Left",
+            "Wacom Offset Top",
+            "Wacom Offset Right",
+            "Wacom Offset Bottom",
+        ]
+        if not set(require).issubset(set([f.usage_name for f in rdesc])):
+            return None
+
+        report = ReportData()
+        report.wacomoffsetleft = self.offset
+        report.wacomoffsettop = self.offset
+        report.wacomoffsetright = self.offset
+        report.wacomoffsetbottom = self.offset
+        r = rdesc.create_report([report], None)
+        return (0, r)
+
+
+class OpaqueTablet(BaseTablet):
+    """
+    Bare-bones opaque tablet with a minimum of features.
+
+    A tablet stripped down to its absolute core. It is capable of
+    reporting X/Y position and if the pen is in contact. No pressure,
+    no barrel switches, no eraser. Notably it *does* report an "In
+    Range" flag, but this is only because the Wacom driver expects
+    one to function properly. The device uses only standard HID usages,
+    not any of Wacom's vendor-defined pages.
+    """
+
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x0D,                     # . Usage Page (Digitizer),
+        0x09, 0x01,                     # . Usage (Digitizer),
+        0xA1, 0x01,                     # . Collection (Application),
+        0x85, 0x01,                     # .     Report ID (1),
+        0x09, 0x20,                     # .     Usage (Stylus),
+        0xA1, 0x00,                     # .     Collection (Physical),
+        0x09, 0x42,                     # .         Usage (Tip Switch),
+        0x09, 0x32,                     # .         Usage (In Range),
+        0x15, 0x00,                     # .         Logical Minimum (0),
+        0x25, 0x01,                     # .         Logical Maximum (1),
+        0x75, 0x01,                     # .         Report Size (1),
+        0x95, 0x02,                     # .         Report Count (2),
+        0x81, 0x02,                     # .         Input (Variable),
+        0x95, 0x06,                     # .         Report Count (6),
+        0x81, 0x03,                     # .         Input (Constant, Variable),
+        0x05, 0x01,                     # .         Usage Page (Desktop),
+        0x09, 0x30,                     # .         Usage (X),
+        0x27, 0x80, 0x3E, 0x00, 0x00,   # .         Logical Maximum (16000),
+        0x47, 0x80, 0x3E, 0x00, 0x00,   # .         Physical Maximum (16000),
+        0x65, 0x11,                     # .         Unit (Centimeter),
+        0x55, 0x0D,                     # .         Unit Exponent (13),
+        0x75, 0x10,                     # .         Report Size (16),
+        0x95, 0x01,                     # .         Report Count (1),
+        0x81, 0x02,                     # .         Input (Variable),
+        0x09, 0x31,                     # .         Usage (Y),
+        0x27, 0x28, 0x23, 0x00, 0x00,   # .         Logical Maximum (9000),
+        0x47, 0x28, 0x23, 0x00, 0x00,   # .         Physical Maximum (9000),
+        0x81, 0x02,                     # .         Input (Variable),
+        0xC0,                           # .     End Collection,
+        0xC0,                           # . End Collection,
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)):
+        super().__init__(rdesc, name, info)
+        self.default_reportID = 1
+
+
+class OpaqueCTLTablet(BaseTablet):
+    """
+    Opaque tablet similar to something in the CTL product line.
+
+    A pen-only tablet with most basic features you would expect from
+    an actual device. Position, eraser, pressure, barrel buttons.
+    Uses the Wacom vendor-defined usage page.
+    """
+
+    # fmt: off
+    report_descriptor = [
+        0x06, 0x0D, 0xFF,               # . Usage Page (Vnd Wacom Emr),
+        0x09, 0x01,                     # . Usage (Digitizer),
+        0xA1, 0x01,                     # . Collection (Application),
+        0x85, 0x10,                     # .     Report ID (16),
+        0x09, 0x20,                     # .     Usage (Stylus),
+        0x35, 0x00,                     # .     Physical Minimum (0),
+        0x45, 0x00,                     # .     Physical Maximum (0),
+        0x15, 0x00,                     # .     Logical Minimum (0),
+        0x25, 0x01,                     # .     Logical Maximum (1),
+        0xA1, 0x00,                     # .     Collection (Physical),
+        0x09, 0x42,                     # .         Usage (Tip Switch),
+        0x09, 0x44,                     # .         Usage (Barrel Switch),
+        0x09, 0x5A,                     # .         Usage (Secondary Barrel Switch),
+        0x09, 0x45,                     # .         Usage (Eraser),
+        0x09, 0x3C,                     # .         Usage (Invert),
+        0x09, 0x32,                     # .         Usage (In Range),
+        0x09, 0x36,                     # .         Usage (In Proximity),
+        0x25, 0x01,                     # .         Logical Maximum (1),
+        0x75, 0x01,                     # .         Report Size (1),
+        0x95, 0x07,                     # .         Report Count (7),
+        0x81, 0x02,                     # .         Input (Variable),
+        0x95, 0x01,                     # .         Report Count (1),
+        0x81, 0x03,                     # .         Input (Constant, Variable),
+        0x0A, 0x30, 0x01,               # .         Usage (X),
+        0x65, 0x11,                     # .         Unit (Centimeter),
+        0x55, 0x0D,                     # .         Unit Exponent (13),
+        0x47, 0x80, 0x3E, 0x00, 0x00,   # .         Physical Maximum (16000),
+        0x27, 0x80, 0x3E, 0x00, 0x00,   # .         Logical Maximum (16000),
+        0x75, 0x18,                     # .         Report Size (24),
+        0x95, 0x01,                     # .         Report Count (1),
+        0x81, 0x02,                     # .         Input (Variable),
+        0x0A, 0x31, 0x01,               # .         Usage (Y),
+        0x47, 0x28, 0x23, 0x00, 0x00,   # .         Physical Maximum (9000),
+        0x27, 0x28, 0x23, 0x00, 0x00,   # .         Logical Maximum (9000),
+        0x81, 0x02,                     # .         Input (Variable),
+        0x09, 0x30,                     # .         Usage (Tip Pressure),
+        0x55, 0x00,                     # .         Unit Exponent (0),
+        0x65, 0x00,                     # .         Unit,
+        0x47, 0x00, 0x00, 0x00, 0x00,   # .         Physical Maximum (0),
+        0x26, 0xFF, 0x0F,               # .         Logical Maximum (4095),
+        0x75, 0x10,                     # .         Report Size (16),
+        0x81, 0x02,                     # .         Input (Variable),
+        0x75, 0x08,                     # .         Report Size (8),
+        0x95, 0x06,                     # .         Report Count (6),
+        0x81, 0x03,                     # .         Input (Constant, Variable),
+        0x0A, 0x32, 0x01,               # .         Usage (Z),
+        0x25, 0x3F,                     # .         Logical Maximum (63),
+        0x75, 0x08,                     # .         Report Size (8),
+        0x95, 0x01,                     # .         Report Count (1),
+        0x81, 0x02,                     # .         Input (Variable),
+        0x09, 0x5B,                     # .         Usage (Transducer Serial Number),
+        0x09, 0x5C,                     # .         Usage (Transducer Serial Number Hi),
+        0x17, 0x00, 0x00, 0x00, 0x80,   # .         Logical Minimum (-2147483648),
+        0x27, 0xFF, 0xFF, 0xFF, 0x7F,   # .         Logical Maximum (2147483647),
+        0x75, 0x20,                     # .         Report Size (32),
+        0x95, 0x02,                     # .         Report Count (2),
+        0x81, 0x02,                     # .         Input (Variable),
+        0x09, 0x77,                     # .         Usage (Tool Type),
+        0x15, 0x00,                     # .         Logical Minimum (0),
+        0x26, 0xFF, 0x0F,               # .         Logical Maximum (4095),
+        0x75, 0x10,                     # .         Report Size (16),
+        0x95, 0x01,                     # .         Report Count (1),
+        0x81, 0x02,                     # .         Input (Variable),
+        0xC0,                           # .     End Collection,
+        0xC0                            # . End Collection
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)):
+        super().__init__(rdesc, name, info)
+        self.default_reportID = 16
+
+
+class PTHX60_Pen(BaseTablet):
+    """
+    Pen interface of a PTH-660 / PTH-860 / PTH-460 tablet.
+
+    This generation of devices are nearly identical to each other, though
+    the PTH-460 uses a slightly different descriptor construction (splits
+    the pad among several physical collections)
+    """
+
+    def __init__(self, rdesc=None, name=None, info=None):
+        super().__init__(rdesc, name, info)
+        self.default_reportID = 16
+
+
+class BaseTest:
+    class TestTablet(base.BaseTestCase.TestUhid):
+        kernel_modules = [KERNEL_MODULE]
+
+        def sync_and_assert_events(
+            self, report, expected_events, auto_syn=True, strict=False
+        ):
+            """
+            Assert we see the expected events in response to a report.
+            """
+            uhdev = self.uhdev
+            syn_event = self.syn_event
+            if auto_syn:
+                expected_events.append(syn_event)
+            actual_events = uhdev.next_sync_events()
+            self.debug_reports(report, uhdev, actual_events)
+            if strict:
+                self.assertInputEvents(expected_events, actual_events)
+            else:
+                self.assertInputEventsIn(expected_events, actual_events)
+
+        def get_usages(self, uhdev):
+            def get_report_usages(report):
+                application = report.application
+                for field in report.fields:
+                    if field.usages is not None:
+                        for usage in field.usages:
+                            yield (field, usage, application)
+                    else:
+                        yield (field, field.usage, application)
+
+            desc = uhdev.parsed_rdesc
+            reports = [
+                *desc.input_reports.values(),
+                *desc.feature_reports.values(),
+                *desc.output_reports.values(),
+            ]
+            for report in reports:
+                for usage in get_report_usages(report):
+                    yield usage
+
+        def assertName(self, uhdev):
+            """
+            Assert that the name is as we expect.
+
+            The Wacom driver applies a number of decorations to the name
+            provided by the hardware. We cannot rely on the definition of
+            this assertion from the base class to work properly.
+            """
+            evdev = uhdev.get_evdev()
+            expected_name = uhdev.name + " Pen"
+            if "wacom" not in expected_name.lower():
+                expected_name = "Wacom " + expected_name
+            assert evdev.name == expected_name
+
+        def test_descriptor_physicals(self):
+            """
+            Verify that all HID usages which should have a physical range
+            actually do, and those which shouldn't don't. Also verify that
+            the associated unit is correct and within a sensible range.
+            """
+
+            def usage_id(page_name, usage_name):
+                page = HUT.usage_page_from_name(page_name)
+                return (page.page_id << 16) | page[usage_name].usage
+
+            required = {
+                usage_id("Generic Desktop", "X"): PhysRange(
+                    PhysRange.CENTIMETER, 5, 150
+                ),
+                usage_id("Generic Desktop", "Y"): PhysRange(
+                    PhysRange.CENTIMETER, 5, 150
+                ),
+                usage_id("Digitizers", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),
+                usage_id("Digitizers", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),
+                usage_id("Digitizers", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360),
+                usage_id("Wacom", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),
+                usage_id("Wacom", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),
+                usage_id("Wacom", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360),
+                usage_id("Wacom", "X"): PhysRange(PhysRange.CENTIMETER, 5, 150),
+                usage_id("Wacom", "Y"): PhysRange(PhysRange.CENTIMETER, 5, 150),
+                usage_id("Wacom", "Wacom TouchRing"): PhysRange(
+                    PhysRange.DEGREE, 358, 360
+                ),
+                usage_id("Wacom", "Wacom Offset Left"): PhysRange(
+                    PhysRange.CENTIMETER, 0, 0.5
+                ),
+                usage_id("Wacom", "Wacom Offset Top"): PhysRange(
+                    PhysRange.CENTIMETER, 0, 0.5
+                ),
+                usage_id("Wacom", "Wacom Offset Right"): PhysRange(
+                    PhysRange.CENTIMETER, 0, 0.5
+                ),
+                usage_id("Wacom", "Wacom Offset Bottom"): PhysRange(
+                    PhysRange.CENTIMETER, 0, 0.5
+                ),
+            }
+            for field, usage, application in self.get_usages(self.uhdev):
+                if application == usage_id("Generic Desktop", "Mouse"):
+                    # Ignore the vestigial Mouse collection which exists
+                    # on Wacom tablets only for backwards compatibility.
+                    continue
+
+                expect_physical = usage in required
+
+                phys_set = field.physical_min != 0 or field.physical_max != 0
+                assert phys_set == expect_physical
+
+                unit_set = field.unit != 0
+                assert unit_set == expect_physical
+
+                if unit_set:
+                    assert required[usage].contains(field)
+
+        def test_prop_direct(self):
+            """
+            Todo: Verify that INPUT_PROP_DIRECT is set on display devices.
+            """
+            pass
+
+        def test_prop_pointer(self):
+            """
+            Todo: Verify that INPUT_PROP_POINTER is set on opaque devices.
+            """
+            pass
+
+
+class TestOpaqueTablet(BaseTest.TestTablet):
+    def create_device(self):
+        return OpaqueTablet()
+
+    def test_sanity(self):
+        """
+        Bring a pen into contact with the tablet, then remove it.
+
+        Ensure that we get the basic tool/touch/motion events that should
+        be sent by the driver.
+        """
+        uhdev = self.uhdev
+
+        self.sync_and_assert_events(
+            uhdev.event(
+                100,
+                200,
+                pressure=300,
+                buttons=Buttons.clear(),
+                toolid=ToolID(serial=1, tooltype=1),
+                proximity=ProximityState.IN_RANGE,
+            ),
+            [
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1),
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100),
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200),
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1),
+            ],
+        )
+
+        self.sync_and_assert_events(
+            uhdev.event(110, 220, pressure=0),
+            [
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110),
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 220),
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0),
+            ],
+        )
+
+        self.sync_and_assert_events(
+            uhdev.event(
+                120,
+                230,
+                pressure=0,
+                toolid=ToolID.clear(),
+                proximity=ProximityState.OUT,
+            ),
+            [
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0),
+            ],
+        )
+
+        self.sync_and_assert_events(
+            uhdev.event(130, 240, pressure=0), [], auto_syn=False, strict=True
+        )
+
+
+class TestOpaqueCTLTablet(TestOpaqueTablet):
+    def create_device(self):
+        return OpaqueCTLTablet()
+
+    def test_buttons(self):
+        """
+        Test that the barrel buttons (side switches) work as expected.
+
+        Press and release each button individually to verify that we get
+        the expected events.
+        """
+        uhdev = self.uhdev
+
+        self.sync_and_assert_events(
+            uhdev.event(
+                100,
+                200,
+                pressure=0,
+                buttons=Buttons.clear(),
+                toolid=ToolID(serial=1, tooltype=1),
+                proximity=ProximityState.IN_RANGE,
+            ),
+            [
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1),
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100),
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200),
+                libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
+            ],
+        )
+
+        self.sync_and_assert_events(
+            uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=True)),
+            [
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1),
+                libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
+            ],
+        )
+
+        self.sync_and_assert_events(
+            uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=False)),
+            [
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0),
+                libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
+            ],
+        )
+
+        self.sync_and_assert_events(
+            uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=True)),
+            [
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 1),
+                libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
+            ],
+        )
+
+        self.sync_and_assert_events(
+            uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=False)),
+            [
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 0),
+                libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
+            ],
+        )
+
+
+PTHX60_Devices = [
+    {"rdesc": wacom_pth660_v145, "info": (0x3, 0x056A, 0x0357)},
+    {"rdesc": wacom_pth660_v150, "info": (0x3, 0x056A, 0x0357)},
+    {"rdesc": wacom_pth860_v145, "info": (0x3, 0x056A, 0x0358)},
+    {"rdesc": wacom_pth860_v150, "info": (0x3, 0x056A, 0x0358)},
+    {"rdesc": wacom_pth460_v105, "info": (0x3, 0x056A, 0x0392)},
+]
+
+PTHX60_Names = [
+    "PTH-660/v145",
+    "PTH-660/v150",
+    "PTH-860/v145",
+    "PTH-860/v150",
+    "PTH-460/v105",
+]
+
+
+class TestPTHX60_Pen(TestOpaqueCTLTablet):
+    @pytest.fixture(
+        autouse=True, scope="class", params=PTHX60_Devices, ids=PTHX60_Names
+    )
+    def set_device_params(self, request):
+        request.cls.device_params = request.param
+
+    def create_device(self):
+        return PTHX60_Pen(**self.device_params)
+
+    @pytest.mark.xfail
+    def test_descriptor_physicals(self):
+        # XFAIL: Various documented errata
+        super().test_descriptor_physicals()
+
+    def test_heartbeat_spurious(self):
+        """
+        Test that the heartbeat report does not send spurious events.
+        """
+        uhdev = self.uhdev
+
+        self.sync_and_assert_events(
+            uhdev.event(
+                100,
+                200,
+                pressure=300,
+                buttons=Buttons.clear(),
+                toolid=ToolID(serial=1, tooltype=0x822),
+                proximity=ProximityState.IN_RANGE,
+            ),
+            [
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1),
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100),
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200),
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1),
+            ],
+        )
+
+        # Exactly zero events: not even a SYN
+        self.sync_and_assert_events(
+            uhdev.event_heartbeat(19), [], auto_syn=False, strict=True
+        )
+
+        self.sync_and_assert_events(
+            uhdev.event(110, 200, pressure=300),
+            [
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110),
+            ],
+        )
+
+    def test_empty_pad_sync(self):
+        self.empty_pad_sync(num=3, denom=16, reverse=True)
+
+    def empty_pad_sync(self, num, denom, reverse):
+        """
+        Test that multiple pad collections do not trigger empty syncs.
+        """
+
+        def offset_rotation(value):
+            """
+            Offset touchring rotation values by the same factor as the
+            Linux kernel. Tablets historically don't use the same origin
+            as HID, and it sometimes changes from tablet to tablet...
+            """
+            evdev = self.uhdev.get_evdev()
+            info = evdev.absinfo[libevdev.EV_ABS.ABS_WHEEL]
+            delta = info.maximum - info.minimum + 1
+            if reverse:
+                value = info.maximum - value
+            value += num * delta // denom
+            if value > info.maximum:
+                value -= delta
+            elif value < info.minimum:
+                value += delta
+            return value
+
+        uhdev = self.uhdev
+        uhdev.application = "Pad"
+        evdev = uhdev.get_evdev()
+
+        print(evdev.name)
+        self.sync_and_assert_events(
+            uhdev.event_pad(reportID=17, ring=0, ek0=1),
+            [
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 1),
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(0)),
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_MISC, 15),
+            ],
+        )
+
+        self.sync_and_assert_events(
+            uhdev.event_pad(reportID=17, ring=1, ek0=1),
+            [libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(1))],
+        )
+
+        self.sync_and_assert_events(
+            uhdev.event_pad(reportID=17, ring=2, ek0=0),
+            [
+                libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(2)),
+                libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 0),
+            ],
+        )
index 90f3415..681b906 100755 (executable)
@@ -16,7 +16,6 @@ x86_64)
        exit 1
        ;;
 esac
-DEFAULT_COMMAND="./hid_bpf"
 SCRIPT_DIR="$(dirname $(realpath $0))"
 OUTPUT_DIR="$SCRIPT_DIR/results"
 KCONFIG_REL_PATHS=("${SCRIPT_DIR}/config" "${SCRIPT_DIR}/config.common" "${SCRIPT_DIR}/config.${ARCH}")
@@ -25,7 +24,10 @@ NUM_COMPILE_JOBS="$(nproc)"
 LOG_FILE_BASE="$(date +"hid_selftests.%Y-%m-%d_%H-%M-%S")"
 LOG_FILE="${LOG_FILE_BASE}.log"
 EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
-CONTAINER_IMAGE="registry.fedoraproject.org/fedora:36"
+CONTAINER_IMAGE="registry.freedesktop.org/libevdev/hid-tools/fedora/37:2023-02-17.1"
+
+TARGETS="${TARGETS:=$(basename ${SCRIPT_DIR})}"
+DEFAULT_COMMAND="pip3 install hid-tools; make -C tools/testing/selftests TARGETS=${TARGETS} run_tests"
 
 usage()
 {
@@ -33,9 +35,9 @@ usage()
 Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>]
 
 <command> is the command you would normally run when you are in
-tools/testing/selftests/bpf. e.g:
+the source kernel direcory. e.g:
 
-       $0 -- ./hid_bpf
+       $0 -- ./tools/testing/selftests/hid/hid_bpf
 
 If no command is specified and a debug shell (-s) is not requested,
 "${DEFAULT_COMMAND}" will be run by default.
@@ -43,11 +45,11 @@ If no command is specified and a debug shell (-s) is not requested,
 If you build your kernel using KBUILD_OUTPUT= or O= options, these
 can be passed as environment variables to the script:
 
-  O=<kernel_build_path> $0 -- ./hid_bpf
+  O=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
 
 or
 
-  KBUILD_OUTPUT=<kernel_build_path> $0 -- ./hid_bpf
+  KBUILD_OUTPUT=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
 
 Options:
 
@@ -91,11 +93,14 @@ update_selftests()
 
 run_vm()
 {
-       local b2c="$1"
-       local kernel_bzimage="$2"
-       local command="$3"
+       local run_dir="$1"
+       local b2c="$2"
+       local kernel_bzimage="$3"
+       local command="$4"
        local post_command=""
 
+       cd "${run_dir}"
+
        if ! which "${QEMU_BINARY}" &> /dev/null; then
                cat <<EOF
 Could not find ${QEMU_BINARY}
@@ -273,7 +278,7 @@ main()
        fi
 
        update_selftests "${kernel_checkout}" "${make_command}"
-       run_vm $b2c "${kernel_bzimage}" "${command}"
+       run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}"
        if [[ "${debug_shell}" != "yes" ]]; then
                echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
        fi