File Index
Files and directories
Sign in
FILE

test_wacom_generic.py

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/hid/tests/test_wacom_generic.py PYTHON
   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        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
 614class PenTabletTest(BaseTest.TestTablet):
 615    def assertName(self, uhdev):
 616        super().assertName(uhdev, " Pen")
 617
 618
 619class TouchTabletTest(BaseTest.TestTablet):
 620    def assertName(self, uhdev):
 621        super().assertName(uhdev, " Finger")
 622
 623
 624class 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
 681class 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
 744PTHX60_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
 752PTHX60_Names = [
 753    "PTH-660/v145",
 754    "PTH-660/v150",
 755    "PTH-860/v145",
 756    "PTH-860/v150",
 757    "PTH-460/v105",
 758]
 759
 760
 761class 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
 865class 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        )
~/ Read-only file preview.