xref: /openbmc/linux/tools/testing/selftests/hid/tests/test_wacom_generic.py (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
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