1*1dec39d4SBenjamin Tissoires#!/bin/env python3 2*1dec39d4SBenjamin Tissoires# SPDX-License-Identifier: GPL-2.0 3*1dec39d4SBenjamin Tissoires# -*- coding: utf-8 -*- 4*1dec39d4SBenjamin Tissoires# 5*1dec39d4SBenjamin Tissoires# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com> 6*1dec39d4SBenjamin Tissoires# Copyright (c) 2017 Red Hat, Inc. 7*1dec39d4SBenjamin Tissoires# Copyright (c) 2020 Wacom Technology Corp. 8*1dec39d4SBenjamin Tissoires# 9*1dec39d4SBenjamin Tissoires# Authors: 10*1dec39d4SBenjamin Tissoires# Jason Gerecke <jason.gerecke@wacom.com> 11*1dec39d4SBenjamin Tissoires 12*1dec39d4SBenjamin Tissoires""" 13*1dec39d4SBenjamin TissoiresTests for the Wacom driver generic codepath. 14*1dec39d4SBenjamin Tissoires 15*1dec39d4SBenjamin TissoiresThis module tests the function of the Wacom driver's generic codepath. 16*1dec39d4SBenjamin TissoiresThe generic codepath is used by devices which are not explicitly listed 17*1dec39d4SBenjamin Tissoiresin the driver's device table. It uses the device's HID descriptor to 18*1dec39d4SBenjamin Tissoiresdecode reports sent by the device. 19*1dec39d4SBenjamin Tissoires""" 20*1dec39d4SBenjamin Tissoires 21*1dec39d4SBenjamin Tissoiresfrom .descriptors_wacom import ( 22*1dec39d4SBenjamin Tissoires wacom_pth660_v145, 23*1dec39d4SBenjamin Tissoires wacom_pth660_v150, 24*1dec39d4SBenjamin Tissoires wacom_pth860_v145, 25*1dec39d4SBenjamin Tissoires wacom_pth860_v150, 26*1dec39d4SBenjamin Tissoires wacom_pth460_v105, 27*1dec39d4SBenjamin Tissoires) 28*1dec39d4SBenjamin Tissoires 29*1dec39d4SBenjamin Tissoiresimport attr 30*1dec39d4SBenjamin Tissoiresfrom enum import Enum 31*1dec39d4SBenjamin Tissoiresfrom hidtools.hut import HUT 32*1dec39d4SBenjamin Tissoiresfrom hidtools.hid import HidUnit 33*1dec39d4SBenjamin Tissoiresfrom . import base 34*1dec39d4SBenjamin Tissoiresfrom . import test_multitouch 35*1dec39d4SBenjamin Tissoiresimport libevdev 36*1dec39d4SBenjamin Tissoiresimport pytest 37*1dec39d4SBenjamin Tissoires 38*1dec39d4SBenjamin Tissoiresimport logging 39*1dec39d4SBenjamin Tissoires 40*1dec39d4SBenjamin Tissoireslogger = logging.getLogger("hidtools.test.wacom") 41*1dec39d4SBenjamin Tissoires 42*1dec39d4SBenjamin TissoiresKERNEL_MODULE = ("wacom", "wacom") 43*1dec39d4SBenjamin Tissoires 44*1dec39d4SBenjamin Tissoires 45*1dec39d4SBenjamin Tissoiresclass ProximityState(Enum): 46*1dec39d4SBenjamin Tissoires """ 47*1dec39d4SBenjamin Tissoires Enumeration of allowed proximity states. 48*1dec39d4SBenjamin Tissoires """ 49*1dec39d4SBenjamin Tissoires 50*1dec39d4SBenjamin Tissoires # Tool is not able to be sensed by the device 51*1dec39d4SBenjamin Tissoires OUT = 0 52*1dec39d4SBenjamin Tissoires 53*1dec39d4SBenjamin Tissoires # Tool is close enough to be sensed, but some data may be invalid 54*1dec39d4SBenjamin Tissoires # or inaccurate 55*1dec39d4SBenjamin Tissoires IN_PROXIMITY = 1 56*1dec39d4SBenjamin Tissoires 57*1dec39d4SBenjamin Tissoires # Tool is close enough to be sensed with high accuracy. All data 58*1dec39d4SBenjamin Tissoires # valid. 59*1dec39d4SBenjamin Tissoires IN_RANGE = 2 60*1dec39d4SBenjamin Tissoires 61*1dec39d4SBenjamin Tissoires def fill(self, reportdata): 62*1dec39d4SBenjamin Tissoires """Fill a report with approrpiate HID properties/values.""" 63*1dec39d4SBenjamin Tissoires reportdata.inrange = self in [ProximityState.IN_RANGE] 64*1dec39d4SBenjamin Tissoires reportdata.wacomsense = self in [ 65*1dec39d4SBenjamin Tissoires ProximityState.IN_PROXIMITY, 66*1dec39d4SBenjamin Tissoires ProximityState.IN_RANGE, 67*1dec39d4SBenjamin Tissoires ] 68*1dec39d4SBenjamin Tissoires 69*1dec39d4SBenjamin Tissoires 70*1dec39d4SBenjamin Tissoiresclass ReportData: 71*1dec39d4SBenjamin Tissoires """ 72*1dec39d4SBenjamin Tissoires Placeholder for HID report values. 73*1dec39d4SBenjamin Tissoires """ 74*1dec39d4SBenjamin Tissoires 75*1dec39d4SBenjamin Tissoires pass 76*1dec39d4SBenjamin Tissoires 77*1dec39d4SBenjamin Tissoires 78*1dec39d4SBenjamin Tissoires@attr.s 79*1dec39d4SBenjamin Tissoiresclass Buttons: 80*1dec39d4SBenjamin Tissoires """ 81*1dec39d4SBenjamin Tissoires Stylus button state. 82*1dec39d4SBenjamin Tissoires 83*1dec39d4SBenjamin Tissoires Describes the state of each of the buttons / "side switches" that 84*1dec39d4SBenjamin Tissoires may be present on a stylus. Buttons set to 'None' indicate the 85*1dec39d4SBenjamin Tissoires state is "unchanged" since the previous event. 86*1dec39d4SBenjamin Tissoires """ 87*1dec39d4SBenjamin Tissoires 88*1dec39d4SBenjamin Tissoires primary = attr.ib(default=None) 89*1dec39d4SBenjamin Tissoires secondary = attr.ib(default=None) 90*1dec39d4SBenjamin Tissoires tertiary = attr.ib(default=None) 91*1dec39d4SBenjamin Tissoires 92*1dec39d4SBenjamin Tissoires @staticmethod 93*1dec39d4SBenjamin Tissoires def clear(): 94*1dec39d4SBenjamin Tissoires """Button object with all states cleared.""" 95*1dec39d4SBenjamin Tissoires return Buttons(False, False, False) 96*1dec39d4SBenjamin Tissoires 97*1dec39d4SBenjamin Tissoires def fill(self, reportdata): 98*1dec39d4SBenjamin Tissoires """Fill a report with approrpiate HID properties/values.""" 99*1dec39d4SBenjamin Tissoires reportdata.barrelswitch = int(self.primary or 0) 100*1dec39d4SBenjamin Tissoires reportdata.secondarybarrelswitch = int(self.secondary or 0) 101*1dec39d4SBenjamin Tissoires reportdata.b3 = int(self.tertiary or 0) 102*1dec39d4SBenjamin Tissoires 103*1dec39d4SBenjamin Tissoires 104*1dec39d4SBenjamin Tissoires@attr.s 105*1dec39d4SBenjamin Tissoiresclass ToolID: 106*1dec39d4SBenjamin Tissoires """ 107*1dec39d4SBenjamin Tissoires Stylus tool identifiers. 108*1dec39d4SBenjamin Tissoires 109*1dec39d4SBenjamin Tissoires Contains values used to identify a specific stylus, e.g. its serial 110*1dec39d4SBenjamin Tissoires number and tool-type identifier. Values of ``0`` may sometimes be 111*1dec39d4SBenjamin Tissoires used for the out-of-range condition. 112*1dec39d4SBenjamin Tissoires """ 113*1dec39d4SBenjamin Tissoires 114*1dec39d4SBenjamin Tissoires serial = attr.ib() 115*1dec39d4SBenjamin Tissoires tooltype = attr.ib() 116*1dec39d4SBenjamin Tissoires 117*1dec39d4SBenjamin Tissoires @staticmethod 118*1dec39d4SBenjamin Tissoires def clear(): 119*1dec39d4SBenjamin Tissoires """ToolID object with all fields cleared.""" 120*1dec39d4SBenjamin Tissoires return ToolID(0, 0) 121*1dec39d4SBenjamin Tissoires 122*1dec39d4SBenjamin Tissoires def fill(self, reportdata): 123*1dec39d4SBenjamin Tissoires """Fill a report with approrpiate HID properties/values.""" 124*1dec39d4SBenjamin Tissoires reportdata.transducerserialnumber = self.serial & 0xFFFFFFFF 125*1dec39d4SBenjamin Tissoires reportdata.serialhi = (self.serial >> 32) & 0xFFFFFFFF 126*1dec39d4SBenjamin Tissoires reportdata.tooltype = self.tooltype 127*1dec39d4SBenjamin Tissoires 128*1dec39d4SBenjamin Tissoires 129*1dec39d4SBenjamin Tissoires@attr.s 130*1dec39d4SBenjamin Tissoiresclass PhysRange: 131*1dec39d4SBenjamin Tissoires """ 132*1dec39d4SBenjamin Tissoires Range of HID physical values, with units. 133*1dec39d4SBenjamin Tissoires """ 134*1dec39d4SBenjamin Tissoires 135*1dec39d4SBenjamin Tissoires unit = attr.ib() 136*1dec39d4SBenjamin Tissoires min_size = attr.ib() 137*1dec39d4SBenjamin Tissoires max_size = attr.ib() 138*1dec39d4SBenjamin Tissoires 139*1dec39d4SBenjamin Tissoires CENTIMETER = HidUnit.from_string("SILinear: cm") 140*1dec39d4SBenjamin Tissoires DEGREE = HidUnit.from_string("EnglishRotation: deg") 141*1dec39d4SBenjamin Tissoires 142*1dec39d4SBenjamin Tissoires def contains(self, field): 143*1dec39d4SBenjamin Tissoires """ 144*1dec39d4SBenjamin Tissoires Check if the physical size of the provided field is in range. 145*1dec39d4SBenjamin Tissoires 146*1dec39d4SBenjamin Tissoires Compare the physical size described by the provided HID field 147*1dec39d4SBenjamin Tissoires against the range of sizes described by this object. This is 148*1dec39d4SBenjamin Tissoires an exclusive range comparison (e.g. 0 cm is not within the 149*1dec39d4SBenjamin Tissoires range 0 cm - 5 cm) and exact unit comparison (e.g. 1 inch is 150*1dec39d4SBenjamin Tissoires not within the range 0 cm - 5 cm). 151*1dec39d4SBenjamin Tissoires """ 152*1dec39d4SBenjamin Tissoires phys_size = (field.physical_max - field.physical_min) * 10 ** (field.unit_exp) 153*1dec39d4SBenjamin Tissoires return ( 154*1dec39d4SBenjamin Tissoires field.unit == self.unit.value 155*1dec39d4SBenjamin Tissoires and phys_size > self.min_size 156*1dec39d4SBenjamin Tissoires and phys_size < self.max_size 157*1dec39d4SBenjamin Tissoires ) 158*1dec39d4SBenjamin Tissoires 159*1dec39d4SBenjamin Tissoires 160*1dec39d4SBenjamin Tissoiresclass BaseTablet(base.UHIDTestDevice): 161*1dec39d4SBenjamin Tissoires """ 162*1dec39d4SBenjamin Tissoires Skeleton object for all kinds of tablet devices. 163*1dec39d4SBenjamin Tissoires """ 164*1dec39d4SBenjamin Tissoires 165*1dec39d4SBenjamin Tissoires def __init__(self, rdesc, name=None, info=None): 166*1dec39d4SBenjamin Tissoires assert rdesc is not None 167*1dec39d4SBenjamin Tissoires super().__init__(name, "Pen", input_info=info, rdesc=rdesc) 168*1dec39d4SBenjamin Tissoires self.buttons = Buttons.clear() 169*1dec39d4SBenjamin Tissoires self.toolid = ToolID.clear() 170*1dec39d4SBenjamin Tissoires self.proximity = ProximityState.OUT 171*1dec39d4SBenjamin Tissoires self.offset = 0 172*1dec39d4SBenjamin Tissoires self.ring = -1 173*1dec39d4SBenjamin Tissoires self.ek0 = False 174*1dec39d4SBenjamin Tissoires 175*1dec39d4SBenjamin Tissoires def match_evdev_rule(self, application, evdev): 176*1dec39d4SBenjamin Tissoires """ 177*1dec39d4SBenjamin Tissoires Filter out evdev nodes based on the requested application. 178*1dec39d4SBenjamin Tissoires 179*1dec39d4SBenjamin Tissoires The Wacom driver may create several device nodes for each USB 180*1dec39d4SBenjamin Tissoires interface device. It is crucial that we run tests with the 181*1dec39d4SBenjamin Tissoires expected device node or things will obviously go off the rails. 182*1dec39d4SBenjamin Tissoires Use the Wacom driver's usual naming conventions to apply a 183*1dec39d4SBenjamin Tissoires sensible default filter. 184*1dec39d4SBenjamin Tissoires """ 185*1dec39d4SBenjamin Tissoires if application in ["Pen", "Pad"]: 186*1dec39d4SBenjamin Tissoires return evdev.name.endswith(application) 187*1dec39d4SBenjamin Tissoires else: 188*1dec39d4SBenjamin Tissoires return True 189*1dec39d4SBenjamin Tissoires 190*1dec39d4SBenjamin Tissoires def create_report( 191*1dec39d4SBenjamin Tissoires self, x, y, pressure, buttons=None, toolid=None, proximity=None, reportID=None 192*1dec39d4SBenjamin Tissoires ): 193*1dec39d4SBenjamin Tissoires """ 194*1dec39d4SBenjamin Tissoires Return an input report for this device. 195*1dec39d4SBenjamin Tissoires 196*1dec39d4SBenjamin Tissoires :param x: absolute x 197*1dec39d4SBenjamin Tissoires :param y: absolute y 198*1dec39d4SBenjamin Tissoires :param pressure: pressure 199*1dec39d4SBenjamin Tissoires :param buttons: stylus button state. Use ``None`` for unchanged. 200*1dec39d4SBenjamin Tissoires :param toolid: tool identifiers. Use ``None`` for unchanged. 201*1dec39d4SBenjamin Tissoires :param proximity: a ProximityState indicating the sensor's ability 202*1dec39d4SBenjamin Tissoires to detect and report attributes of this tool. Use ``None`` 203*1dec39d4SBenjamin Tissoires for unchanged. 204*1dec39d4SBenjamin Tissoires :param reportID: the numeric report ID for this report, if needed 205*1dec39d4SBenjamin Tissoires """ 206*1dec39d4SBenjamin Tissoires if buttons is not None: 207*1dec39d4SBenjamin Tissoires self.buttons = buttons 208*1dec39d4SBenjamin Tissoires buttons = self.buttons 209*1dec39d4SBenjamin Tissoires 210*1dec39d4SBenjamin Tissoires if toolid is not None: 211*1dec39d4SBenjamin Tissoires self.toolid = toolid 212*1dec39d4SBenjamin Tissoires toolid = self.toolid 213*1dec39d4SBenjamin Tissoires 214*1dec39d4SBenjamin Tissoires if proximity is not None: 215*1dec39d4SBenjamin Tissoires self.proximity = proximity 216*1dec39d4SBenjamin Tissoires proximity = self.proximity 217*1dec39d4SBenjamin Tissoires 218*1dec39d4SBenjamin Tissoires reportID = reportID or self.default_reportID 219*1dec39d4SBenjamin Tissoires 220*1dec39d4SBenjamin Tissoires report = ReportData() 221*1dec39d4SBenjamin Tissoires report.x = x 222*1dec39d4SBenjamin Tissoires report.y = y 223*1dec39d4SBenjamin Tissoires report.tippressure = pressure 224*1dec39d4SBenjamin Tissoires report.tipswitch = pressure > 0 225*1dec39d4SBenjamin Tissoires buttons.fill(report) 226*1dec39d4SBenjamin Tissoires proximity.fill(report) 227*1dec39d4SBenjamin Tissoires toolid.fill(report) 228*1dec39d4SBenjamin Tissoires 229*1dec39d4SBenjamin Tissoires return super().create_report(report, reportID=reportID) 230*1dec39d4SBenjamin Tissoires 231*1dec39d4SBenjamin Tissoires def create_report_heartbeat(self, reportID): 232*1dec39d4SBenjamin Tissoires """ 233*1dec39d4SBenjamin Tissoires Return a heartbeat input report for this device. 234*1dec39d4SBenjamin Tissoires 235*1dec39d4SBenjamin Tissoires Heartbeat reports generally contain battery status information, 236*1dec39d4SBenjamin Tissoires among other things. 237*1dec39d4SBenjamin Tissoires """ 238*1dec39d4SBenjamin Tissoires report = ReportData() 239*1dec39d4SBenjamin Tissoires report.wacombatterycharging = 1 240*1dec39d4SBenjamin Tissoires return super().create_report(report, reportID=reportID) 241*1dec39d4SBenjamin Tissoires 242*1dec39d4SBenjamin Tissoires def create_report_pad(self, reportID, ring, ek0): 243*1dec39d4SBenjamin Tissoires report = ReportData() 244*1dec39d4SBenjamin Tissoires 245*1dec39d4SBenjamin Tissoires if ring is not None: 246*1dec39d4SBenjamin Tissoires self.ring = ring 247*1dec39d4SBenjamin Tissoires ring = self.ring 248*1dec39d4SBenjamin Tissoires 249*1dec39d4SBenjamin Tissoires if ek0 is not None: 250*1dec39d4SBenjamin Tissoires self.ek0 = ek0 251*1dec39d4SBenjamin Tissoires ek0 = self.ek0 252*1dec39d4SBenjamin Tissoires 253*1dec39d4SBenjamin Tissoires if ring >= 0: 254*1dec39d4SBenjamin Tissoires report.wacomtouchring = ring 255*1dec39d4SBenjamin Tissoires report.wacomtouchringstatus = 1 256*1dec39d4SBenjamin Tissoires else: 257*1dec39d4SBenjamin Tissoires report.wacomtouchring = 0x7F 258*1dec39d4SBenjamin Tissoires report.wacomtouchringstatus = 0 259*1dec39d4SBenjamin Tissoires 260*1dec39d4SBenjamin Tissoires report.wacomexpresskey00 = ek0 261*1dec39d4SBenjamin Tissoires return super().create_report(report, reportID=reportID) 262*1dec39d4SBenjamin Tissoires 263*1dec39d4SBenjamin Tissoires def event(self, x, y, pressure, buttons=None, toolid=None, proximity=None): 264*1dec39d4SBenjamin Tissoires """ 265*1dec39d4SBenjamin Tissoires Send an input event on the default report ID. 266*1dec39d4SBenjamin Tissoires 267*1dec39d4SBenjamin Tissoires :param x: absolute x 268*1dec39d4SBenjamin Tissoires :param y: absolute y 269*1dec39d4SBenjamin Tissoires :param buttons: stylus button state. Use ``None`` for unchanged. 270*1dec39d4SBenjamin Tissoires :param toolid: tool identifiers. Use ``None`` for unchanged. 271*1dec39d4SBenjamin Tissoires :param proximity: a ProximityState indicating the sensor's ability 272*1dec39d4SBenjamin Tissoires to detect and report attributes of this tool. Use ``None`` 273*1dec39d4SBenjamin Tissoires for unchanged. 274*1dec39d4SBenjamin Tissoires """ 275*1dec39d4SBenjamin Tissoires r = self.create_report(x, y, pressure, buttons, toolid, proximity) 276*1dec39d4SBenjamin Tissoires self.call_input_event(r) 277*1dec39d4SBenjamin Tissoires return [r] 278*1dec39d4SBenjamin Tissoires 279*1dec39d4SBenjamin Tissoires def event_heartbeat(self, reportID): 280*1dec39d4SBenjamin Tissoires """ 281*1dec39d4SBenjamin Tissoires Send a heartbeat event on the requested report ID. 282*1dec39d4SBenjamin Tissoires """ 283*1dec39d4SBenjamin Tissoires r = self.create_report_heartbeat(reportID) 284*1dec39d4SBenjamin Tissoires self.call_input_event(r) 285*1dec39d4SBenjamin Tissoires return [r] 286*1dec39d4SBenjamin Tissoires 287*1dec39d4SBenjamin Tissoires def event_pad(self, reportID, ring=None, ek0=None): 288*1dec39d4SBenjamin Tissoires """ 289*1dec39d4SBenjamin Tissoires Send a pad event on the requested report ID. 290*1dec39d4SBenjamin Tissoires """ 291*1dec39d4SBenjamin Tissoires r = self.create_report_pad(reportID, ring, ek0) 292*1dec39d4SBenjamin Tissoires self.call_input_event(r) 293*1dec39d4SBenjamin Tissoires return [r] 294*1dec39d4SBenjamin Tissoires 295*1dec39d4SBenjamin Tissoires def get_report(self, req, rnum, rtype): 296*1dec39d4SBenjamin Tissoires if rtype != self.UHID_FEATURE_REPORT: 297*1dec39d4SBenjamin Tissoires return (1, []) 298*1dec39d4SBenjamin Tissoires 299*1dec39d4SBenjamin Tissoires rdesc = None 300*1dec39d4SBenjamin Tissoires for v in self.parsed_rdesc.feature_reports.values(): 301*1dec39d4SBenjamin Tissoires if v.report_ID == rnum: 302*1dec39d4SBenjamin Tissoires rdesc = v 303*1dec39d4SBenjamin Tissoires 304*1dec39d4SBenjamin Tissoires if rdesc is None: 305*1dec39d4SBenjamin Tissoires return (1, []) 306*1dec39d4SBenjamin Tissoires 307*1dec39d4SBenjamin Tissoires result = (1, []) 308*1dec39d4SBenjamin Tissoires result = self.create_report_offset(rdesc) or result 309*1dec39d4SBenjamin Tissoires return result 310*1dec39d4SBenjamin Tissoires 311*1dec39d4SBenjamin Tissoires def create_report_offset(self, rdesc): 312*1dec39d4SBenjamin Tissoires require = [ 313*1dec39d4SBenjamin Tissoires "Wacom Offset Left", 314*1dec39d4SBenjamin Tissoires "Wacom Offset Top", 315*1dec39d4SBenjamin Tissoires "Wacom Offset Right", 316*1dec39d4SBenjamin Tissoires "Wacom Offset Bottom", 317*1dec39d4SBenjamin Tissoires ] 318*1dec39d4SBenjamin Tissoires if not set(require).issubset(set([f.usage_name for f in rdesc])): 319*1dec39d4SBenjamin Tissoires return None 320*1dec39d4SBenjamin Tissoires 321*1dec39d4SBenjamin Tissoires report = ReportData() 322*1dec39d4SBenjamin Tissoires report.wacomoffsetleft = self.offset 323*1dec39d4SBenjamin Tissoires report.wacomoffsettop = self.offset 324*1dec39d4SBenjamin Tissoires report.wacomoffsetright = self.offset 325*1dec39d4SBenjamin Tissoires report.wacomoffsetbottom = self.offset 326*1dec39d4SBenjamin Tissoires r = rdesc.create_report([report], None) 327*1dec39d4SBenjamin Tissoires return (0, r) 328*1dec39d4SBenjamin Tissoires 329*1dec39d4SBenjamin Tissoires 330*1dec39d4SBenjamin Tissoiresclass OpaqueTablet(BaseTablet): 331*1dec39d4SBenjamin Tissoires """ 332*1dec39d4SBenjamin Tissoires Bare-bones opaque tablet with a minimum of features. 333*1dec39d4SBenjamin Tissoires 334*1dec39d4SBenjamin Tissoires A tablet stripped down to its absolute core. It is capable of 335*1dec39d4SBenjamin Tissoires reporting X/Y position and if the pen is in contact. No pressure, 336*1dec39d4SBenjamin Tissoires no barrel switches, no eraser. Notably it *does* report an "In 337*1dec39d4SBenjamin Tissoires Range" flag, but this is only because the Wacom driver expects 338*1dec39d4SBenjamin Tissoires one to function properly. The device uses only standard HID usages, 339*1dec39d4SBenjamin Tissoires not any of Wacom's vendor-defined pages. 340*1dec39d4SBenjamin Tissoires """ 341*1dec39d4SBenjamin Tissoires 342*1dec39d4SBenjamin Tissoires # fmt: off 343*1dec39d4SBenjamin Tissoires report_descriptor = [ 344*1dec39d4SBenjamin Tissoires 0x05, 0x0D, # . Usage Page (Digitizer), 345*1dec39d4SBenjamin Tissoires 0x09, 0x01, # . Usage (Digitizer), 346*1dec39d4SBenjamin Tissoires 0xA1, 0x01, # . Collection (Application), 347*1dec39d4SBenjamin Tissoires 0x85, 0x01, # . Report ID (1), 348*1dec39d4SBenjamin Tissoires 0x09, 0x20, # . Usage (Stylus), 349*1dec39d4SBenjamin Tissoires 0xA1, 0x00, # . Collection (Physical), 350*1dec39d4SBenjamin Tissoires 0x09, 0x42, # . Usage (Tip Switch), 351*1dec39d4SBenjamin Tissoires 0x09, 0x32, # . Usage (In Range), 352*1dec39d4SBenjamin Tissoires 0x15, 0x00, # . Logical Minimum (0), 353*1dec39d4SBenjamin Tissoires 0x25, 0x01, # . Logical Maximum (1), 354*1dec39d4SBenjamin Tissoires 0x75, 0x01, # . Report Size (1), 355*1dec39d4SBenjamin Tissoires 0x95, 0x02, # . Report Count (2), 356*1dec39d4SBenjamin Tissoires 0x81, 0x02, # . Input (Variable), 357*1dec39d4SBenjamin Tissoires 0x95, 0x06, # . Report Count (6), 358*1dec39d4SBenjamin Tissoires 0x81, 0x03, # . Input (Constant, Variable), 359*1dec39d4SBenjamin Tissoires 0x05, 0x01, # . Usage Page (Desktop), 360*1dec39d4SBenjamin Tissoires 0x09, 0x30, # . Usage (X), 361*1dec39d4SBenjamin Tissoires 0x27, 0x80, 0x3E, 0x00, 0x00, # . Logical Maximum (16000), 362*1dec39d4SBenjamin Tissoires 0x47, 0x80, 0x3E, 0x00, 0x00, # . Physical Maximum (16000), 363*1dec39d4SBenjamin Tissoires 0x65, 0x11, # . Unit (Centimeter), 364*1dec39d4SBenjamin Tissoires 0x55, 0x0D, # . Unit Exponent (13), 365*1dec39d4SBenjamin Tissoires 0x75, 0x10, # . Report Size (16), 366*1dec39d4SBenjamin Tissoires 0x95, 0x01, # . Report Count (1), 367*1dec39d4SBenjamin Tissoires 0x81, 0x02, # . Input (Variable), 368*1dec39d4SBenjamin Tissoires 0x09, 0x31, # . Usage (Y), 369*1dec39d4SBenjamin Tissoires 0x27, 0x28, 0x23, 0x00, 0x00, # . Logical Maximum (9000), 370*1dec39d4SBenjamin Tissoires 0x47, 0x28, 0x23, 0x00, 0x00, # . Physical Maximum (9000), 371*1dec39d4SBenjamin Tissoires 0x81, 0x02, # . Input (Variable), 372*1dec39d4SBenjamin Tissoires 0xC0, # . End Collection, 373*1dec39d4SBenjamin Tissoires 0xC0, # . End Collection, 374*1dec39d4SBenjamin Tissoires ] 375*1dec39d4SBenjamin Tissoires # fmt: on 376*1dec39d4SBenjamin Tissoires 377*1dec39d4SBenjamin Tissoires def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)): 378*1dec39d4SBenjamin Tissoires super().__init__(rdesc, name, info) 379*1dec39d4SBenjamin Tissoires self.default_reportID = 1 380*1dec39d4SBenjamin Tissoires 381*1dec39d4SBenjamin Tissoires 382*1dec39d4SBenjamin Tissoiresclass OpaqueCTLTablet(BaseTablet): 383*1dec39d4SBenjamin Tissoires """ 384*1dec39d4SBenjamin Tissoires Opaque tablet similar to something in the CTL product line. 385*1dec39d4SBenjamin Tissoires 386*1dec39d4SBenjamin Tissoires A pen-only tablet with most basic features you would expect from 387*1dec39d4SBenjamin Tissoires an actual device. Position, eraser, pressure, barrel buttons. 388*1dec39d4SBenjamin Tissoires Uses the Wacom vendor-defined usage page. 389*1dec39d4SBenjamin Tissoires """ 390*1dec39d4SBenjamin Tissoires 391*1dec39d4SBenjamin Tissoires # fmt: off 392*1dec39d4SBenjamin Tissoires report_descriptor = [ 393*1dec39d4SBenjamin Tissoires 0x06, 0x0D, 0xFF, # . Usage Page (Vnd Wacom Emr), 394*1dec39d4SBenjamin Tissoires 0x09, 0x01, # . Usage (Digitizer), 395*1dec39d4SBenjamin Tissoires 0xA1, 0x01, # . Collection (Application), 396*1dec39d4SBenjamin Tissoires 0x85, 0x10, # . Report ID (16), 397*1dec39d4SBenjamin Tissoires 0x09, 0x20, # . Usage (Stylus), 398*1dec39d4SBenjamin Tissoires 0x35, 0x00, # . Physical Minimum (0), 399*1dec39d4SBenjamin Tissoires 0x45, 0x00, # . Physical Maximum (0), 400*1dec39d4SBenjamin Tissoires 0x15, 0x00, # . Logical Minimum (0), 401*1dec39d4SBenjamin Tissoires 0x25, 0x01, # . Logical Maximum (1), 402*1dec39d4SBenjamin Tissoires 0xA1, 0x00, # . Collection (Physical), 403*1dec39d4SBenjamin Tissoires 0x09, 0x42, # . Usage (Tip Switch), 404*1dec39d4SBenjamin Tissoires 0x09, 0x44, # . Usage (Barrel Switch), 405*1dec39d4SBenjamin Tissoires 0x09, 0x5A, # . Usage (Secondary Barrel Switch), 406*1dec39d4SBenjamin Tissoires 0x09, 0x45, # . Usage (Eraser), 407*1dec39d4SBenjamin Tissoires 0x09, 0x3C, # . Usage (Invert), 408*1dec39d4SBenjamin Tissoires 0x09, 0x32, # . Usage (In Range), 409*1dec39d4SBenjamin Tissoires 0x09, 0x36, # . Usage (In Proximity), 410*1dec39d4SBenjamin Tissoires 0x25, 0x01, # . Logical Maximum (1), 411*1dec39d4SBenjamin Tissoires 0x75, 0x01, # . Report Size (1), 412*1dec39d4SBenjamin Tissoires 0x95, 0x07, # . Report Count (7), 413*1dec39d4SBenjamin Tissoires 0x81, 0x02, # . Input (Variable), 414*1dec39d4SBenjamin Tissoires 0x95, 0x01, # . Report Count (1), 415*1dec39d4SBenjamin Tissoires 0x81, 0x03, # . Input (Constant, Variable), 416*1dec39d4SBenjamin Tissoires 0x0A, 0x30, 0x01, # . Usage (X), 417*1dec39d4SBenjamin Tissoires 0x65, 0x11, # . Unit (Centimeter), 418*1dec39d4SBenjamin Tissoires 0x55, 0x0D, # . Unit Exponent (13), 419*1dec39d4SBenjamin Tissoires 0x47, 0x80, 0x3E, 0x00, 0x00, # . Physical Maximum (16000), 420*1dec39d4SBenjamin Tissoires 0x27, 0x80, 0x3E, 0x00, 0x00, # . Logical Maximum (16000), 421*1dec39d4SBenjamin Tissoires 0x75, 0x18, # . Report Size (24), 422*1dec39d4SBenjamin Tissoires 0x95, 0x01, # . Report Count (1), 423*1dec39d4SBenjamin Tissoires 0x81, 0x02, # . Input (Variable), 424*1dec39d4SBenjamin Tissoires 0x0A, 0x31, 0x01, # . Usage (Y), 425*1dec39d4SBenjamin Tissoires 0x47, 0x28, 0x23, 0x00, 0x00, # . Physical Maximum (9000), 426*1dec39d4SBenjamin Tissoires 0x27, 0x28, 0x23, 0x00, 0x00, # . Logical Maximum (9000), 427*1dec39d4SBenjamin Tissoires 0x81, 0x02, # . Input (Variable), 428*1dec39d4SBenjamin Tissoires 0x09, 0x30, # . Usage (Tip Pressure), 429*1dec39d4SBenjamin Tissoires 0x55, 0x00, # . Unit Exponent (0), 430*1dec39d4SBenjamin Tissoires 0x65, 0x00, # . Unit, 431*1dec39d4SBenjamin Tissoires 0x47, 0x00, 0x00, 0x00, 0x00, # . Physical Maximum (0), 432*1dec39d4SBenjamin Tissoires 0x26, 0xFF, 0x0F, # . Logical Maximum (4095), 433*1dec39d4SBenjamin Tissoires 0x75, 0x10, # . Report Size (16), 434*1dec39d4SBenjamin Tissoires 0x81, 0x02, # . Input (Variable), 435*1dec39d4SBenjamin Tissoires 0x75, 0x08, # . Report Size (8), 436*1dec39d4SBenjamin Tissoires 0x95, 0x06, # . Report Count (6), 437*1dec39d4SBenjamin Tissoires 0x81, 0x03, # . Input (Constant, Variable), 438*1dec39d4SBenjamin Tissoires 0x0A, 0x32, 0x01, # . Usage (Z), 439*1dec39d4SBenjamin Tissoires 0x25, 0x3F, # . Logical Maximum (63), 440*1dec39d4SBenjamin Tissoires 0x75, 0x08, # . Report Size (8), 441*1dec39d4SBenjamin Tissoires 0x95, 0x01, # . Report Count (1), 442*1dec39d4SBenjamin Tissoires 0x81, 0x02, # . Input (Variable), 443*1dec39d4SBenjamin Tissoires 0x09, 0x5B, # . Usage (Transducer Serial Number), 444*1dec39d4SBenjamin Tissoires 0x09, 0x5C, # . Usage (Transducer Serial Number Hi), 445*1dec39d4SBenjamin Tissoires 0x17, 0x00, 0x00, 0x00, 0x80, # . Logical Minimum (-2147483648), 446*1dec39d4SBenjamin Tissoires 0x27, 0xFF, 0xFF, 0xFF, 0x7F, # . Logical Maximum (2147483647), 447*1dec39d4SBenjamin Tissoires 0x75, 0x20, # . Report Size (32), 448*1dec39d4SBenjamin Tissoires 0x95, 0x02, # . Report Count (2), 449*1dec39d4SBenjamin Tissoires 0x81, 0x02, # . Input (Variable), 450*1dec39d4SBenjamin Tissoires 0x09, 0x77, # . Usage (Tool Type), 451*1dec39d4SBenjamin Tissoires 0x15, 0x00, # . Logical Minimum (0), 452*1dec39d4SBenjamin Tissoires 0x26, 0xFF, 0x0F, # . Logical Maximum (4095), 453*1dec39d4SBenjamin Tissoires 0x75, 0x10, # . Report Size (16), 454*1dec39d4SBenjamin Tissoires 0x95, 0x01, # . Report Count (1), 455*1dec39d4SBenjamin Tissoires 0x81, 0x02, # . Input (Variable), 456*1dec39d4SBenjamin Tissoires 0xC0, # . End Collection, 457*1dec39d4SBenjamin Tissoires 0xC0 # . End Collection 458*1dec39d4SBenjamin Tissoires ] 459*1dec39d4SBenjamin Tissoires # fmt: on 460*1dec39d4SBenjamin Tissoires 461*1dec39d4SBenjamin Tissoires def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)): 462*1dec39d4SBenjamin Tissoires super().__init__(rdesc, name, info) 463*1dec39d4SBenjamin Tissoires self.default_reportID = 16 464*1dec39d4SBenjamin Tissoires 465*1dec39d4SBenjamin Tissoires 466*1dec39d4SBenjamin Tissoiresclass PTHX60_Pen(BaseTablet): 467*1dec39d4SBenjamin Tissoires """ 468*1dec39d4SBenjamin Tissoires Pen interface of a PTH-660 / PTH-860 / PTH-460 tablet. 469*1dec39d4SBenjamin Tissoires 470*1dec39d4SBenjamin Tissoires This generation of devices are nearly identical to each other, though 471*1dec39d4SBenjamin Tissoires the PTH-460 uses a slightly different descriptor construction (splits 472*1dec39d4SBenjamin Tissoires the pad among several physical collections) 473*1dec39d4SBenjamin Tissoires """ 474*1dec39d4SBenjamin Tissoires 475*1dec39d4SBenjamin Tissoires def __init__(self, rdesc=None, name=None, info=None): 476*1dec39d4SBenjamin Tissoires super().__init__(rdesc, name, info) 477*1dec39d4SBenjamin Tissoires self.default_reportID = 16 478*1dec39d4SBenjamin Tissoires 479*1dec39d4SBenjamin Tissoires 480*1dec39d4SBenjamin Tissoiresclass BaseTest: 481*1dec39d4SBenjamin Tissoires class TestTablet(base.BaseTestCase.TestUhid): 482*1dec39d4SBenjamin Tissoires kernel_modules = [KERNEL_MODULE] 483*1dec39d4SBenjamin Tissoires 484*1dec39d4SBenjamin Tissoires def sync_and_assert_events( 485*1dec39d4SBenjamin Tissoires self, report, expected_events, auto_syn=True, strict=False 486*1dec39d4SBenjamin Tissoires ): 487*1dec39d4SBenjamin Tissoires """ 488*1dec39d4SBenjamin Tissoires Assert we see the expected events in response to a report. 489*1dec39d4SBenjamin Tissoires """ 490*1dec39d4SBenjamin Tissoires uhdev = self.uhdev 491*1dec39d4SBenjamin Tissoires syn_event = self.syn_event 492*1dec39d4SBenjamin Tissoires if auto_syn: 493*1dec39d4SBenjamin Tissoires expected_events.append(syn_event) 494*1dec39d4SBenjamin Tissoires actual_events = uhdev.next_sync_events() 495*1dec39d4SBenjamin Tissoires self.debug_reports(report, uhdev, actual_events) 496*1dec39d4SBenjamin Tissoires if strict: 497*1dec39d4SBenjamin Tissoires self.assertInputEvents(expected_events, actual_events) 498*1dec39d4SBenjamin Tissoires else: 499*1dec39d4SBenjamin Tissoires self.assertInputEventsIn(expected_events, actual_events) 500*1dec39d4SBenjamin Tissoires 501*1dec39d4SBenjamin Tissoires def get_usages(self, uhdev): 502*1dec39d4SBenjamin Tissoires def get_report_usages(report): 503*1dec39d4SBenjamin Tissoires application = report.application 504*1dec39d4SBenjamin Tissoires for field in report.fields: 505*1dec39d4SBenjamin Tissoires if field.usages is not None: 506*1dec39d4SBenjamin Tissoires for usage in field.usages: 507*1dec39d4SBenjamin Tissoires yield (field, usage, application) 508*1dec39d4SBenjamin Tissoires else: 509*1dec39d4SBenjamin Tissoires yield (field, field.usage, application) 510*1dec39d4SBenjamin Tissoires 511*1dec39d4SBenjamin Tissoires desc = uhdev.parsed_rdesc 512*1dec39d4SBenjamin Tissoires reports = [ 513*1dec39d4SBenjamin Tissoires *desc.input_reports.values(), 514*1dec39d4SBenjamin Tissoires *desc.feature_reports.values(), 515*1dec39d4SBenjamin Tissoires *desc.output_reports.values(), 516*1dec39d4SBenjamin Tissoires ] 517*1dec39d4SBenjamin Tissoires for report in reports: 518*1dec39d4SBenjamin Tissoires for usage in get_report_usages(report): 519*1dec39d4SBenjamin Tissoires yield usage 520*1dec39d4SBenjamin Tissoires 521*1dec39d4SBenjamin Tissoires def assertName(self, uhdev, type): 522*1dec39d4SBenjamin Tissoires """ 523*1dec39d4SBenjamin Tissoires Assert that the name is as we expect. 524*1dec39d4SBenjamin Tissoires 525*1dec39d4SBenjamin Tissoires The Wacom driver applies a number of decorations to the name 526*1dec39d4SBenjamin Tissoires provided by the hardware. We cannot rely on the definition of 527*1dec39d4SBenjamin Tissoires this assertion from the base class to work properly. 528*1dec39d4SBenjamin Tissoires """ 529*1dec39d4SBenjamin Tissoires evdev = uhdev.get_evdev() 530*1dec39d4SBenjamin Tissoires expected_name = uhdev.name + type 531*1dec39d4SBenjamin Tissoires if "wacom" not in expected_name.lower(): 532*1dec39d4SBenjamin Tissoires expected_name = "Wacom " + expected_name 533*1dec39d4SBenjamin Tissoires assert evdev.name == expected_name 534*1dec39d4SBenjamin Tissoires 535*1dec39d4SBenjamin Tissoires def test_descriptor_physicals(self): 536*1dec39d4SBenjamin Tissoires """ 537*1dec39d4SBenjamin Tissoires Verify that all HID usages which should have a physical range 538*1dec39d4SBenjamin Tissoires actually do, and those which shouldn't don't. Also verify that 539*1dec39d4SBenjamin Tissoires the associated unit is correct and within a sensible range. 540*1dec39d4SBenjamin Tissoires """ 541*1dec39d4SBenjamin Tissoires 542*1dec39d4SBenjamin Tissoires def usage_id(page_name, usage_name): 543*1dec39d4SBenjamin Tissoires page = HUT.usage_page_from_name(page_name) 544*1dec39d4SBenjamin Tissoires return (page.page_id << 16) | page[usage_name].usage 545*1dec39d4SBenjamin Tissoires 546*1dec39d4SBenjamin Tissoires required = { 547*1dec39d4SBenjamin Tissoires usage_id("Generic Desktop", "X"): PhysRange( 548*1dec39d4SBenjamin Tissoires PhysRange.CENTIMETER, 5, 150 549*1dec39d4SBenjamin Tissoires ), 550*1dec39d4SBenjamin Tissoires usage_id("Generic Desktop", "Y"): PhysRange( 551*1dec39d4SBenjamin Tissoires PhysRange.CENTIMETER, 5, 150 552*1dec39d4SBenjamin Tissoires ), 553*1dec39d4SBenjamin Tissoires usage_id("Digitizers", "Width"): PhysRange( 554*1dec39d4SBenjamin Tissoires PhysRange.CENTIMETER, 5, 150 555*1dec39d4SBenjamin Tissoires ), 556*1dec39d4SBenjamin Tissoires usage_id("Digitizers", "Height"): PhysRange( 557*1dec39d4SBenjamin Tissoires PhysRange.CENTIMETER, 5, 150 558*1dec39d4SBenjamin Tissoires ), 559*1dec39d4SBenjamin Tissoires usage_id("Digitizers", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180), 560*1dec39d4SBenjamin Tissoires usage_id("Digitizers", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180), 561*1dec39d4SBenjamin Tissoires usage_id("Digitizers", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360), 562*1dec39d4SBenjamin Tissoires usage_id("Wacom", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180), 563*1dec39d4SBenjamin Tissoires usage_id("Wacom", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180), 564*1dec39d4SBenjamin Tissoires usage_id("Wacom", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360), 565*1dec39d4SBenjamin Tissoires usage_id("Wacom", "X"): PhysRange(PhysRange.CENTIMETER, 5, 150), 566*1dec39d4SBenjamin Tissoires usage_id("Wacom", "Y"): PhysRange(PhysRange.CENTIMETER, 5, 150), 567*1dec39d4SBenjamin Tissoires usage_id("Wacom", "Wacom TouchRing"): PhysRange( 568*1dec39d4SBenjamin Tissoires PhysRange.DEGREE, 358, 360 569*1dec39d4SBenjamin Tissoires ), 570*1dec39d4SBenjamin Tissoires usage_id("Wacom", "Wacom Offset Left"): PhysRange( 571*1dec39d4SBenjamin Tissoires PhysRange.CENTIMETER, 0, 0.5 572*1dec39d4SBenjamin Tissoires ), 573*1dec39d4SBenjamin Tissoires usage_id("Wacom", "Wacom Offset Top"): PhysRange( 574*1dec39d4SBenjamin Tissoires PhysRange.CENTIMETER, 0, 0.5 575*1dec39d4SBenjamin Tissoires ), 576*1dec39d4SBenjamin Tissoires usage_id("Wacom", "Wacom Offset Right"): PhysRange( 577*1dec39d4SBenjamin Tissoires PhysRange.CENTIMETER, 0, 0.5 578*1dec39d4SBenjamin Tissoires ), 579*1dec39d4SBenjamin Tissoires usage_id("Wacom", "Wacom Offset Bottom"): PhysRange( 580*1dec39d4SBenjamin Tissoires PhysRange.CENTIMETER, 0, 0.5 581*1dec39d4SBenjamin Tissoires ), 582*1dec39d4SBenjamin Tissoires } 583*1dec39d4SBenjamin Tissoires for field, usage, application in self.get_usages(self.uhdev): 584*1dec39d4SBenjamin Tissoires if application == usage_id("Generic Desktop", "Mouse"): 585*1dec39d4SBenjamin Tissoires # Ignore the vestigial Mouse collection which exists 586*1dec39d4SBenjamin Tissoires # on Wacom tablets only for backwards compatibility. 587*1dec39d4SBenjamin Tissoires continue 588*1dec39d4SBenjamin Tissoires 589*1dec39d4SBenjamin Tissoires expect_physical = usage in required 590*1dec39d4SBenjamin Tissoires 591*1dec39d4SBenjamin Tissoires phys_set = field.physical_min != 0 or field.physical_max != 0 592*1dec39d4SBenjamin Tissoires assert phys_set == expect_physical 593*1dec39d4SBenjamin Tissoires 594*1dec39d4SBenjamin Tissoires unit_set = field.unit != 0 595*1dec39d4SBenjamin Tissoires assert unit_set == expect_physical 596*1dec39d4SBenjamin Tissoires 597*1dec39d4SBenjamin Tissoires if unit_set: 598*1dec39d4SBenjamin Tissoires assert required[usage].contains(field) 599*1dec39d4SBenjamin Tissoires 600*1dec39d4SBenjamin Tissoires def test_prop_direct(self): 601*1dec39d4SBenjamin Tissoires """ 602*1dec39d4SBenjamin Tissoires Todo: Verify that INPUT_PROP_DIRECT is set on display devices. 603*1dec39d4SBenjamin Tissoires """ 604*1dec39d4SBenjamin Tissoires pass 605*1dec39d4SBenjamin Tissoires 606*1dec39d4SBenjamin Tissoires def test_prop_pointer(self): 607*1dec39d4SBenjamin Tissoires """ 608*1dec39d4SBenjamin Tissoires Todo: Verify that INPUT_PROP_POINTER is set on opaque devices. 609*1dec39d4SBenjamin Tissoires """ 610*1dec39d4SBenjamin Tissoires pass 611*1dec39d4SBenjamin Tissoires 612*1dec39d4SBenjamin Tissoires 613*1dec39d4SBenjamin Tissoiresclass PenTabletTest(BaseTest.TestTablet): 614*1dec39d4SBenjamin Tissoires def assertName(self, uhdev): 615*1dec39d4SBenjamin Tissoires super().assertName(uhdev, " Pen") 616*1dec39d4SBenjamin Tissoires 617*1dec39d4SBenjamin Tissoires 618*1dec39d4SBenjamin Tissoiresclass TouchTabletTest(BaseTest.TestTablet): 619*1dec39d4SBenjamin Tissoires def assertName(self, uhdev): 620*1dec39d4SBenjamin Tissoires super().assertName(uhdev, " Finger") 621*1dec39d4SBenjamin Tissoires 622*1dec39d4SBenjamin Tissoires 623*1dec39d4SBenjamin Tissoiresclass TestOpaqueTablet(PenTabletTest): 624*1dec39d4SBenjamin Tissoires def create_device(self): 625*1dec39d4SBenjamin Tissoires return OpaqueTablet() 626*1dec39d4SBenjamin Tissoires 627*1dec39d4SBenjamin Tissoires def test_sanity(self): 628*1dec39d4SBenjamin Tissoires """ 629*1dec39d4SBenjamin Tissoires Bring a pen into contact with the tablet, then remove it. 630*1dec39d4SBenjamin Tissoires 631*1dec39d4SBenjamin Tissoires Ensure that we get the basic tool/touch/motion events that should 632*1dec39d4SBenjamin Tissoires be sent by the driver. 633*1dec39d4SBenjamin Tissoires """ 634*1dec39d4SBenjamin Tissoires uhdev = self.uhdev 635*1dec39d4SBenjamin Tissoires 636*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 637*1dec39d4SBenjamin Tissoires uhdev.event( 638*1dec39d4SBenjamin Tissoires 100, 639*1dec39d4SBenjamin Tissoires 200, 640*1dec39d4SBenjamin Tissoires pressure=300, 641*1dec39d4SBenjamin Tissoires buttons=Buttons.clear(), 642*1dec39d4SBenjamin Tissoires toolid=ToolID(serial=1, tooltype=1), 643*1dec39d4SBenjamin Tissoires proximity=ProximityState.IN_RANGE, 644*1dec39d4SBenjamin Tissoires ), 645*1dec39d4SBenjamin Tissoires [ 646*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1), 647*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100), 648*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200), 649*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1), 650*1dec39d4SBenjamin Tissoires ], 651*1dec39d4SBenjamin Tissoires ) 652*1dec39d4SBenjamin Tissoires 653*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 654*1dec39d4SBenjamin Tissoires uhdev.event(110, 220, pressure=0), 655*1dec39d4SBenjamin Tissoires [ 656*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110), 657*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 220), 658*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0), 659*1dec39d4SBenjamin Tissoires ], 660*1dec39d4SBenjamin Tissoires ) 661*1dec39d4SBenjamin Tissoires 662*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 663*1dec39d4SBenjamin Tissoires uhdev.event( 664*1dec39d4SBenjamin Tissoires 120, 665*1dec39d4SBenjamin Tissoires 230, 666*1dec39d4SBenjamin Tissoires pressure=0, 667*1dec39d4SBenjamin Tissoires toolid=ToolID.clear(), 668*1dec39d4SBenjamin Tissoires proximity=ProximityState.OUT, 669*1dec39d4SBenjamin Tissoires ), 670*1dec39d4SBenjamin Tissoires [ 671*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0), 672*1dec39d4SBenjamin Tissoires ], 673*1dec39d4SBenjamin Tissoires ) 674*1dec39d4SBenjamin Tissoires 675*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 676*1dec39d4SBenjamin Tissoires uhdev.event(130, 240, pressure=0), [], auto_syn=False, strict=True 677*1dec39d4SBenjamin Tissoires ) 678*1dec39d4SBenjamin Tissoires 679*1dec39d4SBenjamin Tissoires 680*1dec39d4SBenjamin Tissoiresclass TestOpaqueCTLTablet(TestOpaqueTablet): 681*1dec39d4SBenjamin Tissoires def create_device(self): 682*1dec39d4SBenjamin Tissoires return OpaqueCTLTablet() 683*1dec39d4SBenjamin Tissoires 684*1dec39d4SBenjamin Tissoires def test_buttons(self): 685*1dec39d4SBenjamin Tissoires """ 686*1dec39d4SBenjamin Tissoires Test that the barrel buttons (side switches) work as expected. 687*1dec39d4SBenjamin Tissoires 688*1dec39d4SBenjamin Tissoires Press and release each button individually to verify that we get 689*1dec39d4SBenjamin Tissoires the expected events. 690*1dec39d4SBenjamin Tissoires """ 691*1dec39d4SBenjamin Tissoires uhdev = self.uhdev 692*1dec39d4SBenjamin Tissoires 693*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 694*1dec39d4SBenjamin Tissoires uhdev.event( 695*1dec39d4SBenjamin Tissoires 100, 696*1dec39d4SBenjamin Tissoires 200, 697*1dec39d4SBenjamin Tissoires pressure=0, 698*1dec39d4SBenjamin Tissoires buttons=Buttons.clear(), 699*1dec39d4SBenjamin Tissoires toolid=ToolID(serial=1, tooltype=1), 700*1dec39d4SBenjamin Tissoires proximity=ProximityState.IN_RANGE, 701*1dec39d4SBenjamin Tissoires ), 702*1dec39d4SBenjamin Tissoires [ 703*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1), 704*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100), 705*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200), 706*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), 707*1dec39d4SBenjamin Tissoires ], 708*1dec39d4SBenjamin Tissoires ) 709*1dec39d4SBenjamin Tissoires 710*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 711*1dec39d4SBenjamin Tissoires uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=True)), 712*1dec39d4SBenjamin Tissoires [ 713*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1), 714*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), 715*1dec39d4SBenjamin Tissoires ], 716*1dec39d4SBenjamin Tissoires ) 717*1dec39d4SBenjamin Tissoires 718*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 719*1dec39d4SBenjamin Tissoires uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=False)), 720*1dec39d4SBenjamin Tissoires [ 721*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0), 722*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), 723*1dec39d4SBenjamin Tissoires ], 724*1dec39d4SBenjamin Tissoires ) 725*1dec39d4SBenjamin Tissoires 726*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 727*1dec39d4SBenjamin Tissoires uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=True)), 728*1dec39d4SBenjamin Tissoires [ 729*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 1), 730*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), 731*1dec39d4SBenjamin Tissoires ], 732*1dec39d4SBenjamin Tissoires ) 733*1dec39d4SBenjamin Tissoires 734*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 735*1dec39d4SBenjamin Tissoires uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=False)), 736*1dec39d4SBenjamin Tissoires [ 737*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 0), 738*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), 739*1dec39d4SBenjamin Tissoires ], 740*1dec39d4SBenjamin Tissoires ) 741*1dec39d4SBenjamin Tissoires 742*1dec39d4SBenjamin Tissoires 743*1dec39d4SBenjamin TissoiresPTHX60_Devices = [ 744*1dec39d4SBenjamin Tissoires {"rdesc": wacom_pth660_v145, "info": (0x3, 0x056A, 0x0357)}, 745*1dec39d4SBenjamin Tissoires {"rdesc": wacom_pth660_v150, "info": (0x3, 0x056A, 0x0357)}, 746*1dec39d4SBenjamin Tissoires {"rdesc": wacom_pth860_v145, "info": (0x3, 0x056A, 0x0358)}, 747*1dec39d4SBenjamin Tissoires {"rdesc": wacom_pth860_v150, "info": (0x3, 0x056A, 0x0358)}, 748*1dec39d4SBenjamin Tissoires {"rdesc": wacom_pth460_v105, "info": (0x3, 0x056A, 0x0392)}, 749*1dec39d4SBenjamin Tissoires] 750*1dec39d4SBenjamin Tissoires 751*1dec39d4SBenjamin TissoiresPTHX60_Names = [ 752*1dec39d4SBenjamin Tissoires "PTH-660/v145", 753*1dec39d4SBenjamin Tissoires "PTH-660/v150", 754*1dec39d4SBenjamin Tissoires "PTH-860/v145", 755*1dec39d4SBenjamin Tissoires "PTH-860/v150", 756*1dec39d4SBenjamin Tissoires "PTH-460/v105", 757*1dec39d4SBenjamin Tissoires] 758*1dec39d4SBenjamin Tissoires 759*1dec39d4SBenjamin Tissoires 760*1dec39d4SBenjamin Tissoiresclass TestPTHX60_Pen(TestOpaqueCTLTablet): 761*1dec39d4SBenjamin Tissoires @pytest.fixture( 762*1dec39d4SBenjamin Tissoires autouse=True, scope="class", params=PTHX60_Devices, ids=PTHX60_Names 763*1dec39d4SBenjamin Tissoires ) 764*1dec39d4SBenjamin Tissoires def set_device_params(self, request): 765*1dec39d4SBenjamin Tissoires request.cls.device_params = request.param 766*1dec39d4SBenjamin Tissoires 767*1dec39d4SBenjamin Tissoires def create_device(self): 768*1dec39d4SBenjamin Tissoires return PTHX60_Pen(**self.device_params) 769*1dec39d4SBenjamin Tissoires 770*1dec39d4SBenjamin Tissoires @pytest.mark.xfail 771*1dec39d4SBenjamin Tissoires def test_descriptor_physicals(self): 772*1dec39d4SBenjamin Tissoires # XFAIL: Various documented errata 773*1dec39d4SBenjamin Tissoires super().test_descriptor_physicals() 774*1dec39d4SBenjamin Tissoires 775*1dec39d4SBenjamin Tissoires def test_heartbeat_spurious(self): 776*1dec39d4SBenjamin Tissoires """ 777*1dec39d4SBenjamin Tissoires Test that the heartbeat report does not send spurious events. 778*1dec39d4SBenjamin Tissoires """ 779*1dec39d4SBenjamin Tissoires uhdev = self.uhdev 780*1dec39d4SBenjamin Tissoires 781*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 782*1dec39d4SBenjamin Tissoires uhdev.event( 783*1dec39d4SBenjamin Tissoires 100, 784*1dec39d4SBenjamin Tissoires 200, 785*1dec39d4SBenjamin Tissoires pressure=300, 786*1dec39d4SBenjamin Tissoires buttons=Buttons.clear(), 787*1dec39d4SBenjamin Tissoires toolid=ToolID(serial=1, tooltype=0x822), 788*1dec39d4SBenjamin Tissoires proximity=ProximityState.IN_RANGE, 789*1dec39d4SBenjamin Tissoires ), 790*1dec39d4SBenjamin Tissoires [ 791*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1), 792*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100), 793*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200), 794*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1), 795*1dec39d4SBenjamin Tissoires ], 796*1dec39d4SBenjamin Tissoires ) 797*1dec39d4SBenjamin Tissoires 798*1dec39d4SBenjamin Tissoires # Exactly zero events: not even a SYN 799*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 800*1dec39d4SBenjamin Tissoires uhdev.event_heartbeat(19), [], auto_syn=False, strict=True 801*1dec39d4SBenjamin Tissoires ) 802*1dec39d4SBenjamin Tissoires 803*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 804*1dec39d4SBenjamin Tissoires uhdev.event(110, 200, pressure=300), 805*1dec39d4SBenjamin Tissoires [ 806*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110), 807*1dec39d4SBenjamin Tissoires ], 808*1dec39d4SBenjamin Tissoires ) 809*1dec39d4SBenjamin Tissoires 810*1dec39d4SBenjamin Tissoires def test_empty_pad_sync(self): 811*1dec39d4SBenjamin Tissoires self.empty_pad_sync(num=3, denom=16, reverse=True) 812*1dec39d4SBenjamin Tissoires 813*1dec39d4SBenjamin Tissoires def empty_pad_sync(self, num, denom, reverse): 814*1dec39d4SBenjamin Tissoires """ 815*1dec39d4SBenjamin Tissoires Test that multiple pad collections do not trigger empty syncs. 816*1dec39d4SBenjamin Tissoires """ 817*1dec39d4SBenjamin Tissoires 818*1dec39d4SBenjamin Tissoires def offset_rotation(value): 819*1dec39d4SBenjamin Tissoires """ 820*1dec39d4SBenjamin Tissoires Offset touchring rotation values by the same factor as the 821*1dec39d4SBenjamin Tissoires Linux kernel. Tablets historically don't use the same origin 822*1dec39d4SBenjamin Tissoires as HID, and it sometimes changes from tablet to tablet... 823*1dec39d4SBenjamin Tissoires """ 824*1dec39d4SBenjamin Tissoires evdev = self.uhdev.get_evdev() 825*1dec39d4SBenjamin Tissoires info = evdev.absinfo[libevdev.EV_ABS.ABS_WHEEL] 826*1dec39d4SBenjamin Tissoires delta = info.maximum - info.minimum + 1 827*1dec39d4SBenjamin Tissoires if reverse: 828*1dec39d4SBenjamin Tissoires value = info.maximum - value 829*1dec39d4SBenjamin Tissoires value += num * delta // denom 830*1dec39d4SBenjamin Tissoires if value > info.maximum: 831*1dec39d4SBenjamin Tissoires value -= delta 832*1dec39d4SBenjamin Tissoires elif value < info.minimum: 833*1dec39d4SBenjamin Tissoires value += delta 834*1dec39d4SBenjamin Tissoires return value 835*1dec39d4SBenjamin Tissoires 836*1dec39d4SBenjamin Tissoires uhdev = self.uhdev 837*1dec39d4SBenjamin Tissoires uhdev.application = "Pad" 838*1dec39d4SBenjamin Tissoires evdev = uhdev.get_evdev() 839*1dec39d4SBenjamin Tissoires 840*1dec39d4SBenjamin Tissoires print(evdev.name) 841*1dec39d4SBenjamin Tissoires self.sync_and_assert_events( 842*1dec39d4SBenjamin Tissoires uhdev.event_pad(reportID=17, ring=0, ek0=1), 843*1dec39d4SBenjamin Tissoires [ 844*1dec39d4SBenjamin Tissoires libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 1), 845 libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(0)), 846 libevdev.InputEvent(libevdev.EV_ABS.ABS_MISC, 15), 847 ], 848 ) 849 850 self.sync_and_assert_events( 851 uhdev.event_pad(reportID=17, ring=1, ek0=1), 852 [libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(1))], 853 ) 854 855 self.sync_and_assert_events( 856 uhdev.event_pad(reportID=17, ring=2, ek0=0), 857 [ 858 libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(2)), 859 libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 0), 860 ], 861 ) 862 863 864class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest): 865 def create_device(self): 866 return test_multitouch.Digitizer( 867 "DTH 2452", 868 rdesc="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", 869 input_info=(0x3, 0x056A, 0x0383), 870 ) 871 872 def test_contact_id_0(self): 873 """ 874 Bring a finger in contact with the tablet, then hold it down and remove it. 875 876 Ensure that even with contact ID = 0 which is usually given as an invalid 877 touch event by most tablets with the exception of a few, that given the 878 confidence bit is set to 1 it should process it as a valid touch to cover 879 the few tablets using contact ID = 0 as a valid touch value. 880 """ 881 uhdev = self.uhdev 882 evdev = uhdev.get_evdev() 883 884 t0 = test_multitouch.Touch(0, 50, 100) 885 r = uhdev.event([t0]) 886 events = uhdev.next_sync_events() 887 self.debug_reports(r, uhdev, events) 888 889 slot = self.get_slot(uhdev, t0, 0) 890 891 assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events 892 assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 893 assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50 894 assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100 895 896 t0.tipswitch = False 897 if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks: 898 t0.inrange = False 899 r = uhdev.event([t0]) 900 events = uhdev.next_sync_events() 901 self.debug_reports(r, uhdev, events) 902 assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events 903 assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 904 905 def test_confidence_false(self): 906 """ 907 Bring a finger in contact with the tablet with confidence set to false. 908 909 Ensure that the confidence bit being set to false should not result in a touch event. 910 """ 911 uhdev = self.uhdev 912 evdev = uhdev.get_evdev() 913 914 t0 = test_multitouch.Touch(1, 50, 100) 915 t0.confidence = False 916 r = uhdev.event([t0]) 917 events = uhdev.next_sync_events() 918 self.debug_reports(r, uhdev, events) 919 920 slot = self.get_slot(uhdev, t0, 0) 921 922 assert not events