Path: blob/master/tools/testing/selftests/hid/tests/test_wacom_generic.py
26308 views
#!/bin/env python31# SPDX-License-Identifier: GPL-2.02# -*- coding: utf-8 -*-3#4# Copyright (c) 2017 Benjamin Tissoires <[email protected]>5# Copyright (c) 2017 Red Hat, Inc.6# Copyright (c) 2020 Wacom Technology Corp.7#8# Authors:9# Jason Gerecke <[email protected]>1011"""12Tests for the Wacom driver generic codepath.1314This module tests the function of the Wacom driver's generic codepath.15The generic codepath is used by devices which are not explicitly listed16in the driver's device table. It uses the device's HID descriptor to17decode reports sent by the device.18"""1920from .descriptors_wacom import (21wacom_pth660_v145,22wacom_pth660_v150,23wacom_pth860_v145,24wacom_pth860_v150,25wacom_pth460_v105,26)2728import attr29from collections import namedtuple30from enum import Enum31from hidtools.hut import HUT32from hidtools.hid import HidUnit33from . import base34from . import test_multitouch35import libevdev36import pytest3738import logging3940logger = logging.getLogger("hidtools.test.wacom")4142KERNEL_MODULE = base.KernelModule("wacom", "wacom")434445class ProximityState(Enum):46"""47Enumeration of allowed proximity states.48"""4950# Tool is not able to be sensed by the device51OUT = 05253# Tool is close enough to be sensed, but some data may be invalid54# or inaccurate55IN_PROXIMITY = 15657# Tool is close enough to be sensed with high accuracy. All data58# valid.59IN_RANGE = 26061def fill(self, reportdata):62"""Fill a report with approrpiate HID properties/values."""63reportdata.inrange = self in [ProximityState.IN_RANGE]64reportdata.wacomsense = self in [65ProximityState.IN_PROXIMITY,66ProximityState.IN_RANGE,67]686970class ReportData:71"""72Placeholder for HID report values.73"""7475pass767778@attr.s79class Buttons:80"""81Stylus button state.8283Describes the state of each of the buttons / "side switches" that84may be present on a stylus. Buttons set to 'None' indicate the85state is "unchanged" since the previous event.86"""8788primary = attr.ib(default=None)89secondary = attr.ib(default=None)90tertiary = attr.ib(default=None)9192@staticmethod93def clear():94"""Button object with all states cleared."""95return Buttons(False, False, False)9697def fill(self, reportdata):98"""Fill a report with approrpiate HID properties/values."""99reportdata.barrelswitch = int(self.primary or 0)100reportdata.secondarybarrelswitch = int(self.secondary or 0)101reportdata.b3 = int(self.tertiary or 0)102103104@attr.s105class ToolID:106"""107Stylus tool identifiers.108109Contains values used to identify a specific stylus, e.g. its serial110number and tool-type identifier. Values of ``0`` may sometimes be111used for the out-of-range condition.112"""113114serial = attr.ib()115tooltype = attr.ib()116117@staticmethod118def clear():119"""ToolID object with all fields cleared."""120return ToolID(0, 0)121122def fill(self, reportdata):123"""Fill a report with approrpiate HID properties/values."""124reportdata.transducerserialnumber = self.serial & 0xFFFFFFFF125reportdata.serialhi = (self.serial >> 32) & 0xFFFFFFFF126reportdata.tooltype = self.tooltype127128129@attr.s130class PhysRange:131"""132Range of HID physical values, with units.133"""134135unit = attr.ib()136min_size = attr.ib()137max_size = attr.ib()138139CENTIMETER = HidUnit.from_string("SILinear: cm")140DEGREE = HidUnit.from_string("EnglishRotation: deg")141142def contains(self, field):143"""144Check if the physical size of the provided field is in range.145146Compare the physical size described by the provided HID field147against the range of sizes described by this object. This is148an exclusive range comparison (e.g. 0 cm is not within the149range 0 cm - 5 cm) and exact unit comparison (e.g. 1 inch is150not within the range 0 cm - 5 cm).151"""152phys_size = (field.physical_max - field.physical_min) * 10 ** (field.unit_exp)153return (154field.unit == self.unit.value155and phys_size > self.min_size156and phys_size < self.max_size157)158159160class BaseTablet(base.UHIDTestDevice):161"""162Skeleton object for all kinds of tablet devices.163"""164165def __init__(self, rdesc, name=None, info=None):166assert rdesc is not None167super().__init__(name, "Pen", input_info=info, rdesc=rdesc)168self.buttons = Buttons.clear()169self.toolid = ToolID.clear()170self.proximity = ProximityState.OUT171self.offset = 0172self.ring = -1173self.ek0 = False174175def match_evdev_rule(self, application, evdev):176"""177Filter out evdev nodes based on the requested application.178179The Wacom driver may create several device nodes for each USB180interface device. It is crucial that we run tests with the181expected device node or things will obviously go off the rails.182Use the Wacom driver's usual naming conventions to apply a183sensible default filter.184"""185if application in ["Pen", "Pad"]:186return evdev.name.endswith(application)187else:188return True189190def create_report(191self, x, y, pressure, buttons=None, toolid=None, proximity=None, reportID=None192):193"""194Return an input report for this device.195196:param x: absolute x197:param y: absolute y198:param pressure: pressure199:param buttons: stylus button state. Use ``None`` for unchanged.200:param toolid: tool identifiers. Use ``None`` for unchanged.201:param proximity: a ProximityState indicating the sensor's ability202to detect and report attributes of this tool. Use ``None``203for unchanged.204:param reportID: the numeric report ID for this report, if needed205"""206if buttons is not None:207self.buttons = buttons208buttons = self.buttons209210if toolid is not None:211self.toolid = toolid212toolid = self.toolid213214if proximity is not None:215self.proximity = proximity216proximity = self.proximity217218reportID = reportID or self.default_reportID219220report = ReportData()221report.x = x222report.y = y223report.tippressure = pressure224report.tipswitch = pressure > 0225buttons.fill(report)226proximity.fill(report)227toolid.fill(report)228229return super().create_report(report, reportID=reportID)230231def create_report_heartbeat(self, reportID):232"""233Return a heartbeat input report for this device.234235Heartbeat reports generally contain battery status information,236among other things.237"""238report = ReportData()239report.wacombatterycharging = 1240return super().create_report(report, reportID=reportID)241242def create_report_pad(self, reportID, ring, ek0):243report = ReportData()244245if ring is not None:246self.ring = ring247ring = self.ring248249if ek0 is not None:250self.ek0 = ek0251ek0 = self.ek0252253if ring >= 0:254report.wacomtouchring = ring255report.wacomtouchringstatus = 1256else:257report.wacomtouchring = 0x7F258report.wacomtouchringstatus = 0259260report.wacomexpresskey00 = ek0261return super().create_report(report, reportID=reportID)262263def event(self, x, y, pressure, buttons=None, toolid=None, proximity=None):264"""265Send an input event on the default report ID.266267:param x: absolute x268:param y: absolute y269:param buttons: stylus button state. Use ``None`` for unchanged.270:param toolid: tool identifiers. Use ``None`` for unchanged.271:param proximity: a ProximityState indicating the sensor's ability272to detect and report attributes of this tool. Use ``None``273for unchanged.274"""275r = self.create_report(x, y, pressure, buttons, toolid, proximity)276self.call_input_event(r)277return [r]278279def event_heartbeat(self, reportID):280"""281Send a heartbeat event on the requested report ID.282"""283r = self.create_report_heartbeat(reportID)284self.call_input_event(r)285return [r]286287def event_pad(self, reportID, ring=None, ek0=None):288"""289Send a pad event on the requested report ID.290"""291r = self.create_report_pad(reportID, ring, ek0)292self.call_input_event(r)293return [r]294295def get_report(self, req, rnum, rtype):296if rtype != self.UHID_FEATURE_REPORT:297return (1, [])298299rdesc = None300for v in self.parsed_rdesc.feature_reports.values():301if v.report_ID == rnum:302rdesc = v303304if rdesc is None:305return (1, [])306307result = (1, [])308result = self.create_report_offset(rdesc) or result309return result310311def create_report_offset(self, rdesc):312require = [313"Wacom Offset Left",314"Wacom Offset Top",315"Wacom Offset Right",316"Wacom Offset Bottom",317]318if not set(require).issubset(set([f.usage_name for f in rdesc])):319return None320321report = ReportData()322report.wacomoffsetleft = self.offset323report.wacomoffsettop = self.offset324report.wacomoffsetright = self.offset325report.wacomoffsetbottom = self.offset326r = rdesc.create_report([report], None)327return (0, r)328329330class OpaqueTablet(BaseTablet):331"""332Bare-bones opaque tablet with a minimum of features.333334A tablet stripped down to its absolute core. It is capable of335reporting X/Y position and if the pen is in contact. No pressure,336no barrel switches, no eraser. Notably it *does* report an "In337Range" flag, but this is only because the Wacom driver expects338one to function properly. The device uses only standard HID usages,339not any of Wacom's vendor-defined pages.340"""341342# fmt: off343report_descriptor = [3440x05, 0x0D, # . Usage Page (Digitizer),3450x09, 0x01, # . Usage (Digitizer),3460xA1, 0x01, # . Collection (Application),3470x85, 0x01, # . Report ID (1),3480x09, 0x20, # . Usage (Stylus),3490xA1, 0x00, # . Collection (Physical),3500x09, 0x42, # . Usage (Tip Switch),3510x09, 0x32, # . Usage (In Range),3520x15, 0x00, # . Logical Minimum (0),3530x25, 0x01, # . Logical Maximum (1),3540x75, 0x01, # . Report Size (1),3550x95, 0x02, # . Report Count (2),3560x81, 0x02, # . Input (Variable),3570x95, 0x06, # . Report Count (6),3580x81, 0x03, # . Input (Constant, Variable),3590x05, 0x01, # . Usage Page (Desktop),3600x09, 0x30, # . Usage (X),3610x27, 0x80, 0x3E, 0x00, 0x00, # . Logical Maximum (16000),3620x47, 0x80, 0x3E, 0x00, 0x00, # . Physical Maximum (16000),3630x65, 0x11, # . Unit (Centimeter),3640x55, 0x0D, # . Unit Exponent (13),3650x75, 0x10, # . Report Size (16),3660x95, 0x01, # . Report Count (1),3670x81, 0x02, # . Input (Variable),3680x09, 0x31, # . Usage (Y),3690x27, 0x28, 0x23, 0x00, 0x00, # . Logical Maximum (9000),3700x47, 0x28, 0x23, 0x00, 0x00, # . Physical Maximum (9000),3710x81, 0x02, # . Input (Variable),3720xC0, # . End Collection,3730xC0, # . End Collection,374]375# fmt: on376377def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)):378super().__init__(rdesc, name, info)379self.default_reportID = 1380381382class OpaqueCTLTablet(BaseTablet):383"""384Opaque tablet similar to something in the CTL product line.385386A pen-only tablet with most basic features you would expect from387an actual device. Position, eraser, pressure, barrel buttons.388Uses the Wacom vendor-defined usage page.389"""390391# fmt: off392report_descriptor = [3930x06, 0x0D, 0xFF, # . Usage Page (Vnd Wacom Emr),3940x09, 0x01, # . Usage (Digitizer),3950xA1, 0x01, # . Collection (Application),3960x85, 0x10, # . Report ID (16),3970x09, 0x20, # . Usage (Stylus),3980x35, 0x00, # . Physical Minimum (0),3990x45, 0x00, # . Physical Maximum (0),4000x15, 0x00, # . Logical Minimum (0),4010x25, 0x01, # . Logical Maximum (1),4020xA1, 0x00, # . Collection (Physical),4030x09, 0x42, # . Usage (Tip Switch),4040x09, 0x44, # . Usage (Barrel Switch),4050x09, 0x5A, # . Usage (Secondary Barrel Switch),4060x09, 0x45, # . Usage (Eraser),4070x09, 0x3C, # . Usage (Invert),4080x09, 0x32, # . Usage (In Range),4090x09, 0x36, # . Usage (In Proximity),4100x25, 0x01, # . Logical Maximum (1),4110x75, 0x01, # . Report Size (1),4120x95, 0x07, # . Report Count (7),4130x81, 0x02, # . Input (Variable),4140x95, 0x01, # . Report Count (1),4150x81, 0x03, # . Input (Constant, Variable),4160x0A, 0x30, 0x01, # . Usage (X),4170x65, 0x11, # . Unit (Centimeter),4180x55, 0x0D, # . Unit Exponent (13),4190x47, 0x80, 0x3E, 0x00, 0x00, # . Physical Maximum (16000),4200x27, 0x80, 0x3E, 0x00, 0x00, # . Logical Maximum (16000),4210x75, 0x18, # . Report Size (24),4220x95, 0x01, # . Report Count (1),4230x81, 0x02, # . Input (Variable),4240x0A, 0x31, 0x01, # . Usage (Y),4250x47, 0x28, 0x23, 0x00, 0x00, # . Physical Maximum (9000),4260x27, 0x28, 0x23, 0x00, 0x00, # . Logical Maximum (9000),4270x81, 0x02, # . Input (Variable),4280x09, 0x30, # . Usage (Tip Pressure),4290x55, 0x00, # . Unit Exponent (0),4300x65, 0x00, # . Unit,4310x47, 0x00, 0x00, 0x00, 0x00, # . Physical Maximum (0),4320x26, 0xFF, 0x0F, # . Logical Maximum (4095),4330x75, 0x10, # . Report Size (16),4340x81, 0x02, # . Input (Variable),4350x75, 0x08, # . Report Size (8),4360x95, 0x06, # . Report Count (6),4370x81, 0x03, # . Input (Constant, Variable),4380x0A, 0x32, 0x01, # . Usage (Z),4390x25, 0x3F, # . Logical Maximum (63),4400x75, 0x08, # . Report Size (8),4410x95, 0x01, # . Report Count (1),4420x81, 0x02, # . Input (Variable),4430x09, 0x5B, # . Usage (Transducer Serial Number),4440x09, 0x5C, # . Usage (Transducer Serial Number Hi),4450x17, 0x00, 0x00, 0x00, 0x80, # . Logical Minimum (-2147483648),4460x27, 0xFF, 0xFF, 0xFF, 0x7F, # . Logical Maximum (2147483647),4470x75, 0x20, # . Report Size (32),4480x95, 0x02, # . Report Count (2),4490x81, 0x02, # . Input (Variable),4500x09, 0x77, # . Usage (Tool Type),4510x15, 0x00, # . Logical Minimum (0),4520x26, 0xFF, 0x0F, # . Logical Maximum (4095),4530x75, 0x10, # . Report Size (16),4540x95, 0x01, # . Report Count (1),4550x81, 0x02, # . Input (Variable),4560xC0, # . End Collection,4570xC0 # . End Collection458]459# fmt: on460461def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)):462super().__init__(rdesc, name, info)463self.default_reportID = 16464465466class PTHX60_Pen(BaseTablet):467"""468Pen interface of a PTH-660 / PTH-860 / PTH-460 tablet.469470This generation of devices are nearly identical to each other, though471the PTH-460 uses a slightly different descriptor construction (splits472the pad among several physical collections)473"""474475def __init__(self, rdesc=None, name=None, info=None):476super().__init__(rdesc, name, info)477self.default_reportID = 16478479480class BaseTest:481class TestTablet(base.BaseTestCase.TestUhid):482kernel_modules = [KERNEL_MODULE]483484def sync_and_assert_events(485self, report, expected_events, auto_syn=True, strict=False486):487"""488Assert we see the expected events in response to a report.489"""490uhdev = self.uhdev491syn_event = self.syn_event492if auto_syn:493expected_events.append(syn_event)494actual_events = uhdev.next_sync_events()495self.debug_reports(report, uhdev, actual_events)496if strict:497self.assertInputEvents(expected_events, actual_events)498else:499self.assertInputEventsIn(expected_events, actual_events)500501def get_usages(self, uhdev):502def get_report_usages(report):503application = report.application504for field in report.fields:505if field.usages is not None:506for usage in field.usages:507yield (field, usage, application)508else:509yield (field, field.usage, application)510511desc = uhdev.parsed_rdesc512reports = [513*desc.input_reports.values(),514*desc.feature_reports.values(),515*desc.output_reports.values(),516]517for report in reports:518for usage in get_report_usages(report):519yield usage520521def assertName(self, uhdev, type):522"""523Assert that the name is as we expect.524525The Wacom driver applies a number of decorations to the name526provided by the hardware. We cannot rely on the definition of527this assertion from the base class to work properly.528"""529evdev = uhdev.get_evdev()530expected_name = uhdev.name + type531if "wacom" not in expected_name.lower():532expected_name = "Wacom " + expected_name533assert evdev.name == expected_name534535def test_descriptor_physicals(self):536"""537Verify that all HID usages which should have a physical range538actually do, and those which shouldn't don't. Also verify that539the associated unit is correct and within a sensible range.540"""541542def usage_id(page_name, usage_name):543page = HUT.usage_page_from_name(page_name)544return (page.page_id << 16) | page[usage_name].usage545546required = {547usage_id("Generic Desktop", "X"): PhysRange(548PhysRange.CENTIMETER, 5, 150549),550usage_id("Generic Desktop", "Y"): PhysRange(551PhysRange.CENTIMETER, 5, 150552),553usage_id("Digitizers", "Width"): PhysRange(554PhysRange.CENTIMETER, 5, 150555),556usage_id("Digitizers", "Height"): PhysRange(557PhysRange.CENTIMETER, 5, 150558),559usage_id("Digitizers", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),560usage_id("Digitizers", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),561usage_id("Digitizers", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360),562usage_id("Wacom", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),563usage_id("Wacom", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),564usage_id("Wacom", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360),565usage_id("Wacom", "X"): PhysRange(PhysRange.CENTIMETER, 5, 150),566usage_id("Wacom", "Y"): PhysRange(PhysRange.CENTIMETER, 5, 150),567usage_id("Wacom", "Wacom TouchRing"): PhysRange(568PhysRange.DEGREE, 358, 360569),570usage_id("Wacom", "Wacom Offset Left"): PhysRange(571PhysRange.CENTIMETER, 0, 0.5572),573usage_id("Wacom", "Wacom Offset Top"): PhysRange(574PhysRange.CENTIMETER, 0, 0.5575),576usage_id("Wacom", "Wacom Offset Right"): PhysRange(577PhysRange.CENTIMETER, 0, 0.5578),579usage_id("Wacom", "Wacom Offset Bottom"): PhysRange(580PhysRange.CENTIMETER, 0, 0.5581),582}583for field, usage, application in self.get_usages(self.uhdev):584if application == usage_id("Generic Desktop", "Mouse"):585# Ignore the vestigial Mouse collection which exists586# on Wacom tablets only for backwards compatibility.587continue588589expect_physical = usage in required590591phys_set = field.physical_min != 0 or field.physical_max != 0592assert phys_set == expect_physical593594unit_set = field.unit != 0595assert unit_set == expect_physical596597if unit_set:598assert required[usage].contains(field)599600def test_prop_direct(self):601"""602Todo: Verify that INPUT_PROP_DIRECT is set on display devices.603"""604pass605606def test_prop_pointer(self):607"""608Todo: Verify that INPUT_PROP_POINTER is set on opaque devices.609"""610pass611612613class PenTabletTest(BaseTest.TestTablet):614def assertName(self, uhdev):615super().assertName(uhdev, " Pen")616617618class TouchTabletTest(BaseTest.TestTablet):619def assertName(self, uhdev):620super().assertName(uhdev, " Finger")621622623class TestOpaqueTablet(PenTabletTest):624def create_device(self):625return OpaqueTablet()626627def test_sanity(self):628"""629Bring a pen into contact with the tablet, then remove it.630631Ensure that we get the basic tool/touch/motion events that should632be sent by the driver.633"""634uhdev = self.uhdev635636self.sync_and_assert_events(637uhdev.event(638100,639200,640pressure=300,641buttons=Buttons.clear(),642toolid=ToolID(serial=1, tooltype=1),643proximity=ProximityState.IN_RANGE,644),645[646libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1),647libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100),648libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200),649libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1),650],651)652653self.sync_and_assert_events(654uhdev.event(110, 220, pressure=0),655[656libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110),657libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 220),658libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0),659],660)661662self.sync_and_assert_events(663uhdev.event(664120,665230,666pressure=0,667toolid=ToolID.clear(),668proximity=ProximityState.OUT,669),670[671libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0),672],673)674675self.sync_and_assert_events(676uhdev.event(130, 240, pressure=0), [], auto_syn=False, strict=True677)678679680class TestOpaqueCTLTablet(TestOpaqueTablet):681def create_device(self):682return OpaqueCTLTablet()683684def test_buttons(self):685"""686Test that the barrel buttons (side switches) work as expected.687688Press and release each button individually to verify that we get689the expected events.690"""691uhdev = self.uhdev692693self.sync_and_assert_events(694uhdev.event(695100,696200,697pressure=0,698buttons=Buttons.clear(),699toolid=ToolID(serial=1, tooltype=1),700proximity=ProximityState.IN_RANGE,701),702[703libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1),704libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100),705libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200),706libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),707],708)709710self.sync_and_assert_events(711uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=True)),712[713libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1),714libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),715],716)717718self.sync_and_assert_events(719uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=False)),720[721libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0),722libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),723],724)725726self.sync_and_assert_events(727uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=True)),728[729libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 1),730libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),731],732)733734self.sync_and_assert_events(735uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=False)),736[737libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 0),738libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),739],740)741742743PTHX60_Devices = [744{"rdesc": wacom_pth660_v145, "info": (0x3, 0x056A, 0x0357)},745{"rdesc": wacom_pth660_v150, "info": (0x3, 0x056A, 0x0357)},746{"rdesc": wacom_pth860_v145, "info": (0x3, 0x056A, 0x0358)},747{"rdesc": wacom_pth860_v150, "info": (0x3, 0x056A, 0x0358)},748{"rdesc": wacom_pth460_v105, "info": (0x3, 0x056A, 0x0392)},749]750751PTHX60_Names = [752"PTH-660/v145",753"PTH-660/v150",754"PTH-860/v145",755"PTH-860/v150",756"PTH-460/v105",757]758759760class TestPTHX60_Pen(TestOpaqueCTLTablet):761@pytest.fixture(762autouse=True, scope="class", params=PTHX60_Devices, ids=PTHX60_Names763)764def set_device_params(self, request):765request.cls.device_params = request.param766767def create_device(self):768return PTHX60_Pen(**self.device_params)769770@pytest.mark.xfail771def test_descriptor_physicals(self):772# XFAIL: Various documented errata773super().test_descriptor_physicals()774775def test_heartbeat_spurious(self):776"""777Test that the heartbeat report does not send spurious events.778"""779uhdev = self.uhdev780781self.sync_and_assert_events(782uhdev.event(783100,784200,785pressure=300,786buttons=Buttons.clear(),787toolid=ToolID(serial=1, tooltype=0x822),788proximity=ProximityState.IN_RANGE,789),790[791libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1),792libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100),793libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200),794libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1),795],796)797798# Exactly zero events: not even a SYN799self.sync_and_assert_events(800uhdev.event_heartbeat(19), [], auto_syn=False, strict=True801)802803self.sync_and_assert_events(804uhdev.event(110, 200, pressure=300),805[806libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110),807],808)809810def test_empty_pad_sync(self):811self.empty_pad_sync(num=3, denom=16, reverse=True)812813def empty_pad_sync(self, num, denom, reverse):814"""815Test that multiple pad collections do not trigger empty syncs.816"""817818def offset_rotation(value):819"""820Offset touchring rotation values by the same factor as the821Linux kernel. Tablets historically don't use the same origin822as HID, and it sometimes changes from tablet to tablet...823"""824evdev = self.uhdev.get_evdev()825info = evdev.absinfo[libevdev.EV_ABS.ABS_WHEEL]826delta = info.maximum - info.minimum + 1827if reverse:828value = info.maximum - value829value += num * delta // denom830if value > info.maximum:831value -= delta832elif value < info.minimum:833value += delta834return value835836uhdev = self.uhdev837uhdev.application = "Pad"838evdev = uhdev.get_evdev()839840print(evdev.name)841self.sync_and_assert_events(842uhdev.event_pad(reportID=17, ring=0, ek0=1),843[844libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 1),845libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(0)),846libevdev.InputEvent(libevdev.EV_ABS.ABS_MISC, 15),847],848)849850self.sync_and_assert_events(851uhdev.event_pad(reportID=17, ring=1, ek0=1),852[libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(1))],853)854855self.sync_and_assert_events(856uhdev.event_pad(reportID=17, ring=2, ek0=0),857[858libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(2)),859libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 0),860],861)862863864class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest):865ContactIds = namedtuple("ContactIds", "contact_id, tracking_id, slot_num")866867def create_device(self):868return test_multitouch.Digitizer(869"DTH 2452",870rdesc="05 0d 09 04 a1 01 85 0c 95 01 75 08 15 00 26 ff 00 81 03 09 54 81 02 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 75 08 95 0e 81 03 09 55 26 ff 00 75 08 b1 02 85 0a 06 00 ff 09 c5 96 00 01 b1 02 c0 06 00 ff 09 01 a1 01 09 01 85 13 15 00 26 ff 00 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0",871input_info=(0x3, 0x056A, 0x0383),872)873874def make_contact(self, contact_id=0, t=0):875"""876Make a single touch contact that can move over time.877878Creates a touch object that has a well-known position in space that879does not overlap with other contacts. The value of `t` may be880incremented over time to move the point along a linear path.881"""882x = 50 + 10 * contact_id + t * 11883y = 100 + 100 * contact_id + t * 11884return test_multitouch.Touch(contact_id, x, y)885886def make_contacts(self, n, t=0):887"""888Make multiple touch contacts that can move over time.889890Returns a list of `n` touch objects that are positioned at well-known891locations. The value of `t` may be incremented over time to move the892points along a linear path.893"""894return [self.make_contact(id, t) for id in range(0, n)]895896def assert_contact(self, evdev, contact_ids, t=0):897"""898Assert properties of a contact generated by make_contact.899"""900contact_id = contact_ids.contact_id901tracking_id = contact_ids.tracking_id902slot_num = contact_ids.slot_num903904x = 50 + 10 * contact_id + t * 11905y = 100 + 100 * contact_id + t * 11906907# If the data isn't supposed to be stored in any slots, there is908# nothing we can check for in the evdev stream.909if slot_num is None:910assert tracking_id == -1911return912913assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == tracking_id914if tracking_id != -1:915assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_POSITION_X] == x916assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_POSITION_Y] == y917918def assert_contacts(self, evdev, data, t=0):919"""920Assert properties of a list of contacts generated by make_contacts.921"""922for contact_ids in data:923self.assert_contact(evdev, contact_ids, t)924925def test_contact_id_0(self):926"""927Bring a finger in contact with the tablet, then hold it down and remove it.928929Ensure that even with contact ID = 0 which is usually given as an invalid930touch event by most tablets with the exception of a few, that given the931confidence bit is set to 1 it should process it as a valid touch to cover932the few tablets using contact ID = 0 as a valid touch value.933"""934uhdev = self.uhdev935evdev = uhdev.get_evdev()936937t0 = test_multitouch.Touch(0, 50, 100)938r = uhdev.event([t0])939events = uhdev.next_sync_events()940self.debug_reports(r, uhdev, events)941942slot = self.get_slot(uhdev, t0, 0)943944assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events945assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0946assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50947assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100948949t0.tipswitch = False950if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:951t0.inrange = False952r = uhdev.event([t0])953events = uhdev.next_sync_events()954self.debug_reports(r, uhdev, events)955assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events956assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1957958def test_confidence_false(self):959"""960Bring a finger in contact with the tablet with confidence set to false.961962Ensure that the confidence bit being set to false should not result in a touch event.963"""964uhdev = self.uhdev965_evdev = uhdev.get_evdev()966967t0 = test_multitouch.Touch(1, 50, 100)968t0.confidence = False969r = uhdev.event([t0])970events = uhdev.next_sync_events()971self.debug_reports(r, uhdev, events)972973_slot = self.get_slot(uhdev, t0, 0)974975assert not events976977def test_confidence_multitouch(self):978"""979Bring multiple fingers in contact with the tablet, some with the980confidence bit set, and some without.981982Ensure that all confident touches are reported and that all non-983confident touches are ignored.984"""985uhdev = self.uhdev986evdev = uhdev.get_evdev()987988touches = self.make_contacts(5)989touches[0].confidence = False990touches[2].confidence = False991touches[4].confidence = False992993r = uhdev.event(touches)994events = uhdev.next_sync_events()995self.debug_reports(r, uhdev, events)996997assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events998999self.assert_contacts(1000evdev,1001[1002self.ContactIds(contact_id=0, tracking_id=-1, slot_num=None),1003self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),1004self.ContactIds(contact_id=2, tracking_id=-1, slot_num=None),1005self.ContactIds(contact_id=3, tracking_id=1, slot_num=1),1006self.ContactIds(contact_id=4, tracking_id=-1, slot_num=None),1007],1008)10091010def confidence_change_assert_playback(self, uhdev, evdev, timeline):1011"""1012Assert proper behavior of contacts that move and change tipswitch /1013confidence status over time.10141015Given a `timeline` list of touch states to iterate over, verify1016that the contacts move and are reported as up/down as expected1017by the state of the tipswitch and confidence bits.1018"""1019t = 010201021for state in timeline:1022touches = self.make_contacts(len(state), t)10231024for item in zip(touches, state):1025item[0].tipswitch = item[1][1]1026item[0].confidence = item[1][2]10271028r = uhdev.event(touches)1029events = uhdev.next_sync_events()1030self.debug_reports(r, uhdev, events)10311032ids = [x[0] for x in state]1033self.assert_contacts(evdev, ids, t)10341035t += 110361037def test_confidence_loss_a(self):1038"""1039Transition a confident contact to a non-confident contact by1040first clearing the tipswitch.10411042Ensure that the driver reports the transitioned contact as1043being removed and that other contacts continue to report1044normally. This mode of confidence loss is used by the1045DTH-2452.1046"""1047uhdev = self.uhdev1048evdev = uhdev.get_evdev()10491050self.confidence_change_assert_playback(1051uhdev,1052evdev,1053[1054# t=0: Contact 0 == Down + confident; Contact 1 == Down + confident1055# Both fingers confidently in contact1056[1057(1058self.ContactIds(contact_id=0, tracking_id=0, slot_num=0),1059True,1060True,1061),1062(1063self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1064True,1065True,1066),1067],1068# t=1: Contact 0 == !Down + confident; Contact 1 == Down + confident1069# First finger looses confidence and clears only the tipswitch flag1070[1071(1072self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),1073False,1074True,1075),1076(1077self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1078True,1079True,1080),1081],1082# t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident1083# First finger has lost confidence and has both flags cleared1084[1085(1086self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),1087False,1088False,1089),1090(1091self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1092True,1093True,1094),1095],1096# t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident1097# First finger has lost confidence and has both flags cleared1098[1099(1100self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),1101False,1102False,1103),1104(1105self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1106True,1107True,1108),1109],1110],1111)11121113def test_confidence_loss_b(self):1114"""1115Transition a confident contact to a non-confident contact by1116cleraing both tipswitch and confidence bits simultaneously.11171118Ensure that the driver reports the transitioned contact as1119being removed and that other contacts continue to report1120normally. This mode of confidence loss is used by some1121AES devices.1122"""1123uhdev = self.uhdev1124evdev = uhdev.get_evdev()11251126self.confidence_change_assert_playback(1127uhdev,1128evdev,1129[1130# t=0: Contact 0 == Down + confident; Contact 1 == Down + confident1131# Both fingers confidently in contact1132[1133(1134self.ContactIds(contact_id=0, tracking_id=0, slot_num=0),1135True,1136True,1137),1138(1139self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1140True,1141True,1142),1143],1144# t=1: Contact 0 == !Down + !confident; Contact 1 == Down + confident1145# First finger looses confidence and has both flags cleared simultaneously1146[1147(1148self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),1149False,1150False,1151),1152(1153self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1154True,1155True,1156),1157],1158# t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident1159# First finger has lost confidence and has both flags cleared1160[1161(1162self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),1163False,1164False,1165),1166(1167self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1168True,1169True,1170),1171],1172# t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident1173# First finger has lost confidence and has both flags cleared1174[1175(1176self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),1177False,1178False,1179),1180(1181self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1182True,1183True,1184),1185],1186],1187)11881189def test_confidence_loss_c(self):1190"""1191Transition a confident contact to a non-confident contact by1192clearing only the confidence bit.11931194Ensure that the driver reports the transitioned contact as1195being removed and that other contacts continue to report1196normally.1197"""1198uhdev = self.uhdev1199evdev = uhdev.get_evdev()12001201self.confidence_change_assert_playback(1202uhdev,1203evdev,1204[1205# t=0: Contact 0 == Down + confident; Contact 1 == Down + confident1206# Both fingers confidently in contact1207[1208(1209self.ContactIds(contact_id=0, tracking_id=0, slot_num=0),1210True,1211True,1212),1213(1214self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1215True,1216True,1217),1218],1219# t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident1220# First finger looses confidence and clears only the confidence flag1221[1222(1223self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),1224True,1225False,1226),1227(1228self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1229True,1230True,1231),1232],1233# t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident1234# First finger has lost confidence and has both flags cleared1235[1236(1237self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),1238False,1239False,1240),1241(1242self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1243True,1244True,1245),1246],1247# t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident1248# First finger has lost confidence and has both flags cleared1249[1250(1251self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),1252False,1253False,1254),1255(1256self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1257True,1258True,1259),1260],1261],1262)12631264def test_confidence_gain_a(self):1265"""1266Transition a contact that was always non-confident to confident.12671268Ensure that the confident contact is reported normally.1269"""1270uhdev = self.uhdev1271evdev = uhdev.get_evdev()12721273self.confidence_change_assert_playback(1274uhdev,1275evdev,1276[1277# t=0: Contact 0 == Down + !confident; Contact 1 == Down + confident1278# Only second finger is confidently in contact1279[1280(1281self.ContactIds(contact_id=0, tracking_id=-1, slot_num=None),1282True,1283False,1284),1285(1286self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),1287True,1288True,1289),1290],1291# t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident1292# First finger gains confidence1293[1294(1295self.ContactIds(contact_id=0, tracking_id=-1, slot_num=None),1296True,1297False,1298),1299(1300self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),1301True,1302True,1303),1304],1305# t=2: Contact 0 == Down + confident; Contact 1 == Down + confident1306# First finger remains confident1307[1308(1309self.ContactIds(contact_id=0, tracking_id=1, slot_num=1),1310True,1311True,1312),1313(1314self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),1315True,1316True,1317),1318],1319# t=3: Contact 0 == Down + confident; Contact 1 == Down + confident1320# First finger remains confident1321[1322(1323self.ContactIds(contact_id=0, tracking_id=1, slot_num=1),1324True,1325True,1326),1327(1328self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),1329True,1330True,1331),1332],1333],1334)13351336def test_confidence_gain_b(self):1337"""1338Transition a contact from non-confident to confident.13391340Ensure that the confident contact is reported normally.1341"""1342uhdev = self.uhdev1343evdev = uhdev.get_evdev()13441345self.confidence_change_assert_playback(1346uhdev,1347evdev,1348[1349# t=0: Contact 0 == Down + confident; Contact 1 == Down + confident1350# First and second finger confidently in contact1351[1352(1353self.ContactIds(contact_id=0, tracking_id=0, slot_num=0),1354True,1355True,1356),1357(1358self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1359True,1360True,1361),1362],1363# t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident1364# Firtst finger looses confidence1365[1366(1367self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),1368True,1369False,1370),1371(1372self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1373True,1374True,1375),1376],1377# t=2: Contact 0 == Down + confident; Contact 1 == Down + confident1378# First finger gains confidence1379[1380(1381self.ContactIds(contact_id=0, tracking_id=2, slot_num=0),1382True,1383True,1384),1385(1386self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1387True,1388True,1389),1390],1391# t=3: Contact 0 == !Down + confident; Contact 1 == Down + confident1392# First finger goes up1393[1394(1395self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),1396False,1397True,1398),1399(1400self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),1401True,1402True,1403),1404],1405],1406)140714081409