1#!/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3# -*- coding: utf-8 -*- 4# 5# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com> 6# Copyright (c) 2017 Red Hat, Inc. 7# Copyright (c) 2020 Wacom Technology Corp. 8# 9# Authors: 10# Jason Gerecke <jason.gerecke@wacom.com> 11 12""" 13Tests for the Wacom driver generic codepath. 14 15This module tests the function of the Wacom driver's generic codepath. 16The generic codepath is used by devices which are not explicitly listed 17in the driver's device table. It uses the device's HID descriptor to 18decode reports sent by the device. 19""" 20 21from .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 29import attr 30from collections import namedtuple 31from enum import Enum 32from hidtools.hut import HUT 33from hidtools.hid import HidUnit 34from . import base 35from . import test_multitouch 36import libevdev 37import pytest 38 39import logging 40 41logger = logging.getLogger("hidtools.test.wacom") 42 43KERNEL_MODULE = base.KernelModule("wacom", "wacom") 44 45 46class 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 71class ReportData: 72 """ 73 Placeholder for HID report values. 74 """ 75 76 pass 77 78 79@attr.s 80class 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 106class 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 131class 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 161class 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 331class 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 383class 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 467class 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 481class 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 602class PenTabletTest(BaseTest.TestTablet): 603 def assertName(self, uhdev): 604 super().assertName(uhdev, " Pen") 605 606 607class TouchTabletTest(BaseTest.TestTablet): 608 def assertName(self, uhdev): 609 super().assertName(uhdev, " Finger") 610 611 612class TestOpaqueTablet(PenTabletTest): 613 def create_device(self): 614 return OpaqueTablet() 615 616 def test_sanity(self): 617 """ 618 Bring a pen into contact with the tablet, then remove it. 619 620 Ensure that we get the basic tool/touch/motion events that should 621 be sent by the driver. 622 """ 623 uhdev = self.uhdev 624 625 self.sync_and_assert_events( 626 uhdev.event( 627 100, 628 200, 629 pressure=300, 630 buttons=Buttons.clear(), 631 toolid=ToolID(serial=1, tooltype=1), 632 proximity=ProximityState.IN_RANGE, 633 ), 634 [ 635 libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1), 636 libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100), 637 libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200), 638 libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1), 639 ], 640 ) 641 642 self.sync_and_assert_events( 643 uhdev.event(110, 220, pressure=0), 644 [ 645 libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110), 646 libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 220), 647 libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0), 648 ], 649 ) 650 651 self.sync_and_assert_events( 652 uhdev.event( 653 120, 654 230, 655 pressure=0, 656 toolid=ToolID.clear(), 657 proximity=ProximityState.OUT, 658 ), 659 [ 660 libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0), 661 ], 662 ) 663 664 self.sync_and_assert_events( 665 uhdev.event(130, 240, pressure=0), [], auto_syn=False, strict=True 666 ) 667 668 def test_prop_pointer(self): 669 """ 670 Verify that INPUT_PROP_POINTER is set and INPUT_PROP_DIRECT 671 is not set on opaque devices. 672 """ 673 evdev = self.uhdev.get_evdev() 674 assert libevdev.INPUT_PROP_POINTER in evdev.properties 675 assert libevdev.INPUT_PROP_DIRECT not in evdev.properties 676 677 678class TestOpaqueCTLTablet(TestOpaqueTablet): 679 def create_device(self): 680 return OpaqueCTLTablet() 681 682 def test_buttons(self): 683 """ 684 Test that the barrel buttons (side switches) work as expected. 685 686 Press and release each button individually to verify that we get 687 the expected events. 688 """ 689 uhdev = self.uhdev 690 691 self.sync_and_assert_events( 692 uhdev.event( 693 100, 694 200, 695 pressure=0, 696 buttons=Buttons.clear(), 697 toolid=ToolID(serial=1, tooltype=1), 698 proximity=ProximityState.IN_RANGE, 699 ), 700 [ 701 libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1), 702 libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100), 703 libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200), 704 libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), 705 ], 706 ) 707 708 self.sync_and_assert_events( 709 uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=True)), 710 [ 711 libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1), 712 libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), 713 ], 714 ) 715 716 self.sync_and_assert_events( 717 uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=False)), 718 [ 719 libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0), 720 libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), 721 ], 722 ) 723 724 self.sync_and_assert_events( 725 uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=True)), 726 [ 727 libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 1), 728 libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), 729 ], 730 ) 731 732 self.sync_and_assert_events( 733 uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=False)), 734 [ 735 libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 0), 736 libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), 737 ], 738 ) 739 740 741PTHX60_Devices = [ 742 {"rdesc": wacom_pth660_v145, "info": (0x3, 0x056A, 0x0357)}, 743 {"rdesc": wacom_pth660_v150, "info": (0x3, 0x056A, 0x0357)}, 744 {"rdesc": wacom_pth860_v145, "info": (0x3, 0x056A, 0x0358)}, 745 {"rdesc": wacom_pth860_v150, "info": (0x3, 0x056A, 0x0358)}, 746 {"rdesc": wacom_pth460_v105, "info": (0x3, 0x056A, 0x0392)}, 747] 748 749PTHX60_Names = [ 750 "PTH-660/v145", 751 "PTH-660/v150", 752 "PTH-860/v145", 753 "PTH-860/v150", 754 "PTH-460/v105", 755] 756 757 758class TestPTHX60_Pen(TestOpaqueCTLTablet): 759 @pytest.fixture( 760 autouse=True, scope="class", params=PTHX60_Devices, ids=PTHX60_Names 761 ) 762 def set_device_params(self, request): 763 request.cls.device_params = request.param 764 765 def create_device(self): 766 return PTHX60_Pen(**self.device_params) 767 768 @pytest.mark.xfail 769 def test_descriptor_physicals(self): 770 # XFAIL: Various documented errata 771 super().test_descriptor_physicals() 772 773 def test_heartbeat_spurious(self): 774 """ 775 Test that the heartbeat report does not send spurious events. 776 """ 777 uhdev = self.uhdev 778 779 self.sync_and_assert_events( 780 uhdev.event( 781 100, 782 200, 783 pressure=300, 784 buttons=Buttons.clear(), 785 toolid=ToolID(serial=1, tooltype=0x822), 786 proximity=ProximityState.IN_RANGE, 787 ), 788 [ 789 libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1), 790 libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100), 791 libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200), 792 libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1), 793 ], 794 ) 795 796 # Exactly zero events: not even a SYN 797 self.sync_and_assert_events( 798 uhdev.event_heartbeat(19), [], auto_syn=False, strict=True 799 ) 800 801 self.sync_and_assert_events( 802 uhdev.event(110, 200, pressure=300), 803 [ 804 libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110), 805 ], 806 ) 807 808 def test_empty_pad_sync(self): 809 self.empty_pad_sync(num=3, denom=16, reverse=True) 810 811 def empty_pad_sync(self, num, denom, reverse): 812 """ 813 Test that multiple pad collections do not trigger empty syncs. 814 """ 815 816 def offset_rotation(value): 817 """ 818 Offset touchring rotation values by the same factor as the 819 Linux kernel. Tablets historically don't use the same origin 820 as HID, and it sometimes changes from tablet to tablet... 821 """ 822 evdev = self.uhdev.get_evdev() 823 info = evdev.absinfo[libevdev.EV_ABS.ABS_WHEEL] 824 delta = info.maximum - info.minimum + 1 825 if reverse: 826 value = info.maximum - value 827 value += num * delta // denom 828 if value > info.maximum: 829 value -= delta 830 elif value < info.minimum: 831 value += delta 832 return value 833 834 uhdev = self.uhdev 835 uhdev.application = "Pad" 836 evdev = uhdev.get_evdev() 837 838 print(evdev.name) 839 self.sync_and_assert_events( 840 uhdev.event_pad(reportID=17, ring=0, ek0=1), 841 [ 842 libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 1), 843 libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(0)), 844 libevdev.InputEvent(libevdev.EV_ABS.ABS_MISC, 15), 845 ], 846 ) 847 848 self.sync_and_assert_events( 849 uhdev.event_pad(reportID=17, ring=1, ek0=1), 850 [libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(1))], 851 ) 852 853 self.sync_and_assert_events( 854 uhdev.event_pad(reportID=17, ring=2, ek0=0), 855 [ 856 libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(2)), 857 libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 0), 858 ], 859 ) 860 861 862class DirectTabletTest(): 863 def test_prop_direct(self): 864 """ 865 Verify that INPUT_PROP_DIRECT is set and INPUT_PROP_POINTER 866 is not set on display devices. 867 """ 868 evdev = self.uhdev.get_evdev() 869 assert libevdev.INPUT_PROP_DIRECT in evdev.properties 870 assert libevdev.INPUT_PROP_POINTER not in evdev.properties 871 872 873class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest, DirectTabletTest): 874 ContactIds = namedtuple("ContactIds", "contact_id, tracking_id, slot_num") 875 876 def create_device(self): 877 return test_multitouch.Digitizer( 878 "DTH 2452", 879 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", 880 input_info=(0x3, 0x056A, 0x0383), 881 ) 882 883 def make_contact(self, contact_id=0, t=0): 884 """ 885 Make a single touch contact that can move over time. 886 887 Creates a touch object that has a well-known position in space that 888 does not overlap with other contacts. The value of `t` may be 889 incremented over time to move the point along a linear path. 890 """ 891 x = 50 + 10 * contact_id + t * 11 892 y = 100 + 100 * contact_id + t * 11 893 return test_multitouch.Touch(contact_id, x, y) 894 895 def make_contacts(self, n, t=0): 896 """ 897 Make multiple touch contacts that can move over time. 898 899 Returns a list of `n` touch objects that are positioned at well-known 900 locations. The value of `t` may be incremented over time to move the 901 points along a linear path. 902 """ 903 return [self.make_contact(id, t) for id in range(0, n)] 904 905 def assert_contact(self, evdev, contact_ids, t=0): 906 """ 907 Assert properties of a contact generated by make_contact. 908 """ 909 contact_id = contact_ids.contact_id 910 tracking_id = contact_ids.tracking_id 911 slot_num = contact_ids.slot_num 912 913 x = 50 + 10 * contact_id + t * 11 914 y = 100 + 100 * contact_id + t * 11 915 916 # If the data isn't supposed to be stored in any slots, there is 917 # nothing we can check for in the evdev stream. 918 if slot_num is None: 919 assert tracking_id == -1 920 return 921 922 assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == tracking_id 923 if tracking_id != -1: 924 assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_POSITION_X] == x 925 assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_POSITION_Y] == y 926 927 def assert_contacts(self, evdev, data, t=0): 928 """ 929 Assert properties of a list of contacts generated by make_contacts. 930 """ 931 for contact_ids in data: 932 self.assert_contact(evdev, contact_ids, t) 933 934 def test_contact_id_0(self): 935 """ 936 Bring a finger in contact with the tablet, then hold it down and remove it. 937 938 Ensure that even with contact ID = 0 which is usually given as an invalid 939 touch event by most tablets with the exception of a few, that given the 940 confidence bit is set to 1 it should process it as a valid touch to cover 941 the few tablets using contact ID = 0 as a valid touch value. 942 """ 943 uhdev = self.uhdev 944 evdev = uhdev.get_evdev() 945 946 t0 = test_multitouch.Touch(0, 50, 100) 947 r = uhdev.event([t0]) 948 events = uhdev.next_sync_events() 949 self.debug_reports(r, uhdev, events) 950 951 slot = self.get_slot(uhdev, t0, 0) 952 953 assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events 954 assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 955 assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50 956 assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100 957 958 t0.tipswitch = False 959 if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks: 960 t0.inrange = False 961 r = uhdev.event([t0]) 962 events = uhdev.next_sync_events() 963 self.debug_reports(r, uhdev, events) 964 assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events 965 assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 966 967 def test_confidence_false(self): 968 """ 969 Bring a finger in contact with the tablet with confidence set to false. 970 971 Ensure that the confidence bit being set to false should not result in a touch event. 972 """ 973 uhdev = self.uhdev 974 _evdev = uhdev.get_evdev() 975 976 t0 = test_multitouch.Touch(1, 50, 100) 977 t0.confidence = False 978 r = uhdev.event([t0]) 979 events = uhdev.next_sync_events() 980 self.debug_reports(r, uhdev, events) 981 982 _slot = self.get_slot(uhdev, t0, 0) 983 984 assert not events 985 986 def test_confidence_multitouch(self): 987 """ 988 Bring multiple fingers in contact with the tablet, some with the 989 confidence bit set, and some without. 990 991 Ensure that all confident touches are reported and that all non- 992 confident touches are ignored. 993 """ 994 uhdev = self.uhdev 995 evdev = uhdev.get_evdev() 996 997 touches = self.make_contacts(5) 998 touches[0].confidence = False 999 touches[2].confidence = False 1000 touches[4].confidence = False 1001 1002 r = uhdev.event(touches) 1003 events = uhdev.next_sync_events() 1004 self.debug_reports(r, uhdev, events) 1005 1006 assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events 1007 1008 self.assert_contacts( 1009 evdev, 1010 [ 1011 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=None), 1012 self.ContactIds(contact_id=1, tracking_id=0, slot_num=0), 1013 self.ContactIds(contact_id=2, tracking_id=-1, slot_num=None), 1014 self.ContactIds(contact_id=3, tracking_id=1, slot_num=1), 1015 self.ContactIds(contact_id=4, tracking_id=-1, slot_num=None), 1016 ], 1017 ) 1018 1019 def confidence_change_assert_playback(self, uhdev, evdev, timeline): 1020 """ 1021 Assert proper behavior of contacts that move and change tipswitch / 1022 confidence status over time. 1023 1024 Given a `timeline` list of touch states to iterate over, verify 1025 that the contacts move and are reported as up/down as expected 1026 by the state of the tipswitch and confidence bits. 1027 """ 1028 t = 0 1029 1030 for state in timeline: 1031 touches = self.make_contacts(len(state), t) 1032 1033 for item in zip(touches, state): 1034 item[0].tipswitch = item[1][1] 1035 item[0].confidence = item[1][2] 1036 1037 r = uhdev.event(touches) 1038 events = uhdev.next_sync_events() 1039 self.debug_reports(r, uhdev, events) 1040 1041 ids = [x[0] for x in state] 1042 self.assert_contacts(evdev, ids, t) 1043 1044 t += 1 1045 1046 def test_confidence_loss_a(self): 1047 """ 1048 Transition a confident contact to a non-confident contact by 1049 first clearing the tipswitch. 1050 1051 Ensure that the driver reports the transitioned contact as 1052 being removed and that other contacts continue to report 1053 normally. This mode of confidence loss is used by the 1054 DTH-2452. 1055 """ 1056 uhdev = self.uhdev 1057 evdev = uhdev.get_evdev() 1058 1059 self.confidence_change_assert_playback( 1060 uhdev, 1061 evdev, 1062 [ 1063 # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident 1064 # Both fingers confidently in contact 1065 [ 1066 ( 1067 self.ContactIds(contact_id=0, tracking_id=0, slot_num=0), 1068 True, 1069 True, 1070 ), 1071 ( 1072 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1073 True, 1074 True, 1075 ), 1076 ], 1077 # t=1: Contact 0 == !Down + confident; Contact 1 == Down + confident 1078 # First finger looses confidence and clears only the tipswitch flag 1079 [ 1080 ( 1081 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0), 1082 False, 1083 True, 1084 ), 1085 ( 1086 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1087 True, 1088 True, 1089 ), 1090 ], 1091 # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident 1092 # First finger has lost confidence and has both flags cleared 1093 [ 1094 ( 1095 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0), 1096 False, 1097 False, 1098 ), 1099 ( 1100 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1101 True, 1102 True, 1103 ), 1104 ], 1105 # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident 1106 # First finger has lost confidence and has both flags cleared 1107 [ 1108 ( 1109 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0), 1110 False, 1111 False, 1112 ), 1113 ( 1114 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1115 True, 1116 True, 1117 ), 1118 ], 1119 ], 1120 ) 1121 1122 def test_confidence_loss_b(self): 1123 """ 1124 Transition a confident contact to a non-confident contact by 1125 cleraing both tipswitch and confidence bits simultaneously. 1126 1127 Ensure that the driver reports the transitioned contact as 1128 being removed and that other contacts continue to report 1129 normally. This mode of confidence loss is used by some 1130 AES devices. 1131 """ 1132 uhdev = self.uhdev 1133 evdev = uhdev.get_evdev() 1134 1135 self.confidence_change_assert_playback( 1136 uhdev, 1137 evdev, 1138 [ 1139 # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident 1140 # Both fingers confidently in contact 1141 [ 1142 ( 1143 self.ContactIds(contact_id=0, tracking_id=0, slot_num=0), 1144 True, 1145 True, 1146 ), 1147 ( 1148 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1149 True, 1150 True, 1151 ), 1152 ], 1153 # t=1: Contact 0 == !Down + !confident; Contact 1 == Down + confident 1154 # First finger looses confidence and has both flags cleared simultaneously 1155 [ 1156 ( 1157 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0), 1158 False, 1159 False, 1160 ), 1161 ( 1162 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1163 True, 1164 True, 1165 ), 1166 ], 1167 # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident 1168 # First finger has lost confidence and has both flags cleared 1169 [ 1170 ( 1171 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0), 1172 False, 1173 False, 1174 ), 1175 ( 1176 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1177 True, 1178 True, 1179 ), 1180 ], 1181 # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident 1182 # First finger has lost confidence and has both flags cleared 1183 [ 1184 ( 1185 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0), 1186 False, 1187 False, 1188 ), 1189 ( 1190 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1191 True, 1192 True, 1193 ), 1194 ], 1195 ], 1196 ) 1197 1198 def test_confidence_loss_c(self): 1199 """ 1200 Transition a confident contact to a non-confident contact by 1201 clearing only the confidence bit. 1202 1203 Ensure that the driver reports the transitioned contact as 1204 being removed and that other contacts continue to report 1205 normally. 1206 """ 1207 uhdev = self.uhdev 1208 evdev = uhdev.get_evdev() 1209 1210 self.confidence_change_assert_playback( 1211 uhdev, 1212 evdev, 1213 [ 1214 # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident 1215 # Both fingers confidently in contact 1216 [ 1217 ( 1218 self.ContactIds(contact_id=0, tracking_id=0, slot_num=0), 1219 True, 1220 True, 1221 ), 1222 ( 1223 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1224 True, 1225 True, 1226 ), 1227 ], 1228 # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident 1229 # First finger looses confidence and clears only the confidence flag 1230 [ 1231 ( 1232 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0), 1233 True, 1234 False, 1235 ), 1236 ( 1237 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1238 True, 1239 True, 1240 ), 1241 ], 1242 # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident 1243 # First finger has lost confidence and has both flags cleared 1244 [ 1245 ( 1246 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0), 1247 False, 1248 False, 1249 ), 1250 ( 1251 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1252 True, 1253 True, 1254 ), 1255 ], 1256 # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident 1257 # First finger has lost confidence and has both flags cleared 1258 [ 1259 ( 1260 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0), 1261 False, 1262 False, 1263 ), 1264 ( 1265 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1266 True, 1267 True, 1268 ), 1269 ], 1270 ], 1271 ) 1272 1273 def test_confidence_gain_a(self): 1274 """ 1275 Transition a contact that was always non-confident to confident. 1276 1277 Ensure that the confident contact is reported normally. 1278 """ 1279 uhdev = self.uhdev 1280 evdev = uhdev.get_evdev() 1281 1282 self.confidence_change_assert_playback( 1283 uhdev, 1284 evdev, 1285 [ 1286 # t=0: Contact 0 == Down + !confident; Contact 1 == Down + confident 1287 # Only second finger is confidently in contact 1288 [ 1289 ( 1290 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=None), 1291 True, 1292 False, 1293 ), 1294 ( 1295 self.ContactIds(contact_id=1, tracking_id=0, slot_num=0), 1296 True, 1297 True, 1298 ), 1299 ], 1300 # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident 1301 # First finger gains confidence 1302 [ 1303 ( 1304 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=None), 1305 True, 1306 False, 1307 ), 1308 ( 1309 self.ContactIds(contact_id=1, tracking_id=0, slot_num=0), 1310 True, 1311 True, 1312 ), 1313 ], 1314 # t=2: Contact 0 == Down + confident; Contact 1 == Down + confident 1315 # First finger remains confident 1316 [ 1317 ( 1318 self.ContactIds(contact_id=0, tracking_id=1, slot_num=1), 1319 True, 1320 True, 1321 ), 1322 ( 1323 self.ContactIds(contact_id=1, tracking_id=0, slot_num=0), 1324 True, 1325 True, 1326 ), 1327 ], 1328 # t=3: Contact 0 == Down + confident; Contact 1 == Down + confident 1329 # First finger remains confident 1330 [ 1331 ( 1332 self.ContactIds(contact_id=0, tracking_id=1, slot_num=1), 1333 True, 1334 True, 1335 ), 1336 ( 1337 self.ContactIds(contact_id=1, tracking_id=0, slot_num=0), 1338 True, 1339 True, 1340 ), 1341 ], 1342 ], 1343 ) 1344 1345 def test_confidence_gain_b(self): 1346 """ 1347 Transition a contact from non-confident to confident. 1348 1349 Ensure that the confident contact is reported normally. 1350 """ 1351 uhdev = self.uhdev 1352 evdev = uhdev.get_evdev() 1353 1354 self.confidence_change_assert_playback( 1355 uhdev, 1356 evdev, 1357 [ 1358 # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident 1359 # First and second finger confidently in contact 1360 [ 1361 ( 1362 self.ContactIds(contact_id=0, tracking_id=0, slot_num=0), 1363 True, 1364 True, 1365 ), 1366 ( 1367 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1368 True, 1369 True, 1370 ), 1371 ], 1372 # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident 1373 # Firtst finger looses confidence 1374 [ 1375 ( 1376 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0), 1377 True, 1378 False, 1379 ), 1380 ( 1381 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1382 True, 1383 True, 1384 ), 1385 ], 1386 # t=2: Contact 0 == Down + confident; Contact 1 == Down + confident 1387 # First finger gains confidence 1388 [ 1389 ( 1390 self.ContactIds(contact_id=0, tracking_id=2, slot_num=0), 1391 True, 1392 True, 1393 ), 1394 ( 1395 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1396 True, 1397 True, 1398 ), 1399 ], 1400 # t=3: Contact 0 == !Down + confident; Contact 1 == Down + confident 1401 # First finger goes up 1402 [ 1403 ( 1404 self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0), 1405 False, 1406 True, 1407 ), 1408 ( 1409 self.ContactIds(contact_id=1, tracking_id=1, slot_num=1), 1410 True, 1411 True, 1412 ), 1413 ], 1414 ], 1415 ) 1416