Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/hid/tests/test_wacom_generic.py
26308 views
1
#!/bin/env python3
2
# SPDX-License-Identifier: GPL-2.0
3
# -*- coding: utf-8 -*-
4
#
5
# Copyright (c) 2017 Benjamin Tissoires <[email protected]>
6
# Copyright (c) 2017 Red Hat, Inc.
7
# Copyright (c) 2020 Wacom Technology Corp.
8
#
9
# Authors:
10
# Jason Gerecke <[email protected]>
11
12
"""
13
Tests for the Wacom driver generic codepath.
14
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.
19
"""
20
21
from .descriptors_wacom import (
22
wacom_pth660_v145,
23
wacom_pth660_v150,
24
wacom_pth860_v145,
25
wacom_pth860_v150,
26
wacom_pth460_v105,
27
)
28
29
import attr
30
from collections import namedtuple
31
from enum import Enum
32
from hidtools.hut import HUT
33
from hidtools.hid import HidUnit
34
from . import base
35
from . import test_multitouch
36
import libevdev
37
import pytest
38
39
import logging
40
41
logger = logging.getLogger("hidtools.test.wacom")
42
43
KERNEL_MODULE = base.KernelModule("wacom", "wacom")
44
45
46
class ProximityState(Enum):
47
"""
48
Enumeration of allowed proximity states.
49
"""
50
51
# Tool is not able to be sensed by the device
52
OUT = 0
53
54
# Tool is close enough to be sensed, but some data may be invalid
55
# or inaccurate
56
IN_PROXIMITY = 1
57
58
# Tool is close enough to be sensed with high accuracy. All data
59
# valid.
60
IN_RANGE = 2
61
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,
68
]
69
70
71
class ReportData:
72
"""
73
Placeholder for HID report values.
74
"""
75
76
pass
77
78
79
@attr.s
80
class Buttons:
81
"""
82
Stylus button state.
83
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.
87
"""
88
89
primary = attr.ib(default=None)
90
secondary = attr.ib(default=None)
91
tertiary = attr.ib(default=None)
92
93
@staticmethod
94
def clear():
95
"""Button object with all states cleared."""
96
return Buttons(False, False, False)
97
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)
103
104
105
@attr.s
106
class ToolID:
107
"""
108
Stylus tool identifiers.
109
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.
113
"""
114
115
serial = attr.ib()
116
tooltype = attr.ib()
117
118
@staticmethod
119
def clear():
120
"""ToolID object with all fields cleared."""
121
return ToolID(0, 0)
122
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
128
129
130
@attr.s
131
class PhysRange:
132
"""
133
Range of HID physical values, with units.
134
"""
135
136
unit = attr.ib()
137
min_size = attr.ib()
138
max_size = attr.ib()
139
140
CENTIMETER = HidUnit.from_string("SILinear: cm")
141
DEGREE = HidUnit.from_string("EnglishRotation: deg")
142
143
def contains(self, field):
144
"""
145
Check if the physical size of the provided field is in range.
146
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).
152
"""
153
phys_size = (field.physical_max - field.physical_min) * 10 ** (field.unit_exp)
154
return (
155
field.unit == self.unit.value
156
and phys_size > self.min_size
157
and phys_size < self.max_size
158
)
159
160
161
class BaseTablet(base.UHIDTestDevice):
162
"""
163
Skeleton object for all kinds of tablet devices.
164
"""
165
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
172
self.offset = 0
173
self.ring = -1
174
self.ek0 = False
175
176
def match_evdev_rule(self, application, evdev):
177
"""
178
Filter out evdev nodes based on the requested application.
179
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.
185
"""
186
if application in ["Pen", "Pad"]:
187
return evdev.name.endswith(application)
188
else:
189
return True
190
191
def create_report(
192
self, x, y, pressure, buttons=None, toolid=None, proximity=None, reportID=None
193
):
194
"""
195
Return an input report for this device.
196
197
:param x: absolute x
198
:param y: absolute y
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``
204
for unchanged.
205
:param reportID: the numeric report ID for this report, if needed
206
"""
207
if buttons is not None:
208
self.buttons = buttons
209
buttons = self.buttons
210
211
if toolid is not None:
212
self.toolid = toolid
213
toolid = self.toolid
214
215
if proximity is not None:
216
self.proximity = proximity
217
proximity = self.proximity
218
219
reportID = reportID or self.default_reportID
220
221
report = ReportData()
222
report.x = x
223
report.y = y
224
report.tippressure = pressure
225
report.tipswitch = pressure > 0
226
buttons.fill(report)
227
proximity.fill(report)
228
toolid.fill(report)
229
230
return super().create_report(report, reportID=reportID)
231
232
def create_report_heartbeat(self, reportID):
233
"""
234
Return a heartbeat input report for this device.
235
236
Heartbeat reports generally contain battery status information,
237
among other things.
238
"""
239
report = ReportData()
240
report.wacombatterycharging = 1
241
return super().create_report(report, reportID=reportID)
242
243
def create_report_pad(self, reportID, ring, ek0):
244
report = ReportData()
245
246
if ring is not None:
247
self.ring = ring
248
ring = self.ring
249
250
if ek0 is not None:
251
self.ek0 = ek0
252
ek0 = self.ek0
253
254
if ring >= 0:
255
report.wacomtouchring = ring
256
report.wacomtouchringstatus = 1
257
else:
258
report.wacomtouchring = 0x7F
259
report.wacomtouchringstatus = 0
260
261
report.wacomexpresskey00 = ek0
262
return super().create_report(report, reportID=reportID)
263
264
def event(self, x, y, pressure, buttons=None, toolid=None, proximity=None):
265
"""
266
Send an input event on the default report ID.
267
268
:param x: absolute x
269
:param y: absolute y
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``
274
for unchanged.
275
"""
276
r = self.create_report(x, y, pressure, buttons, toolid, proximity)
277
self.call_input_event(r)
278
return [r]
279
280
def event_heartbeat(self, reportID):
281
"""
282
Send a heartbeat event on the requested report ID.
283
"""
284
r = self.create_report_heartbeat(reportID)
285
self.call_input_event(r)
286
return [r]
287
288
def event_pad(self, reportID, ring=None, ek0=None):
289
"""
290
Send a pad event on the requested report ID.
291
"""
292
r = self.create_report_pad(reportID, ring, ek0)
293
self.call_input_event(r)
294
return [r]
295
296
def get_report(self, req, rnum, rtype):
297
if rtype != self.UHID_FEATURE_REPORT:
298
return (1, [])
299
300
rdesc = None
301
for v in self.parsed_rdesc.feature_reports.values():
302
if v.report_ID == rnum:
303
rdesc = v
304
305
if rdesc is None:
306
return (1, [])
307
308
result = (1, [])
309
result = self.create_report_offset(rdesc) or result
310
return result
311
312
def create_report_offset(self, rdesc):
313
require = [
314
"Wacom Offset Left",
315
"Wacom Offset Top",
316
"Wacom Offset Right",
317
"Wacom Offset Bottom",
318
]
319
if not set(require).issubset(set([f.usage_name for f in rdesc])):
320
return None
321
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)
328
return (0, r)
329
330
331
class OpaqueTablet(BaseTablet):
332
"""
333
Bare-bones opaque tablet with a minimum of features.
334
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.
341
"""
342
343
# fmt: off
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,
375
]
376
# fmt: on
377
378
def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)):
379
super().__init__(rdesc, name, info)
380
self.default_reportID = 1
381
382
383
class OpaqueCTLTablet(BaseTablet):
384
"""
385
Opaque tablet similar to something in the CTL product line.
386
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.
390
"""
391
392
# fmt: off
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
459
]
460
# fmt: on
461
462
def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)):
463
super().__init__(rdesc, name, info)
464
self.default_reportID = 16
465
466
467
class PTHX60_Pen(BaseTablet):
468
"""
469
Pen interface of a PTH-660 / PTH-860 / PTH-460 tablet.
470
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)
474
"""
475
476
def __init__(self, rdesc=None, name=None, info=None):
477
super().__init__(rdesc, name, info)
478
self.default_reportID = 16
479
480
481
class BaseTest:
482
class TestTablet(base.BaseTestCase.TestUhid):
483
kernel_modules = [KERNEL_MODULE]
484
485
def sync_and_assert_events(
486
self, report, expected_events, auto_syn=True, strict=False
487
):
488
"""
489
Assert we see the expected events in response to a report.
490
"""
491
uhdev = self.uhdev
492
syn_event = self.syn_event
493
if auto_syn:
494
expected_events.append(syn_event)
495
actual_events = uhdev.next_sync_events()
496
self.debug_reports(report, uhdev, actual_events)
497
if strict:
498
self.assertInputEvents(expected_events, actual_events)
499
else:
500
self.assertInputEventsIn(expected_events, actual_events)
501
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)
509
else:
510
yield (field, field.usage, application)
511
512
desc = uhdev.parsed_rdesc
513
reports = [
514
*desc.input_reports.values(),
515
*desc.feature_reports.values(),
516
*desc.output_reports.values(),
517
]
518
for report in reports:
519
for usage in get_report_usages(report):
520
yield usage
521
522
def assertName(self, uhdev, type):
523
"""
524
Assert that the name is as we expect.
525
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.
529
"""
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
535
536
def test_descriptor_physicals(self):
537
"""
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.
541
"""
542
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
546
547
required = {
548
usage_id("Generic Desktop", "X"): PhysRange(
549
PhysRange.CENTIMETER, 5, 150
550
),
551
usage_id("Generic Desktop", "Y"): PhysRange(
552
PhysRange.CENTIMETER, 5, 150
553
),
554
usage_id("Digitizers", "Width"): PhysRange(
555
PhysRange.CENTIMETER, 5, 150
556
),
557
usage_id("Digitizers", "Height"): PhysRange(
558
PhysRange.CENTIMETER, 5, 150
559
),
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
570
),
571
usage_id("Wacom", "Wacom Offset Left"): PhysRange(
572
PhysRange.CENTIMETER, 0, 0.5
573
),
574
usage_id("Wacom", "Wacom Offset Top"): PhysRange(
575
PhysRange.CENTIMETER, 0, 0.5
576
),
577
usage_id("Wacom", "Wacom Offset Right"): PhysRange(
578
PhysRange.CENTIMETER, 0, 0.5
579
),
580
usage_id("Wacom", "Wacom Offset Bottom"): PhysRange(
581
PhysRange.CENTIMETER, 0, 0.5
582
),
583
}
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.
588
continue
589
590
expect_physical = usage in required
591
592
phys_set = field.physical_min != 0 or field.physical_max != 0
593
assert phys_set == expect_physical
594
595
unit_set = field.unit != 0
596
assert unit_set == expect_physical
597
598
if unit_set:
599
assert required[usage].contains(field)
600
601
def test_prop_direct(self):
602
"""
603
Todo: Verify that INPUT_PROP_DIRECT is set on display devices.
604
"""
605
pass
606
607
def test_prop_pointer(self):
608
"""
609
Todo: Verify that INPUT_PROP_POINTER is set on opaque devices.
610
"""
611
pass
612
613
614
class PenTabletTest(BaseTest.TestTablet):
615
def assertName(self, uhdev):
616
super().assertName(uhdev, " Pen")
617
618
619
class TouchTabletTest(BaseTest.TestTablet):
620
def assertName(self, uhdev):
621
super().assertName(uhdev, " Finger")
622
623
624
class TestOpaqueTablet(PenTabletTest):
625
def create_device(self):
626
return OpaqueTablet()
627
628
def test_sanity(self):
629
"""
630
Bring a pen into contact with the tablet, then remove it.
631
632
Ensure that we get the basic tool/touch/motion events that should
633
be sent by the driver.
634
"""
635
uhdev = self.uhdev
636
637
self.sync_and_assert_events(
638
uhdev.event(
639
100,
640
200,
641
pressure=300,
642
buttons=Buttons.clear(),
643
toolid=ToolID(serial=1, tooltype=1),
644
proximity=ProximityState.IN_RANGE,
645
),
646
[
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),
651
],
652
)
653
654
self.sync_and_assert_events(
655
uhdev.event(110, 220, pressure=0),
656
[
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),
660
],
661
)
662
663
self.sync_and_assert_events(
664
uhdev.event(
665
120,
666
230,
667
pressure=0,
668
toolid=ToolID.clear(),
669
proximity=ProximityState.OUT,
670
),
671
[
672
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0),
673
],
674
)
675
676
self.sync_and_assert_events(
677
uhdev.event(130, 240, pressure=0), [], auto_syn=False, strict=True
678
)
679
680
681
class TestOpaqueCTLTablet(TestOpaqueTablet):
682
def create_device(self):
683
return OpaqueCTLTablet()
684
685
def test_buttons(self):
686
"""
687
Test that the barrel buttons (side switches) work as expected.
688
689
Press and release each button individually to verify that we get
690
the expected events.
691
"""
692
uhdev = self.uhdev
693
694
self.sync_and_assert_events(
695
uhdev.event(
696
100,
697
200,
698
pressure=0,
699
buttons=Buttons.clear(),
700
toolid=ToolID(serial=1, tooltype=1),
701
proximity=ProximityState.IN_RANGE,
702
),
703
[
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),
708
],
709
)
710
711
self.sync_and_assert_events(
712
uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=True)),
713
[
714
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1),
715
libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
716
],
717
)
718
719
self.sync_and_assert_events(
720
uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=False)),
721
[
722
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0),
723
libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
724
],
725
)
726
727
self.sync_and_assert_events(
728
uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=True)),
729
[
730
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 1),
731
libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
732
],
733
)
734
735
self.sync_and_assert_events(
736
uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=False)),
737
[
738
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 0),
739
libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
740
],
741
)
742
743
744
PTHX60_Devices = [
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)},
750
]
751
752
PTHX60_Names = [
753
"PTH-660/v145",
754
"PTH-660/v150",
755
"PTH-860/v145",
756
"PTH-860/v150",
757
"PTH-460/v105",
758
]
759
760
761
class TestPTHX60_Pen(TestOpaqueCTLTablet):
762
@pytest.fixture(
763
autouse=True, scope="class", params=PTHX60_Devices, ids=PTHX60_Names
764
)
765
def set_device_params(self, request):
766
request.cls.device_params = request.param
767
768
def create_device(self):
769
return PTHX60_Pen(**self.device_params)
770
771
@pytest.mark.xfail
772
def test_descriptor_physicals(self):
773
# XFAIL: Various documented errata
774
super().test_descriptor_physicals()
775
776
def test_heartbeat_spurious(self):
777
"""
778
Test that the heartbeat report does not send spurious events.
779
"""
780
uhdev = self.uhdev
781
782
self.sync_and_assert_events(
783
uhdev.event(
784
100,
785
200,
786
pressure=300,
787
buttons=Buttons.clear(),
788
toolid=ToolID(serial=1, tooltype=0x822),
789
proximity=ProximityState.IN_RANGE,
790
),
791
[
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),
796
],
797
)
798
799
# Exactly zero events: not even a SYN
800
self.sync_and_assert_events(
801
uhdev.event_heartbeat(19), [], auto_syn=False, strict=True
802
)
803
804
self.sync_and_assert_events(
805
uhdev.event(110, 200, pressure=300),
806
[
807
libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110),
808
],
809
)
810
811
def test_empty_pad_sync(self):
812
self.empty_pad_sync(num=3, denom=16, reverse=True)
813
814
def empty_pad_sync(self, num, denom, reverse):
815
"""
816
Test that multiple pad collections do not trigger empty syncs.
817
"""
818
819
def offset_rotation(value):
820
"""
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...
824
"""
825
evdev = self.uhdev.get_evdev()
826
info = evdev.absinfo[libevdev.EV_ABS.ABS_WHEEL]
827
delta = info.maximum - info.minimum + 1
828
if reverse:
829
value = info.maximum - value
830
value += num * delta // denom
831
if value > info.maximum:
832
value -= delta
833
elif value < info.minimum:
834
value += delta
835
return value
836
837
uhdev = self.uhdev
838
uhdev.application = "Pad"
839
evdev = uhdev.get_evdev()
840
841
print(evdev.name)
842
self.sync_and_assert_events(
843
uhdev.event_pad(reportID=17, ring=0, ek0=1),
844
[
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),
848
],
849
)
850
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))],
854
)
855
856
self.sync_and_assert_events(
857
uhdev.event_pad(reportID=17, ring=2, ek0=0),
858
[
859
libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(2)),
860
libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 0),
861
],
862
)
863
864
865
class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest):
866
ContactIds = namedtuple("ContactIds", "contact_id, tracking_id, slot_num")
867
868
def create_device(self):
869
return test_multitouch.Digitizer(
870
"DTH 2452",
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),
873
)
874
875
def make_contact(self, contact_id=0, t=0):
876
"""
877
Make a single touch contact that can move over time.
878
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.
882
"""
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)
886
887
def make_contacts(self, n, t=0):
888
"""
889
Make multiple touch contacts that can move over time.
890
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.
894
"""
895
return [self.make_contact(id, t) for id in range(0, n)]
896
897
def assert_contact(self, evdev, contact_ids, t=0):
898
"""
899
Assert properties of a contact generated by make_contact.
900
"""
901
contact_id = contact_ids.contact_id
902
tracking_id = contact_ids.tracking_id
903
slot_num = contact_ids.slot_num
904
905
x = 50 + 10 * contact_id + t * 11
906
y = 100 + 100 * contact_id + t * 11
907
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.
910
if slot_num is None:
911
assert tracking_id == -1
912
return
913
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
918
919
def assert_contacts(self, evdev, data, t=0):
920
"""
921
Assert properties of a list of contacts generated by make_contacts.
922
"""
923
for contact_ids in data:
924
self.assert_contact(evdev, contact_ids, t)
925
926
def test_contact_id_0(self):
927
"""
928
Bring a finger in contact with the tablet, then hold it down and remove it.
929
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.
934
"""
935
uhdev = self.uhdev
936
evdev = uhdev.get_evdev()
937
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)
942
943
slot = self.get_slot(uhdev, t0, 0)
944
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
949
950
t0.tipswitch = False
951
if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
952
t0.inrange = False
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
958
959
def test_confidence_false(self):
960
"""
961
Bring a finger in contact with the tablet with confidence set to false.
962
963
Ensure that the confidence bit being set to false should not result in a touch event.
964
"""
965
uhdev = self.uhdev
966
_evdev = uhdev.get_evdev()
967
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)
973
974
_slot = self.get_slot(uhdev, t0, 0)
975
976
assert not events
977
978
def test_confidence_multitouch(self):
979
"""
980
Bring multiple fingers in contact with the tablet, some with the
981
confidence bit set, and some without.
982
983
Ensure that all confident touches are reported and that all non-
984
confident touches are ignored.
985
"""
986
uhdev = self.uhdev
987
evdev = uhdev.get_evdev()
988
989
touches = self.make_contacts(5)
990
touches[0].confidence = False
991
touches[2].confidence = False
992
touches[4].confidence = False
993
994
r = uhdev.event(touches)
995
events = uhdev.next_sync_events()
996
self.debug_reports(r, uhdev, events)
997
998
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
999
1000
self.assert_contacts(
1001
evdev,
1002
[
1003
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=None),
1004
self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),
1005
self.ContactIds(contact_id=2, tracking_id=-1, slot_num=None),
1006
self.ContactIds(contact_id=3, tracking_id=1, slot_num=1),
1007
self.ContactIds(contact_id=4, tracking_id=-1, slot_num=None),
1008
],
1009
)
1010
1011
def confidence_change_assert_playback(self, uhdev, evdev, timeline):
1012
"""
1013
Assert proper behavior of contacts that move and change tipswitch /
1014
confidence status over time.
1015
1016
Given a `timeline` list of touch states to iterate over, verify
1017
that the contacts move and are reported as up/down as expected
1018
by the state of the tipswitch and confidence bits.
1019
"""
1020
t = 0
1021
1022
for state in timeline:
1023
touches = self.make_contacts(len(state), t)
1024
1025
for item in zip(touches, state):
1026
item[0].tipswitch = item[1][1]
1027
item[0].confidence = item[1][2]
1028
1029
r = uhdev.event(touches)
1030
events = uhdev.next_sync_events()
1031
self.debug_reports(r, uhdev, events)
1032
1033
ids = [x[0] for x in state]
1034
self.assert_contacts(evdev, ids, t)
1035
1036
t += 1
1037
1038
def test_confidence_loss_a(self):
1039
"""
1040
Transition a confident contact to a non-confident contact by
1041
first clearing the tipswitch.
1042
1043
Ensure that the driver reports the transitioned contact as
1044
being removed and that other contacts continue to report
1045
normally. This mode of confidence loss is used by the
1046
DTH-2452.
1047
"""
1048
uhdev = self.uhdev
1049
evdev = uhdev.get_evdev()
1050
1051
self.confidence_change_assert_playback(
1052
uhdev,
1053
evdev,
1054
[
1055
# t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
1056
# Both fingers confidently in contact
1057
[
1058
(
1059
self.ContactIds(contact_id=0, tracking_id=0, slot_num=0),
1060
True,
1061
True,
1062
),
1063
(
1064
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1065
True,
1066
True,
1067
),
1068
],
1069
# t=1: Contact 0 == !Down + confident; Contact 1 == Down + confident
1070
# First finger looses confidence and clears only the tipswitch flag
1071
[
1072
(
1073
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
1074
False,
1075
True,
1076
),
1077
(
1078
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1079
True,
1080
True,
1081
),
1082
],
1083
# t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1084
# First finger has lost confidence and has both flags cleared
1085
[
1086
(
1087
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
1088
False,
1089
False,
1090
),
1091
(
1092
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1093
True,
1094
True,
1095
),
1096
],
1097
# t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1098
# First finger has lost confidence and has both flags cleared
1099
[
1100
(
1101
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
1102
False,
1103
False,
1104
),
1105
(
1106
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1107
True,
1108
True,
1109
),
1110
],
1111
],
1112
)
1113
1114
def test_confidence_loss_b(self):
1115
"""
1116
Transition a confident contact to a non-confident contact by
1117
cleraing both tipswitch and confidence bits simultaneously.
1118
1119
Ensure that the driver reports the transitioned contact as
1120
being removed and that other contacts continue to report
1121
normally. This mode of confidence loss is used by some
1122
AES devices.
1123
"""
1124
uhdev = self.uhdev
1125
evdev = uhdev.get_evdev()
1126
1127
self.confidence_change_assert_playback(
1128
uhdev,
1129
evdev,
1130
[
1131
# t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
1132
# Both fingers confidently in contact
1133
[
1134
(
1135
self.ContactIds(contact_id=0, tracking_id=0, slot_num=0),
1136
True,
1137
True,
1138
),
1139
(
1140
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1141
True,
1142
True,
1143
),
1144
],
1145
# t=1: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1146
# First finger looses confidence and has both flags cleared simultaneously
1147
[
1148
(
1149
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
1150
False,
1151
False,
1152
),
1153
(
1154
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1155
True,
1156
True,
1157
),
1158
],
1159
# t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1160
# First finger has lost confidence and has both flags cleared
1161
[
1162
(
1163
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
1164
False,
1165
False,
1166
),
1167
(
1168
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1169
True,
1170
True,
1171
),
1172
],
1173
# t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1174
# First finger has lost confidence and has both flags cleared
1175
[
1176
(
1177
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
1178
False,
1179
False,
1180
),
1181
(
1182
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1183
True,
1184
True,
1185
),
1186
],
1187
],
1188
)
1189
1190
def test_confidence_loss_c(self):
1191
"""
1192
Transition a confident contact to a non-confident contact by
1193
clearing only the confidence bit.
1194
1195
Ensure that the driver reports the transitioned contact as
1196
being removed and that other contacts continue to report
1197
normally.
1198
"""
1199
uhdev = self.uhdev
1200
evdev = uhdev.get_evdev()
1201
1202
self.confidence_change_assert_playback(
1203
uhdev,
1204
evdev,
1205
[
1206
# t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
1207
# Both fingers confidently in contact
1208
[
1209
(
1210
self.ContactIds(contact_id=0, tracking_id=0, slot_num=0),
1211
True,
1212
True,
1213
),
1214
(
1215
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1216
True,
1217
True,
1218
),
1219
],
1220
# t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
1221
# First finger looses confidence and clears only the confidence flag
1222
[
1223
(
1224
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
1225
True,
1226
False,
1227
),
1228
(
1229
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1230
True,
1231
True,
1232
),
1233
],
1234
# t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1235
# First finger has lost confidence and has both flags cleared
1236
[
1237
(
1238
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
1239
False,
1240
False,
1241
),
1242
(
1243
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1244
True,
1245
True,
1246
),
1247
],
1248
# t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
1249
# First finger has lost confidence and has both flags cleared
1250
[
1251
(
1252
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
1253
False,
1254
False,
1255
),
1256
(
1257
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1258
True,
1259
True,
1260
),
1261
],
1262
],
1263
)
1264
1265
def test_confidence_gain_a(self):
1266
"""
1267
Transition a contact that was always non-confident to confident.
1268
1269
Ensure that the confident contact is reported normally.
1270
"""
1271
uhdev = self.uhdev
1272
evdev = uhdev.get_evdev()
1273
1274
self.confidence_change_assert_playback(
1275
uhdev,
1276
evdev,
1277
[
1278
# t=0: Contact 0 == Down + !confident; Contact 1 == Down + confident
1279
# Only second finger is confidently in contact
1280
[
1281
(
1282
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=None),
1283
True,
1284
False,
1285
),
1286
(
1287
self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),
1288
True,
1289
True,
1290
),
1291
],
1292
# t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
1293
# First finger gains confidence
1294
[
1295
(
1296
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=None),
1297
True,
1298
False,
1299
),
1300
(
1301
self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),
1302
True,
1303
True,
1304
),
1305
],
1306
# t=2: Contact 0 == Down + confident; Contact 1 == Down + confident
1307
# First finger remains confident
1308
[
1309
(
1310
self.ContactIds(contact_id=0, tracking_id=1, slot_num=1),
1311
True,
1312
True,
1313
),
1314
(
1315
self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),
1316
True,
1317
True,
1318
),
1319
],
1320
# t=3: Contact 0 == Down + confident; Contact 1 == Down + confident
1321
# First finger remains confident
1322
[
1323
(
1324
self.ContactIds(contact_id=0, tracking_id=1, slot_num=1),
1325
True,
1326
True,
1327
),
1328
(
1329
self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),
1330
True,
1331
True,
1332
),
1333
],
1334
],
1335
)
1336
1337
def test_confidence_gain_b(self):
1338
"""
1339
Transition a contact from non-confident to confident.
1340
1341
Ensure that the confident contact is reported normally.
1342
"""
1343
uhdev = self.uhdev
1344
evdev = uhdev.get_evdev()
1345
1346
self.confidence_change_assert_playback(
1347
uhdev,
1348
evdev,
1349
[
1350
# t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
1351
# First and second finger confidently in contact
1352
[
1353
(
1354
self.ContactIds(contact_id=0, tracking_id=0, slot_num=0),
1355
True,
1356
True,
1357
),
1358
(
1359
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1360
True,
1361
True,
1362
),
1363
],
1364
# t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
1365
# Firtst finger looses confidence
1366
[
1367
(
1368
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
1369
True,
1370
False,
1371
),
1372
(
1373
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1374
True,
1375
True,
1376
),
1377
],
1378
# t=2: Contact 0 == Down + confident; Contact 1 == Down + confident
1379
# First finger gains confidence
1380
[
1381
(
1382
self.ContactIds(contact_id=0, tracking_id=2, slot_num=0),
1383
True,
1384
True,
1385
),
1386
(
1387
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1388
True,
1389
True,
1390
),
1391
],
1392
# t=3: Contact 0 == !Down + confident; Contact 1 == Down + confident
1393
# First finger goes up
1394
[
1395
(
1396
self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
1397
False,
1398
True,
1399
),
1400
(
1401
self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
1402
True,
1403
True,
1404
),
1405
],
1406
],
1407
)
1408
1409