2 # SPDX-License-Identifier: GPL-2.0
3 # -*- coding: utf-8 -*-
5 # Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
6 # Copyright (c) 2017 Red Hat, Inc.
7 # Copyright (c) 2020 Wacom Technology Corp.
10 # Jason Gerecke <jason.gerecke@wacom.com>
13 Tests for the Wacom driver generic codepath.
15 This module tests the function of the Wacom driver's generic codepath.
16 The generic codepath is used by devices which are not explicitly listed
17 in the driver's device table. It uses the device's HID descriptor to
18 decode reports sent by the device.
21 from .descriptors_wacom
import (
30 from collections
import namedtuple
32 from hidtools
.hut
import HUT
33 from hidtools
.hid
import HidUnit
35 from . import test_multitouch
41 logger
= logging
.getLogger("hidtools.test.wacom")
43 KERNEL_MODULE
= ("wacom", "wacom")
46 class ProximityState(Enum
):
48 Enumeration of allowed proximity states.
51 # Tool is not able to be sensed by the device
54 # Tool is close enough to be sensed, but some data may be invalid
58 # Tool is close enough to be sensed with high accuracy. All data
62 def fill(self
, reportdata
):
63 """Fill a report with approrpiate HID properties/values."""
64 reportdata
.inrange
= self
in [ProximityState
.IN_RANGE
]
65 reportdata
.wacomsense
= self
in [
66 ProximityState
.IN_PROXIMITY
,
67 ProximityState
.IN_RANGE
,
73 Placeholder for HID report values.
84 Describes the state of each of the buttons / "side switches" that
85 may be present on a stylus. Buttons set to 'None' indicate the
86 state is "unchanged" since the previous event.
89 primary
= attr
.ib(default
=None)
90 secondary
= attr
.ib(default
=None)
91 tertiary
= attr
.ib(default
=None)
95 """Button object with all states cleared."""
96 return Buttons(False, False, False)
98 def fill(self
, reportdata
):
99 """Fill a report with approrpiate HID properties/values."""
100 reportdata
.barrelswitch
= int(self
.primary
or 0)
101 reportdata
.secondarybarrelswitch
= int(self
.secondary
or 0)
102 reportdata
.b3
= int(self
.tertiary
or 0)
108 Stylus tool identifiers.
110 Contains values used to identify a specific stylus, e.g. its serial
111 number and tool-type identifier. Values of ``0`` may sometimes be
112 used for the out-of-range condition.
120 """ToolID object with all fields cleared."""
123 def fill(self
, reportdata
):
124 """Fill a report with approrpiate HID properties/values."""
125 reportdata
.transducerserialnumber
= self
.serial
& 0xFFFFFFFF
126 reportdata
.serialhi
= (self
.serial
>> 32) & 0xFFFFFFFF
127 reportdata
.tooltype
= self
.tooltype
133 Range of HID physical values, with units.
140 CENTIMETER
= HidUnit
.from_string("SILinear: cm")
141 DEGREE
= HidUnit
.from_string("EnglishRotation: deg")
143 def contains(self
, field
):
145 Check if the physical size of the provided field is in range.
147 Compare the physical size described by the provided HID field
148 against the range of sizes described by this object. This is
149 an exclusive range comparison (e.g. 0 cm is not within the
150 range 0 cm - 5 cm) and exact unit comparison (e.g. 1 inch is
151 not within the range 0 cm - 5 cm).
153 phys_size
= (field
.physical_max
- field
.physical_min
) * 10 ** (field
.unit_exp
)
155 field
.unit
== self
.unit
.value
156 and phys_size
> self
.min_size
157 and phys_size
< self
.max_size
161 class BaseTablet(base
.UHIDTestDevice
):
163 Skeleton object for all kinds of tablet devices.
166 def __init__(self
, rdesc
, name
=None, info
=None):
167 assert rdesc
is not None
168 super().__init
__(name
, "Pen", input_info
=info
, rdesc
=rdesc
)
169 self
.buttons
= Buttons
.clear()
170 self
.toolid
= ToolID
.clear()
171 self
.proximity
= ProximityState
.OUT
176 def match_evdev_rule(self
, application
, evdev
):
178 Filter out evdev nodes based on the requested application.
180 The Wacom driver may create several device nodes for each USB
181 interface device. It is crucial that we run tests with the
182 expected device node or things will obviously go off the rails.
183 Use the Wacom driver's usual naming conventions to apply a
184 sensible default filter.
186 if application
in ["Pen", "Pad"]:
187 return evdev
.name
.endswith(application
)
192 self
, x
, y
, pressure
, buttons
=None, toolid
=None, proximity
=None, reportID
=None
195 Return an input report for this device.
199 :param pressure: pressure
200 :param buttons: stylus button state. Use ``None`` for unchanged.
201 :param toolid: tool identifiers. Use ``None`` for unchanged.
202 :param proximity: a ProximityState indicating the sensor's ability
203 to detect and report attributes of this tool. Use ``None``
205 :param reportID: the numeric report ID for this report, if needed
207 if buttons
is not None:
208 self
.buttons
= buttons
209 buttons
= self
.buttons
211 if toolid
is not None:
215 if proximity
is not None:
216 self
.proximity
= proximity
217 proximity
= self
.proximity
219 reportID
= reportID
or self
.default_reportID
221 report
= ReportData()
224 report
.tippressure
= pressure
225 report
.tipswitch
= pressure
> 0
227 proximity
.fill(report
)
230 return super().create_report(report
, reportID
=reportID
)
232 def create_report_heartbeat(self
, reportID
):
234 Return a heartbeat input report for this device.
236 Heartbeat reports generally contain battery status information,
239 report
= ReportData()
240 report
.wacombatterycharging
= 1
241 return super().create_report(report
, reportID
=reportID
)
243 def create_report_pad(self
, reportID
, ring
, ek0
):
244 report
= ReportData()
255 report
.wacomtouchring
= ring
256 report
.wacomtouchringstatus
= 1
258 report
.wacomtouchring
= 0x7F
259 report
.wacomtouchringstatus
= 0
261 report
.wacomexpresskey00
= ek0
262 return super().create_report(report
, reportID
=reportID
)
264 def event(self
, x
, y
, pressure
, buttons
=None, toolid
=None, proximity
=None):
266 Send an input event on the default report ID.
270 :param buttons: stylus button state. Use ``None`` for unchanged.
271 :param toolid: tool identifiers. Use ``None`` for unchanged.
272 :param proximity: a ProximityState indicating the sensor's ability
273 to detect and report attributes of this tool. Use ``None``
276 r
= self
.create_report(x
, y
, pressure
, buttons
, toolid
, proximity
)
277 self
.call_input_event(r
)
280 def event_heartbeat(self
, reportID
):
282 Send a heartbeat event on the requested report ID.
284 r
= self
.create_report_heartbeat(reportID
)
285 self
.call_input_event(r
)
288 def event_pad(self
, reportID
, ring
=None, ek0
=None):
290 Send a pad event on the requested report ID.
292 r
= self
.create_report_pad(reportID
, ring
, ek0
)
293 self
.call_input_event(r
)
296 def get_report(self
, req
, rnum
, rtype
):
297 if rtype
!= self
.UHID_FEATURE_REPORT
:
301 for v
in self
.parsed_rdesc
.feature_reports
.values():
302 if v
.report_ID
== rnum
:
309 result
= self
.create_report_offset(rdesc
) or result
312 def create_report_offset(self
, rdesc
):
316 "Wacom Offset Right",
317 "Wacom Offset Bottom",
319 if not set(require
).issubset(set([f
.usage_name
for f
in rdesc
])):
322 report
= ReportData()
323 report
.wacomoffsetleft
= self
.offset
324 report
.wacomoffsettop
= self
.offset
325 report
.wacomoffsetright
= self
.offset
326 report
.wacomoffsetbottom
= self
.offset
327 r
= rdesc
.create_report([report
], None)
331 class OpaqueTablet(BaseTablet
):
333 Bare-bones opaque tablet with a minimum of features.
335 A tablet stripped down to its absolute core. It is capable of
336 reporting X/Y position and if the pen is in contact. No pressure,
337 no barrel switches, no eraser. Notably it *does* report an "In
338 Range" flag, but this is only because the Wacom driver expects
339 one to function properly. The device uses only standard HID usages,
340 not any of Wacom's vendor-defined pages.
344 report_descriptor
= [
345 0x05, 0x0D, # . Usage Page (Digitizer),
346 0x09, 0x01, # . Usage (Digitizer),
347 0xA1, 0x01, # . Collection (Application),
348 0x85, 0x01, # . Report ID (1),
349 0x09, 0x20, # . Usage (Stylus),
350 0xA1, 0x00, # . Collection (Physical),
351 0x09, 0x42, # . Usage (Tip Switch),
352 0x09, 0x32, # . Usage (In Range),
353 0x15, 0x00, # . Logical Minimum (0),
354 0x25, 0x01, # . Logical Maximum (1),
355 0x75, 0x01, # . Report Size (1),
356 0x95, 0x02, # . Report Count (2),
357 0x81, 0x02, # . Input (Variable),
358 0x95, 0x06, # . Report Count (6),
359 0x81, 0x03, # . Input (Constant, Variable),
360 0x05, 0x01, # . Usage Page (Desktop),
361 0x09, 0x30, # . Usage (X),
362 0x27, 0x80, 0x3E, 0x00, 0x00, # . Logical Maximum (16000),
363 0x47, 0x80, 0x3E, 0x00, 0x00, # . Physical Maximum (16000),
364 0x65, 0x11, # . Unit (Centimeter),
365 0x55, 0x0D, # . Unit Exponent (13),
366 0x75, 0x10, # . Report Size (16),
367 0x95, 0x01, # . Report Count (1),
368 0x81, 0x02, # . Input (Variable),
369 0x09, 0x31, # . Usage (Y),
370 0x27, 0x28, 0x23, 0x00, 0x00, # . Logical Maximum (9000),
371 0x47, 0x28, 0x23, 0x00, 0x00, # . Physical Maximum (9000),
372 0x81, 0x02, # . Input (Variable),
373 0xC0, # . End Collection,
374 0xC0, # . End Collection,
378 def __init__(self
, rdesc
=report_descriptor
, name
=None, info
=(0x3, 0x056A, 0x9999)):
379 super().__init
__(rdesc
, name
, info
)
380 self
.default_reportID
= 1
383 class OpaqueCTLTablet(BaseTablet
):
385 Opaque tablet similar to something in the CTL product line.
387 A pen-only tablet with most basic features you would expect from
388 an actual device. Position, eraser, pressure, barrel buttons.
389 Uses the Wacom vendor-defined usage page.
393 report_descriptor
= [
394 0x06, 0x0D, 0xFF, # . Usage Page (Vnd Wacom Emr),
395 0x09, 0x01, # . Usage (Digitizer),
396 0xA1, 0x01, # . Collection (Application),
397 0x85, 0x10, # . Report ID (16),
398 0x09, 0x20, # . Usage (Stylus),
399 0x35, 0x00, # . Physical Minimum (0),
400 0x45, 0x00, # . Physical Maximum (0),
401 0x15, 0x00, # . Logical Minimum (0),
402 0x25, 0x01, # . Logical Maximum (1),
403 0xA1, 0x00, # . Collection (Physical),
404 0x09, 0x42, # . Usage (Tip Switch),
405 0x09, 0x44, # . Usage (Barrel Switch),
406 0x09, 0x5A, # . Usage (Secondary Barrel Switch),
407 0x09, 0x45, # . Usage (Eraser),
408 0x09, 0x3C, # . Usage (Invert),
409 0x09, 0x32, # . Usage (In Range),
410 0x09, 0x36, # . Usage (In Proximity),
411 0x25, 0x01, # . Logical Maximum (1),
412 0x75, 0x01, # . Report Size (1),
413 0x95, 0x07, # . Report Count (7),
414 0x81, 0x02, # . Input (Variable),
415 0x95, 0x01, # . Report Count (1),
416 0x81, 0x03, # . Input (Constant, Variable),
417 0x0A, 0x30, 0x01, # . Usage (X),
418 0x65, 0x11, # . Unit (Centimeter),
419 0x55, 0x0D, # . Unit Exponent (13),
420 0x47, 0x80, 0x3E, 0x00, 0x00, # . Physical Maximum (16000),
421 0x27, 0x80, 0x3E, 0x00, 0x00, # . Logical Maximum (16000),
422 0x75, 0x18, # . Report Size (24),
423 0x95, 0x01, # . Report Count (1),
424 0x81, 0x02, # . Input (Variable),
425 0x0A, 0x31, 0x01, # . Usage (Y),
426 0x47, 0x28, 0x23, 0x00, 0x00, # . Physical Maximum (9000),
427 0x27, 0x28, 0x23, 0x00, 0x00, # . Logical Maximum (9000),
428 0x81, 0x02, # . Input (Variable),
429 0x09, 0x30, # . Usage (Tip Pressure),
430 0x55, 0x00, # . Unit Exponent (0),
431 0x65, 0x00, # . Unit,
432 0x47, 0x00, 0x00, 0x00, 0x00, # . Physical Maximum (0),
433 0x26, 0xFF, 0x0F, # . Logical Maximum (4095),
434 0x75, 0x10, # . Report Size (16),
435 0x81, 0x02, # . Input (Variable),
436 0x75, 0x08, # . Report Size (8),
437 0x95, 0x06, # . Report Count (6),
438 0x81, 0x03, # . Input (Constant, Variable),
439 0x0A, 0x32, 0x01, # . Usage (Z),
440 0x25, 0x3F, # . Logical Maximum (63),
441 0x75, 0x08, # . Report Size (8),
442 0x95, 0x01, # . Report Count (1),
443 0x81, 0x02, # . Input (Variable),
444 0x09, 0x5B, # . Usage (Transducer Serial Number),
445 0x09, 0x5C, # . Usage (Transducer Serial Number Hi),
446 0x17, 0x00, 0x00, 0x00, 0x80, # . Logical Minimum (-2147483648),
447 0x27, 0xFF, 0xFF, 0xFF, 0x7F, # . Logical Maximum (2147483647),
448 0x75, 0x20, # . Report Size (32),
449 0x95, 0x02, # . Report Count (2),
450 0x81, 0x02, # . Input (Variable),
451 0x09, 0x77, # . Usage (Tool Type),
452 0x15, 0x00, # . Logical Minimum (0),
453 0x26, 0xFF, 0x0F, # . Logical Maximum (4095),
454 0x75, 0x10, # . Report Size (16),
455 0x95, 0x01, # . Report Count (1),
456 0x81, 0x02, # . Input (Variable),
457 0xC0, # . End Collection,
458 0xC0 # . End Collection
462 def __init__(self
, rdesc
=report_descriptor
, name
=None, info
=(0x3, 0x056A, 0x9999)):
463 super().__init
__(rdesc
, name
, info
)
464 self
.default_reportID
= 16
467 class PTHX60_Pen(BaseTablet
):
469 Pen interface of a PTH-660 / PTH-860 / PTH-460 tablet.
471 This generation of devices are nearly identical to each other, though
472 the PTH-460 uses a slightly different descriptor construction (splits
473 the pad among several physical collections)
476 def __init__(self
, rdesc
=None, name
=None, info
=None):
477 super().__init
__(rdesc
, name
, info
)
478 self
.default_reportID
= 16
482 class TestTablet(base
.BaseTestCase
.TestUhid
):
483 kernel_modules
= [KERNEL_MODULE
]
485 def sync_and_assert_events(
486 self
, report
, expected_events
, auto_syn
=True, strict
=False
489 Assert we see the expected events in response to a report.
492 syn_event
= self
.syn_event
494 expected_events
.append(syn_event
)
495 actual_events
= uhdev
.next_sync_events()
496 self
.debug_reports(report
, uhdev
, actual_events
)
498 self
.assertInputEvents(expected_events
, actual_events
)
500 self
.assertInputEventsIn(expected_events
, actual_events
)
502 def get_usages(self
, uhdev
):
503 def get_report_usages(report
):
504 application
= report
.application
505 for field
in report
.fields
:
506 if field
.usages
is not None:
507 for usage
in field
.usages
:
508 yield (field
, usage
, application
)
510 yield (field
, field
.usage
, application
)
512 desc
= uhdev
.parsed_rdesc
514 *desc
.input_reports
.values(),
515 *desc
.feature_reports
.values(),
516 *desc
.output_reports
.values(),
518 for report
in reports
:
519 for usage
in get_report_usages(report
):
522 def assertName(self
, uhdev
, type):
524 Assert that the name is as we expect.
526 The Wacom driver applies a number of decorations to the name
527 provided by the hardware. We cannot rely on the definition of
528 this assertion from the base class to work properly.
530 evdev
= uhdev
.get_evdev()
531 expected_name
= uhdev
.name
+ type
532 if "wacom" not in expected_name
.lower():
533 expected_name
= "Wacom " + expected_name
534 assert evdev
.name
== expected_name
536 def test_descriptor_physicals(self
):
538 Verify that all HID usages which should have a physical range
539 actually do, and those which shouldn't don't. Also verify that
540 the associated unit is correct and within a sensible range.
543 def usage_id(page_name
, usage_name
):
544 page
= HUT
.usage_page_from_name(page_name
)
545 return (page
.page_id
<< 16) | page
[usage_name
].usage
548 usage_id("Generic Desktop", "X"): PhysRange(
549 PhysRange
.CENTIMETER
, 5, 150
551 usage_id("Generic Desktop", "Y"): PhysRange(
552 PhysRange
.CENTIMETER
, 5, 150
554 usage_id("Digitizers", "Width"): PhysRange(
555 PhysRange
.CENTIMETER
, 5, 150
557 usage_id("Digitizers", "Height"): PhysRange(
558 PhysRange
.CENTIMETER
, 5, 150
560 usage_id("Digitizers", "X Tilt"): PhysRange(PhysRange
.DEGREE
, 90, 180),
561 usage_id("Digitizers", "Y Tilt"): PhysRange(PhysRange
.DEGREE
, 90, 180),
562 usage_id("Digitizers", "Twist"): PhysRange(PhysRange
.DEGREE
, 358, 360),
563 usage_id("Wacom", "X Tilt"): PhysRange(PhysRange
.DEGREE
, 90, 180),
564 usage_id("Wacom", "Y Tilt"): PhysRange(PhysRange
.DEGREE
, 90, 180),
565 usage_id("Wacom", "Twist"): PhysRange(PhysRange
.DEGREE
, 358, 360),
566 usage_id("Wacom", "X"): PhysRange(PhysRange
.CENTIMETER
, 5, 150),
567 usage_id("Wacom", "Y"): PhysRange(PhysRange
.CENTIMETER
, 5, 150),
568 usage_id("Wacom", "Wacom TouchRing"): PhysRange(
569 PhysRange
.DEGREE
, 358, 360
571 usage_id("Wacom", "Wacom Offset Left"): PhysRange(
572 PhysRange
.CENTIMETER
, 0, 0.5
574 usage_id("Wacom", "Wacom Offset Top"): PhysRange(
575 PhysRange
.CENTIMETER
, 0, 0.5
577 usage_id("Wacom", "Wacom Offset Right"): PhysRange(
578 PhysRange
.CENTIMETER
, 0, 0.5
580 usage_id("Wacom", "Wacom Offset Bottom"): PhysRange(
581 PhysRange
.CENTIMETER
, 0, 0.5
584 for field
, usage
, application
in self
.get_usages(self
.uhdev
):
585 if application
== usage_id("Generic Desktop", "Mouse"):
586 # Ignore the vestigial Mouse collection which exists
587 # on Wacom tablets only for backwards compatibility.
590 expect_physical
= usage
in required
592 phys_set
= field
.physical_min
!= 0 or field
.physical_max
!= 0
593 assert phys_set
== expect_physical
595 unit_set
= field
.unit
!= 0
596 assert unit_set
== expect_physical
599 assert required
[usage
].contains(field
)
601 def test_prop_direct(self
):
603 Todo: Verify that INPUT_PROP_DIRECT is set on display devices.
607 def test_prop_pointer(self
):
609 Todo: Verify that INPUT_PROP_POINTER is set on opaque devices.
614 class PenTabletTest(BaseTest
.TestTablet
):
615 def assertName(self
, uhdev
):
616 super().assertName(uhdev
, " Pen")
619 class TouchTabletTest(BaseTest
.TestTablet
):
620 def assertName(self
, uhdev
):
621 super().assertName(uhdev
, " Finger")
624 class TestOpaqueTablet(PenTabletTest
):
625 def create_device(self
):
626 return OpaqueTablet()
628 def test_sanity(self
):
630 Bring a pen into contact with the tablet, then remove it.
632 Ensure that we get the basic tool/touch/motion events that should
633 be sent by the driver.
637 self
.sync_and_assert_events(
642 buttons
=Buttons
.clear(),
643 toolid
=ToolID(serial
=1, tooltype
=1),
644 proximity
=ProximityState
.IN_RANGE
,
647 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOOL_PEN
, 1),
648 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_X
, 100),
649 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_Y
, 200),
650 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOUCH
, 1),
654 self
.sync_and_assert_events(
655 uhdev
.event(110, 220, pressure
=0),
657 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_X
, 110),
658 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_Y
, 220),
659 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOUCH
, 0),
663 self
.sync_and_assert_events(
668 toolid
=ToolID
.clear(),
669 proximity
=ProximityState
.OUT
,
672 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOOL_PEN
, 0),
676 self
.sync_and_assert_events(
677 uhdev
.event(130, 240, pressure
=0), [], auto_syn
=False, strict
=True
681 class TestOpaqueCTLTablet(TestOpaqueTablet
):
682 def create_device(self
):
683 return OpaqueCTLTablet()
685 def test_buttons(self
):
687 Test that the barrel buttons (side switches) work as expected.
689 Press and release each button individually to verify that we get
694 self
.sync_and_assert_events(
699 buttons
=Buttons
.clear(),
700 toolid
=ToolID(serial
=1, tooltype
=1),
701 proximity
=ProximityState
.IN_RANGE
,
704 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOOL_PEN
, 1),
705 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_X
, 100),
706 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_Y
, 200),
707 libevdev
.InputEvent(libevdev
.EV_MSC
.MSC_SERIAL
, 1),
711 self
.sync_and_assert_events(
712 uhdev
.event(100, 200, pressure
=0, buttons
=Buttons(primary
=True)),
714 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_STYLUS
, 1),
715 libevdev
.InputEvent(libevdev
.EV_MSC
.MSC_SERIAL
, 1),
719 self
.sync_and_assert_events(
720 uhdev
.event(100, 200, pressure
=0, buttons
=Buttons(primary
=False)),
722 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_STYLUS
, 0),
723 libevdev
.InputEvent(libevdev
.EV_MSC
.MSC_SERIAL
, 1),
727 self
.sync_and_assert_events(
728 uhdev
.event(100, 200, pressure
=0, buttons
=Buttons(secondary
=True)),
730 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_STYLUS2
, 1),
731 libevdev
.InputEvent(libevdev
.EV_MSC
.MSC_SERIAL
, 1),
735 self
.sync_and_assert_events(
736 uhdev
.event(100, 200, pressure
=0, buttons
=Buttons(secondary
=False)),
738 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_STYLUS2
, 0),
739 libevdev
.InputEvent(libevdev
.EV_MSC
.MSC_SERIAL
, 1),
745 {"rdesc": wacom_pth660_v145
, "info": (0x3, 0x056A, 0x0357)},
746 {"rdesc": wacom_pth660_v150
, "info": (0x3, 0x056A, 0x0357)},
747 {"rdesc": wacom_pth860_v145
, "info": (0x3, 0x056A, 0x0358)},
748 {"rdesc": wacom_pth860_v150
, "info": (0x3, 0x056A, 0x0358)},
749 {"rdesc": wacom_pth460_v105
, "info": (0x3, 0x056A, 0x0392)},
761 class TestPTHX60_Pen(TestOpaqueCTLTablet
):
763 autouse
=True, scope
="class", params
=PTHX60_Devices
, ids
=PTHX60_Names
765 def set_device_params(self
, request
):
766 request
.cls
.device_params
= request
.param
768 def create_device(self
):
769 return PTHX60_Pen(**self
.device_params
)
772 def test_descriptor_physicals(self
):
773 # XFAIL: Various documented errata
774 super().test_descriptor_physicals()
776 def test_heartbeat_spurious(self
):
778 Test that the heartbeat report does not send spurious events.
782 self
.sync_and_assert_events(
787 buttons
=Buttons
.clear(),
788 toolid
=ToolID(serial
=1, tooltype
=0x822),
789 proximity
=ProximityState
.IN_RANGE
,
792 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOOL_PEN
, 1),
793 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_X
, 100),
794 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_Y
, 200),
795 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOUCH
, 1),
799 # Exactly zero events: not even a SYN
800 self
.sync_and_assert_events(
801 uhdev
.event_heartbeat(19), [], auto_syn
=False, strict
=True
804 self
.sync_and_assert_events(
805 uhdev
.event(110, 200, pressure
=300),
807 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_X
, 110),
811 def test_empty_pad_sync(self
):
812 self
.empty_pad_sync(num
=3, denom
=16, reverse
=True)
814 def empty_pad_sync(self
, num
, denom
, reverse
):
816 Test that multiple pad collections do not trigger empty syncs.
819 def offset_rotation(value
):
821 Offset touchring rotation values by the same factor as the
822 Linux kernel. Tablets historically don't use the same origin
823 as HID, and it sometimes changes from tablet to tablet...
825 evdev
= self
.uhdev
.get_evdev()
826 info
= evdev
.absinfo
[libevdev
.EV_ABS
.ABS_WHEEL
]
827 delta
= info
.maximum
- info
.minimum
+ 1
829 value
= info
.maximum
- value
830 value
+= num
* delta
// denom
831 if value
> info
.maximum
:
833 elif value
< info
.minimum
:
838 uhdev
.application
= "Pad"
839 evdev
= uhdev
.get_evdev()
842 self
.sync_and_assert_events(
843 uhdev
.event_pad(reportID
=17, ring
=0, ek0
=1),
845 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_0
, 1),
846 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_WHEEL
, offset_rotation(0)),
847 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_MISC
, 15),
851 self
.sync_and_assert_events(
852 uhdev
.event_pad(reportID
=17, ring
=1, ek0
=1),
853 [libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_WHEEL
, offset_rotation(1))],
856 self
.sync_and_assert_events(
857 uhdev
.event_pad(reportID
=17, ring
=2, ek0
=0),
859 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_WHEEL
, offset_rotation(2)),
860 libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_0
, 0),
865 class TestDTH2452Tablet(test_multitouch
.BaseTest
.TestMultitouch
, TouchTabletTest
):
866 ContactIds
= namedtuple("ContactIds", "contact_id, tracking_id, slot_num")
868 def create_device(self
):
869 return test_multitouch
.Digitizer(
871 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",
872 input_info
=(0x3, 0x056A, 0x0383),
875 def make_contact(self
, contact_id
=0, t
=0):
877 Make a single touch contact that can move over time.
879 Creates a touch object that has a well-known position in space that
880 does not overlap with other contacts. The value of `t` may be
881 incremented over time to move the point along a linear path.
883 x
= 50 + 10 * contact_id
+ t
* 11
884 y
= 100 + 100 * contact_id
+ t
* 11
885 return test_multitouch
.Touch(contact_id
, x
, y
)
887 def make_contacts(self
, n
, t
=0):
889 Make multiple touch contacts that can move over time.
891 Returns a list of `n` touch objects that are positioned at well-known
892 locations. The value of `t` may be incremented over time to move the
893 points along a linear path.
895 return [ self
.make_contact(id, t
) for id in range(0, n
) ]
897 def assert_contact(self
, uhdev
, evdev
, contact_ids
, t
=0):
899 Assert properties of a contact generated by make_contact.
901 contact_id
= contact_ids
.contact_id
902 tracking_id
= contact_ids
.tracking_id
903 slot_num
= contact_ids
.slot_num
905 x
= 50 + 10 * contact_id
+ t
* 11
906 y
= 100 + 100 * contact_id
+ t
* 11
908 # If the data isn't supposed to be stored in any slots, there is
909 # nothing we can check for in the evdev stream.
911 assert tracking_id
== -1
914 assert evdev
.slots
[slot_num
][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == tracking_id
915 if tracking_id
!= -1:
916 assert evdev
.slots
[slot_num
][libevdev
.EV_ABS
.ABS_MT_POSITION_X
] == x
917 assert evdev
.slots
[slot_num
][libevdev
.EV_ABS
.ABS_MT_POSITION_Y
] == y
919 def assert_contacts(self
, uhdev
, evdev
, data
, t
=0):
921 Assert properties of a list of contacts generated by make_contacts.
923 for contact_ids
in data
:
924 self
.assert_contact(uhdev
, evdev
, contact_ids
, t
)
926 def test_contact_id_0(self
):
928 Bring a finger in contact with the tablet, then hold it down and remove it.
930 Ensure that even with contact ID = 0 which is usually given as an invalid
931 touch event by most tablets with the exception of a few, that given the
932 confidence bit is set to 1 it should process it as a valid touch to cover
933 the few tablets using contact ID = 0 as a valid touch value.
936 evdev
= uhdev
.get_evdev()
938 t0
= test_multitouch
.Touch(0, 50, 100)
939 r
= uhdev
.event([t0
])
940 events
= uhdev
.next_sync_events()
941 self
.debug_reports(r
, uhdev
, events
)
943 slot
= self
.get_slot(uhdev
, t0
, 0)
945 assert libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOUCH
, 1) in events
946 assert evdev
.slots
[slot
][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == 0
947 assert evdev
.slots
[slot
][libevdev
.EV_ABS
.ABS_MT_POSITION_X
] == 50
948 assert evdev
.slots
[slot
][libevdev
.EV_ABS
.ABS_MT_POSITION_Y
] == 100
951 if uhdev
.quirks
is None or "VALID_IS_INRANGE" not in uhdev
.quirks
:
953 r
= uhdev
.event([t0
])
954 events
= uhdev
.next_sync_events()
955 self
.debug_reports(r
, uhdev
, events
)
956 assert libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOUCH
, 0) in events
957 assert evdev
.slots
[slot
][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == -1
959 def test_confidence_false(self
):
961 Bring a finger in contact with the tablet with confidence set to false.
963 Ensure that the confidence bit being set to false should not result in a touch event.
966 _evdev
= uhdev
.get_evdev()
968 t0
= test_multitouch
.Touch(1, 50, 100)
969 t0
.confidence
= False
970 r
= uhdev
.event([t0
])
971 events
= uhdev
.next_sync_events()
972 self
.debug_reports(r
, uhdev
, events
)
974 _slot
= self
.get_slot(uhdev
, t0
, 0)
978 def test_confidence_multitouch(self
):
980 Bring multiple fingers in contact with the tablet, some with the
981 confidence bit set, and some without.
983 Ensure that all confident touches are reported and that all non-
984 confident touches are ignored.
987 evdev
= uhdev
.get_evdev()
989 touches
= self
.make_contacts(5)
990 touches
[0].confidence
= False
991 touches
[2].confidence
= False
992 touches
[4].confidence
= False
994 r
= uhdev
.event(touches
)
995 events
= uhdev
.next_sync_events()
996 self
.debug_reports(r
, uhdev
, events
)
998 assert libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOUCH
, 1) in events
1000 self
.assert_contacts(uhdev
, evdev
,
1001 [ self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= None),
1002 self
.ContactIds(contact_id
= 1, tracking_id
= 0, slot_num
= 0),
1003 self
.ContactIds(contact_id
= 2, tracking_id
= -1, slot_num
= None),
1004 self
.ContactIds(contact_id
= 3, tracking_id
= 1, slot_num
= 1),
1005 self
.ContactIds(contact_id
= 4, tracking_id
= -1, slot_num
= None) ])
1007 def confidence_change_assert_playback(self
, uhdev
, evdev
, timeline
):
1009 Assert proper behavior of contacts that move and change tipswitch /
1010 confidence status over time.
1012 Given a `timeline` list of touch states to iterate over, verify
1013 that the contacts move and are reported as up/down as expected
1014 by the state of the tipswitch and confidence bits.
1018 for state
in timeline
:
1019 touches
= self
.make_contacts(len(state
), t
)
1021 for item
in zip(touches
, state
):
1022 item
[0].tipswitch
= item
[1][1]
1023 item
[0].confidence
= item
[1][2]
1025 r
= uhdev
.event(touches
)
1026 events
= uhdev
.next_sync_events()
1027 self
.debug_reports(r
, uhdev
, events
)
1029 ids
= [ x
[0] for x
in state
]
1030 self
.assert_contacts(uhdev
, evdev
, ids
, t
)
1034 def test_confidence_loss_a(self
):
1036 Transition a confident contact to a non-confident contact by
1037 first clearing the tipswitch.
1039 Ensure that the driver reports the transitioned contact as
1040 being removed and that other contacts continue to report
1041 normally. This mode of confidence loss is used by the
1045 evdev
= uhdev
.get_evdev()
1047 self
.confidence_change_assert_playback(uhdev
, evdev
, [
1048 # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
1049 # Both fingers confidently in contact
1050 [(self
.ContactIds(contact_id
= 0, tracking_id
= 0, slot_num
= 0), True, True),
1051 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)],
1053 # t=1: Contact 0 == !Down + confident; Contact 1 == Down + confident
1054 # First finger looses confidence and clears only the tipswitch flag
1055 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= 0), False, True),
1056 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)],
1058 # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1059 # First finger has lost confidence and has both flags cleared
1060 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= 0), False, False),
1061 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)],
1063 # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1064 # First finger has lost confidence and has both flags cleared
1065 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= 0), False, False),
1066 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)]
1069 def test_confidence_loss_b(self
):
1071 Transition a confident contact to a non-confident contact by
1072 cleraing both tipswitch and confidence bits simultaneously.
1074 Ensure that the driver reports the transitioned contact as
1075 being removed and that other contacts continue to report
1076 normally. This mode of confidence loss is used by some
1080 evdev
= uhdev
.get_evdev()
1082 self
.confidence_change_assert_playback(uhdev
, evdev
, [
1083 # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
1084 # Both fingers confidently in contact
1085 [(self
.ContactIds(contact_id
= 0, tracking_id
= 0, slot_num
= 0), True, True),
1086 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)],
1088 # t=1: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1089 # First finger looses confidence and has both flags cleared simultaneously
1090 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= 0), False, False),
1091 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)],
1093 # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1094 # First finger has lost confidence and has both flags cleared
1095 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= 0), False, False),
1096 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)],
1098 # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1099 # First finger has lost confidence and has both flags cleared
1100 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= 0), False, False),
1101 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)]
1104 def test_confidence_loss_c(self
):
1106 Transition a confident contact to a non-confident contact by
1107 clearing only the confidence bit.
1109 Ensure that the driver reports the transitioned contact as
1110 being removed and that other contacts continue to report
1114 evdev
= uhdev
.get_evdev()
1116 self
.confidence_change_assert_playback(uhdev
, evdev
, [
1117 # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
1118 # Both fingers confidently in contact
1119 [(self
.ContactIds(contact_id
= 0, tracking_id
= 0, slot_num
= 0), True, True),
1120 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)],
1122 # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
1123 # First finger looses confidence and clears only the confidence flag
1124 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= 0), True, False),
1125 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)],
1127 # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1128 # First finger has lost confidence and has both flags cleared
1129 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= 0), False, False),
1130 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)],
1132 # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1133 # First finger has lost confidence and has both flags cleared
1134 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= 0), False, False),
1135 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)]
1138 def test_confidence_gain_a(self
):
1140 Transition a contact that was always non-confident to confident.
1142 Ensure that the confident contact is reported normally.
1145 evdev
= uhdev
.get_evdev()
1147 self
.confidence_change_assert_playback(uhdev
, evdev
, [
1148 # t=0: Contact 0 == Down + !confident; Contact 1 == Down + confident
1149 # Only second finger is confidently in contact
1150 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= None), True, False),
1151 (self
.ContactIds(contact_id
= 1, tracking_id
= 0, slot_num
= 0), True, True)],
1153 # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
1154 # First finger gains confidence
1155 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= None), True, False),
1156 (self
.ContactIds(contact_id
= 1, tracking_id
= 0, slot_num
= 0), True, True)],
1158 # t=2: Contact 0 == Down + confident; Contact 1 == Down + confident
1159 # First finger remains confident
1160 [(self
.ContactIds(contact_id
= 0, tracking_id
= 1, slot_num
= 1), True, True),
1161 (self
.ContactIds(contact_id
= 1, tracking_id
= 0, slot_num
= 0), True, True)],
1163 # t=3: Contact 0 == Down + confident; Contact 1 == Down + confident
1164 # First finger remains confident
1165 [(self
.ContactIds(contact_id
= 0, tracking_id
= 1, slot_num
= 1), True, True),
1166 (self
.ContactIds(contact_id
= 1, tracking_id
= 0, slot_num
= 0), True, True)]
1169 def test_confidence_gain_b(self
):
1171 Transition a contact from non-confident to confident.
1173 Ensure that the confident contact is reported normally.
1176 evdev
= uhdev
.get_evdev()
1178 self
.confidence_change_assert_playback(uhdev
, evdev
, [
1179 # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
1180 # First and second finger confidently in contact
1181 [(self
.ContactIds(contact_id
= 0, tracking_id
= 0, slot_num
= 0), True, True),
1182 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)],
1184 # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
1185 # Firtst finger looses confidence
1186 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= 0), True, False),
1187 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)],
1189 # t=2: Contact 0 == Down + confident; Contact 1 == Down + confident
1190 # First finger gains confidence
1191 [(self
.ContactIds(contact_id
= 0, tracking_id
= 2, slot_num
= 0), True, True),
1192 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)],
1194 # t=3: Contact 0 == !Down + confident; Contact 1 == Down + confident
1195 # First finger goes up
1196 [(self
.ContactIds(contact_id
= 0, tracking_id
= -1, slot_num
= 0), False, True),
1197 (self
.ContactIds(contact_id
= 1, tracking_id
= 1, slot_num
= 1), True, True)]