mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-09 23:39:18 +00:00
for-linus-2023042601
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIVAwUAZEmThqZi849r7WBJAQLKTxAAwLKvk8xCUVPardg2tYxLSaNJAeSgo4L0 CKgB52kXa5R6+L3OApKgkREkj0TotNpNA5Gc/1DlPiRrUXPAj7g+NS2ID8SfXOUd Iii42DoVZli03kG2xoLgU9Fy7mJ1JdfCC6dhP95y6oDzsZqb87M8sk+2G59KVhXO KXaVMSU68+AKdXwDCbxhDwR+CH0YpGUqZxURKYycIZQhWPCssBDHorqJLLHzodSx jk+OKAqTAURjt3Pqqn6BwyOXmjhsomUfJ2z01i/I062+zFTjy+6RBhqqbOPBpJ0w D34nDwunyhlha11u1dJoP2lpmujJvliTUPM0ddeZTTMbRf58LzpxtVBPSsy389uI pqC14OdUDEvlp4WX4Xkj7K2m4HpE9hYL1gF2ebnwvyS2f1Sjti1mKSYvs/cJk5nY nlivD7lmj4Cc0SDasyfqnkP9TUxF+1SNoDAImtku/ajtIGsguveU8kYZtZxKj3WO A0LZKabKH/jEvJug/aQA0l5+AdP88mGLre+WYc6xh7IxTlsXnYeLpaYOdGZ19WCQ tjpc+z+nPSszc0wQs2TsJSxQpkzcO+8qS+h9GFhBm5DREfVHR8wrsMrdxot55zvm +j9sMN8oD7RQwxtG9DUF2wzIyjKe/k9b3qbe/BApC65WsMiXdSvlhJKhvNZQs+w3 1OGeT5LJpqc= =JhWj -----END PGP SIGNATURE----- Merge tag 'for-linus-2023042601' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid 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 ...
This commit is contained in:
commit
34da76dca4
@ -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>
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
|
@ -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"
|
||||
|
||||
|
@ -415,6 +415,7 @@
|
||||
#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
|
||||
@ -721,12 +722,19 @@
|
||||
#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
|
||||
|
@ -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);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||
|
@ -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);
|
||||
@ -947,6 +969,54 @@ print_version:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* 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",
|
||||
@ -1745,6 +1813,164 @@ static int hidpp_set_wireless_feature_index(struct hidpp_device *hidpp)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* 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 },
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
while (delta_ms < max_subcmd_rate_ms &&
|
||||
ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) {
|
||||
if (unlikely(ctlr->ctlr_state != JOYCON_CTLR_STATE_READ))
|
||||
return;
|
||||
|
||||
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);
|
||||
|
@ -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_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_PENSKETCH_M912), 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_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_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_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 },
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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 },
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
7
tools/testing/selftests/hid/hid-apple.sh
Executable file
7
tools/testing/selftests/hid/hid-apple.sh
Executable file
@ -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
|
7
tools/testing/selftests/hid/hid-core.sh
Executable file
7
tools/testing/selftests/hid/hid-core.sh
Executable file
@ -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
|
7
tools/testing/selftests/hid/hid-gamepad.sh
Executable file
7
tools/testing/selftests/hid/hid-gamepad.sh
Executable file
@ -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
|
7
tools/testing/selftests/hid/hid-ite.sh
Executable file
7
tools/testing/selftests/hid/hid-ite.sh
Executable file
@ -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
|
7
tools/testing/selftests/hid/hid-keyboard.sh
Executable file
7
tools/testing/selftests/hid/hid-keyboard.sh
Executable file
@ -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
|
7
tools/testing/selftests/hid/hid-mouse.sh
Executable file
7
tools/testing/selftests/hid/hid-mouse.sh
Executable file
@ -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
|
7
tools/testing/selftests/hid/hid-multitouch.sh
Executable file
7
tools/testing/selftests/hid/hid-multitouch.sh
Executable file
@ -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
|
7
tools/testing/selftests/hid/hid-sony.sh
Executable file
7
tools/testing/selftests/hid/hid-sony.sh
Executable file
@ -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
|
7
tools/testing/selftests/hid/hid-tablet.sh
Executable file
7
tools/testing/selftests/hid/hid-tablet.sh
Executable file
@ -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
|
7
tools/testing/selftests/hid/hid-usb_crash.sh
Executable file
7
tools/testing/selftests/hid/hid-usb_crash.sh
Executable file
@ -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
|
7
tools/testing/selftests/hid/hid-wacom.sh
Executable file
7
tools/testing/selftests/hid/hid-wacom.sh
Executable file
@ -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
|
28
tools/testing/selftests/hid/run-hid-tools-tests.sh
Executable file
28
tools/testing/selftests/hid/run-hid-tools-tests.sh
Executable file
@ -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
|
3
tools/testing/selftests/hid/settings
Normal file
3
tools/testing/selftests/hid/settings
Normal file
@ -0,0 +1,3 @@
|
||||
# HID tests can be long, so give a little bit more time
|
||||
# to them
|
||||
timeout=200
|
2
tools/testing/selftests/hid/tests/__init__.py
Normal file
2
tools/testing/selftests/hid/tests/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Just to make sphinx-apidoc document this directory
|
345
tools/testing/selftests/hid/tests/base.py
Normal file
345
tools/testing/selftests/hid/tests/base.py
Normal file
@ -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
|
81
tools/testing/selftests/hid/tests/conftest.py
Normal file
81
tools/testing/selftests/hid/tests/conftest.py
Normal file
@ -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)
|
1360
tools/testing/selftests/hid/tests/descriptors_wacom.py
Normal file
1360
tools/testing/selftests/hid/tests/descriptors_wacom.py
Normal file
File diff suppressed because it is too large
Load Diff
440
tools/testing/selftests/hid/tests/test_apple_keyboard.py
Normal file
440
tools/testing/selftests/hid/tests/test_apple_keyboard.py
Normal file
@ -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
|
209
tools/testing/selftests/hid/tests/test_gamepad.py
Normal file
209
tools/testing/selftests/hid/tests/test_gamepad.py
Normal file
@ -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()
|
154
tools/testing/selftests/hid/tests/test_hid_core.py
Normal file
154
tools/testing/selftests/hid/tests/test_hid_core.py
Normal file
@ -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
|
166
tools/testing/selftests/hid/tests/test_ite_keyboard.py
Normal file
166
tools/testing/selftests/hid/tests/test_ite_keyboard.py
Normal file
@ -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)
|
485
tools/testing/selftests/hid/tests/test_keyboard.py
Normal file
485
tools/testing/selftests/hid/tests/test_keyboard.py
Normal file
@ -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()
|
977
tools/testing/selftests/hid/tests/test_mouse.py
Normal file
977
tools/testing/selftests/hid/tests/test_mouse.py
Normal file
@ -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 == []
|
2088
tools/testing/selftests/hid/tests/test_multitouch.py
Normal file
2088
tools/testing/selftests/hid/tests/test_multitouch.py
Normal file
File diff suppressed because one or more lines are too long
342
tools/testing/selftests/hid/tests/test_sony.py
Normal file
342
tools/testing/selftests/hid/tests/test_sony.py
Normal file
@ -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
|
872
tools/testing/selftests/hid/tests/test_tablet.py
Normal file
872
tools/testing/selftests/hid/tests/test_tablet.py
Normal file
@ -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),
|
||||
)
|
103
tools/testing/selftests/hid/tests/test_usb_crash.py
Normal file
103
tools/testing/selftests/hid/tests/test_usb_crash.py
Normal file
@ -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
|
844
tools/testing/selftests/hid/tests/test_wacom_generic.py
Normal file
844
tools/testing/selftests/hid/tests/test_wacom_generic.py
Normal file
@ -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),
|
||||
],
|
||||
)
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user