| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415 |
- #!/bin/env python3
- # SPDX-License-Identifier: GPL-2.0
- # -*- coding: utf-8 -*-
- #
- # Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
- # Copyright (c) 2017 Red Hat, Inc.
- # Copyright (c) 2020 Wacom Technology Corp.
- #
- # Authors:
- # Jason Gerecke <jason.gerecke@wacom.com>
- """
- Tests for the Wacom driver generic codepath.
- This module tests the function of the Wacom driver's generic codepath.
- The generic codepath is used by devices which are not explicitly listed
- in the driver's device table. It uses the device's HID descriptor to
- decode reports sent by the device.
- """
- from .descriptors_wacom import (
- wacom_pth660_v145,
- wacom_pth660_v150,
- wacom_pth860_v145,
- wacom_pth860_v150,
- wacom_pth460_v105,
- )
- import attr
- from collections import namedtuple
- from enum import Enum
- from hidtools.hut import HUT
- from hidtools.hid import HidUnit
- from . import base
- from . import test_multitouch
- import libevdev
- import pytest
- import logging
- logger = logging.getLogger("hidtools.test.wacom")
- KERNEL_MODULE = base.KernelModule("wacom", "wacom")
- class ProximityState(Enum):
- """
- Enumeration of allowed proximity states.
- """
- # Tool is not able to be sensed by the device
- OUT = 0
- # Tool is close enough to be sensed, but some data may be invalid
- # or inaccurate
- IN_PROXIMITY = 1
- # Tool is close enough to be sensed with high accuracy. All data
- # valid.
- IN_RANGE = 2
- def fill(self, reportdata):
- """Fill a report with approrpiate HID properties/values."""
- reportdata.inrange = self in [ProximityState.IN_RANGE]
- reportdata.wacomsense = self in [
- ProximityState.IN_PROXIMITY,
- ProximityState.IN_RANGE,
- ]
- class ReportData:
- """
- Placeholder for HID report values.
- """
- pass
- @attr.s
- class Buttons:
- """
- Stylus button state.
- Describes the state of each of the buttons / "side switches" that
- may be present on a stylus. Buttons set to 'None' indicate the
- state is "unchanged" since the previous event.
- """
- primary = attr.ib(default=None)
- secondary = attr.ib(default=None)
- tertiary = attr.ib(default=None)
- @staticmethod
- def clear():
- """Button object with all states cleared."""
- return Buttons(False, False, False)
- def fill(self, reportdata):
- """Fill a report with approrpiate HID properties/values."""
- reportdata.barrelswitch = int(self.primary or 0)
- reportdata.secondarybarrelswitch = int(self.secondary or 0)
- reportdata.b3 = int(self.tertiary or 0)
- @attr.s
- class ToolID:
- """
- Stylus tool identifiers.
- Contains values used to identify a specific stylus, e.g. its serial
- number and tool-type identifier. Values of ``0`` may sometimes be
- used for the out-of-range condition.
- """
- serial = attr.ib()
- tooltype = attr.ib()
- @staticmethod
- def clear():
- """ToolID object with all fields cleared."""
- return ToolID(0, 0)
- def fill(self, reportdata):
- """Fill a report with approrpiate HID properties/values."""
- reportdata.transducerserialnumber = self.serial & 0xFFFFFFFF
- reportdata.serialhi = (self.serial >> 32) & 0xFFFFFFFF
- reportdata.tooltype = self.tooltype
- @attr.s
- class PhysRange:
- """
- Range of HID physical values, with units.
- """
- unit = attr.ib()
- min_size = attr.ib()
- max_size = attr.ib()
- CENTIMETER = HidUnit.from_string("SILinear: cm")
- DEGREE = HidUnit.from_string("EnglishRotation: deg")
- def contains(self, field):
- """
- Check if the physical size of the provided field is in range.
- Compare the physical size described by the provided HID field
- against the range of sizes described by this object. This is
- an exclusive range comparison (e.g. 0 cm is not within the
- range 0 cm - 5 cm) and exact unit comparison (e.g. 1 inch is
- not within the range 0 cm - 5 cm).
- """
- phys_size = (field.physical_max - field.physical_min) * 10 ** (field.unit_exp)
- return (
- field.unit == self.unit.value
- and phys_size > self.min_size
- and phys_size < self.max_size
- )
- class BaseTablet(base.UHIDTestDevice):
- """
- Skeleton object for all kinds of tablet devices.
- """
- def __init__(self, rdesc, name=None, info=None):
- assert rdesc is not None
- super().__init__(name, "Pen", input_info=info, rdesc=rdesc)
- self.buttons = Buttons.clear()
- self.toolid = ToolID.clear()
- self.proximity = ProximityState.OUT
- self.offset = 0
- self.ring = -1
- self.ek0 = False
- def match_evdev_rule(self, application, evdev):
- """
- Filter out evdev nodes based on the requested application.
- The Wacom driver may create several device nodes for each USB
- interface device. It is crucial that we run tests with the
- expected device node or things will obviously go off the rails.
- Use the Wacom driver's usual naming conventions to apply a
- sensible default filter.
- """
- if application in ["Pen", "Pad"]:
- return evdev.name.endswith(application)
- else:
- return True
- def create_report(
- self, x, y, pressure, buttons=None, toolid=None, proximity=None, reportID=None
- ):
- """
- Return an input report for this device.
- :param x: absolute x
- :param y: absolute y
- :param pressure: pressure
- :param buttons: stylus button state. Use ``None`` for unchanged.
- :param toolid: tool identifiers. Use ``None`` for unchanged.
- :param proximity: a ProximityState indicating the sensor's ability
- to detect and report attributes of this tool. Use ``None``
- for unchanged.
- :param reportID: the numeric report ID for this report, if needed
- """
- if buttons is not None:
- self.buttons = buttons
- buttons = self.buttons
- if toolid is not None:
- self.toolid = toolid
- toolid = self.toolid
- if proximity is not None:
- self.proximity = proximity
- proximity = self.proximity
- reportID = reportID or self.default_reportID
- report = ReportData()
- report.x = x
- report.y = y
- report.tippressure = pressure
- report.tipswitch = pressure > 0
- buttons.fill(report)
- proximity.fill(report)
- toolid.fill(report)
- return super().create_report(report, reportID=reportID)
- def create_report_heartbeat(self, reportID):
- """
- Return a heartbeat input report for this device.
- Heartbeat reports generally contain battery status information,
- among other things.
- """
- report = ReportData()
- report.wacombatterycharging = 1
- return super().create_report(report, reportID=reportID)
- def create_report_pad(self, reportID, ring, ek0):
- report = ReportData()
- if ring is not None:
- self.ring = ring
- ring = self.ring
- if ek0 is not None:
- self.ek0 = ek0
- ek0 = self.ek0
- if ring >= 0:
- report.wacomtouchring = ring
- report.wacomtouchringstatus = 1
- else:
- report.wacomtouchring = 0x7F
- report.wacomtouchringstatus = 0
- report.wacomexpresskey00 = ek0
- return super().create_report(report, reportID=reportID)
- def event(self, x, y, pressure, buttons=None, toolid=None, proximity=None):
- """
- Send an input event on the default report ID.
- :param x: absolute x
- :param y: absolute y
- :param buttons: stylus button state. Use ``None`` for unchanged.
- :param toolid: tool identifiers. Use ``None`` for unchanged.
- :param proximity: a ProximityState indicating the sensor's ability
- to detect and report attributes of this tool. Use ``None``
- for unchanged.
- """
- r = self.create_report(x, y, pressure, buttons, toolid, proximity)
- self.call_input_event(r)
- return [r]
- def event_heartbeat(self, reportID):
- """
- Send a heartbeat event on the requested report ID.
- """
- r = self.create_report_heartbeat(reportID)
- self.call_input_event(r)
- return [r]
- def event_pad(self, reportID, ring=None, ek0=None):
- """
- Send a pad event on the requested report ID.
- """
- r = self.create_report_pad(reportID, ring, ek0)
- self.call_input_event(r)
- return [r]
- def get_report(self, req, rnum, rtype):
- if rtype != self.UHID_FEATURE_REPORT:
- return (1, [])
- rdesc = None
- for v in self.parsed_rdesc.feature_reports.values():
- if v.report_ID == rnum:
- rdesc = v
- if rdesc is None:
- return (1, [])
- result = (1, [])
- result = self.create_report_offset(rdesc) or result
- return result
- def create_report_offset(self, rdesc):
- require = [
- "Wacom Offset Left",
- "Wacom Offset Top",
- "Wacom Offset Right",
- "Wacom Offset Bottom",
- ]
- if not set(require).issubset(set([f.usage_name for f in rdesc])):
- return None
- report = ReportData()
- report.wacomoffsetleft = self.offset
- report.wacomoffsettop = self.offset
- report.wacomoffsetright = self.offset
- report.wacomoffsetbottom = self.offset
- r = rdesc.create_report([report], None)
- return (0, r)
- class OpaqueTablet(BaseTablet):
- """
- Bare-bones opaque tablet with a minimum of features.
- A tablet stripped down to its absolute core. It is capable of
- reporting X/Y position and if the pen is in contact. No pressure,
- no barrel switches, no eraser. Notably it *does* report an "In
- Range" flag, but this is only because the Wacom driver expects
- one to function properly. The device uses only standard HID usages,
- not any of Wacom's vendor-defined pages.
- """
- # fmt: off
- report_descriptor = [
- 0x05, 0x0D, # . Usage Page (Digitizer),
- 0x09, 0x01, # . Usage (Digitizer),
- 0xA1, 0x01, # . Collection (Application),
- 0x85, 0x01, # . Report ID (1),
- 0x09, 0x20, # . Usage (Stylus),
- 0xA1, 0x00, # . Collection (Physical),
- 0x09, 0x42, # . Usage (Tip Switch),
- 0x09, 0x32, # . Usage (In Range),
- 0x15, 0x00, # . Logical Minimum (0),
- 0x25, 0x01, # . Logical Maximum (1),
- 0x75, 0x01, # . Report Size (1),
- 0x95, 0x02, # . Report Count (2),
- 0x81, 0x02, # . Input (Variable),
- 0x95, 0x06, # . Report Count (6),
- 0x81, 0x03, # . Input (Constant, Variable),
- 0x05, 0x01, # . Usage Page (Desktop),
- 0x09, 0x30, # . Usage (X),
- 0x27, 0x80, 0x3E, 0x00, 0x00, # . Logical Maximum (16000),
- 0x47, 0x80, 0x3E, 0x00, 0x00, # . Physical Maximum (16000),
- 0x65, 0x11, # . Unit (Centimeter),
- 0x55, 0x0D, # . Unit Exponent (13),
- 0x75, 0x10, # . Report Size (16),
- 0x95, 0x01, # . Report Count (1),
- 0x81, 0x02, # . Input (Variable),
- 0x09, 0x31, # . Usage (Y),
- 0x27, 0x28, 0x23, 0x00, 0x00, # . Logical Maximum (9000),
- 0x47, 0x28, 0x23, 0x00, 0x00, # . Physical Maximum (9000),
- 0x81, 0x02, # . Input (Variable),
- 0xC0, # . End Collection,
- 0xC0, # . End Collection,
- ]
- # fmt: on
- def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)):
- super().__init__(rdesc, name, info)
- self.default_reportID = 1
- class OpaqueCTLTablet(BaseTablet):
- """
- Opaque tablet similar to something in the CTL product line.
- A pen-only tablet with most basic features you would expect from
- an actual device. Position, eraser, pressure, barrel buttons.
- Uses the Wacom vendor-defined usage page.
- """
- # fmt: off
- report_descriptor = [
- 0x06, 0x0D, 0xFF, # . Usage Page (Vnd Wacom Emr),
- 0x09, 0x01, # . Usage (Digitizer),
- 0xA1, 0x01, # . Collection (Application),
- 0x85, 0x10, # . Report ID (16),
- 0x09, 0x20, # . Usage (Stylus),
- 0x35, 0x00, # . Physical Minimum (0),
- 0x45, 0x00, # . Physical Maximum (0),
- 0x15, 0x00, # . Logical Minimum (0),
- 0x25, 0x01, # . Logical Maximum (1),
- 0xA1, 0x00, # . Collection (Physical),
- 0x09, 0x42, # . Usage (Tip Switch),
- 0x09, 0x44, # . Usage (Barrel Switch),
- 0x09, 0x5A, # . Usage (Secondary Barrel Switch),
- 0x09, 0x45, # . Usage (Eraser),
- 0x09, 0x3C, # . Usage (Invert),
- 0x09, 0x32, # . Usage (In Range),
- 0x09, 0x36, # . Usage (In Proximity),
- 0x25, 0x01, # . Logical Maximum (1),
- 0x75, 0x01, # . Report Size (1),
- 0x95, 0x07, # . Report Count (7),
- 0x81, 0x02, # . Input (Variable),
- 0x95, 0x01, # . Report Count (1),
- 0x81, 0x03, # . Input (Constant, Variable),
- 0x0A, 0x30, 0x01, # . Usage (X),
- 0x65, 0x11, # . Unit (Centimeter),
- 0x55, 0x0D, # . Unit Exponent (13),
- 0x47, 0x80, 0x3E, 0x00, 0x00, # . Physical Maximum (16000),
- 0x27, 0x80, 0x3E, 0x00, 0x00, # . Logical Maximum (16000),
- 0x75, 0x18, # . Report Size (24),
- 0x95, 0x01, # . Report Count (1),
- 0x81, 0x02, # . Input (Variable),
- 0x0A, 0x31, 0x01, # . Usage (Y),
- 0x47, 0x28, 0x23, 0x00, 0x00, # . Physical Maximum (9000),
- 0x27, 0x28, 0x23, 0x00, 0x00, # . Logical Maximum (9000),
- 0x81, 0x02, # . Input (Variable),
- 0x09, 0x30, # . Usage (Tip Pressure),
- 0x55, 0x00, # . Unit Exponent (0),
- 0x65, 0x00, # . Unit,
- 0x47, 0x00, 0x00, 0x00, 0x00, # . Physical Maximum (0),
- 0x26, 0xFF, 0x0F, # . Logical Maximum (4095),
- 0x75, 0x10, # . Report Size (16),
- 0x81, 0x02, # . Input (Variable),
- 0x75, 0x08, # . Report Size (8),
- 0x95, 0x06, # . Report Count (6),
- 0x81, 0x03, # . Input (Constant, Variable),
- 0x0A, 0x32, 0x01, # . Usage (Z),
- 0x25, 0x3F, # . Logical Maximum (63),
- 0x75, 0x08, # . Report Size (8),
- 0x95, 0x01, # . Report Count (1),
- 0x81, 0x02, # . Input (Variable),
- 0x09, 0x5B, # . Usage (Transducer Serial Number),
- 0x09, 0x5C, # . Usage (Transducer Serial Number Hi),
- 0x17, 0x00, 0x00, 0x00, 0x80, # . Logical Minimum (-2147483648),
- 0x27, 0xFF, 0xFF, 0xFF, 0x7F, # . Logical Maximum (2147483647),
- 0x75, 0x20, # . Report Size (32),
- 0x95, 0x02, # . Report Count (2),
- 0x81, 0x02, # . Input (Variable),
- 0x09, 0x77, # . Usage (Tool Type),
- 0x15, 0x00, # . Logical Minimum (0),
- 0x26, 0xFF, 0x0F, # . Logical Maximum (4095),
- 0x75, 0x10, # . Report Size (16),
- 0x95, 0x01, # . Report Count (1),
- 0x81, 0x02, # . Input (Variable),
- 0xC0, # . End Collection,
- 0xC0 # . End Collection
- ]
- # fmt: on
- def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)):
- super().__init__(rdesc, name, info)
- self.default_reportID = 16
- class PTHX60_Pen(BaseTablet):
- """
- Pen interface of a PTH-660 / PTH-860 / PTH-460 tablet.
- This generation of devices are nearly identical to each other, though
- the PTH-460 uses a slightly different descriptor construction (splits
- the pad among several physical collections)
- """
- def __init__(self, rdesc=None, name=None, info=None):
- super().__init__(rdesc, name, info)
- self.default_reportID = 16
- class BaseTest:
- class TestTablet(base.BaseTestCase.TestUhid):
- kernel_modules = [KERNEL_MODULE]
- def sync_and_assert_events(
- self, report, expected_events, auto_syn=True, strict=False
- ):
- """
- Assert we see the expected events in response to a report.
- """
- uhdev = self.uhdev
- syn_event = self.syn_event
- if auto_syn:
- expected_events.append(syn_event)
- actual_events = uhdev.next_sync_events()
- self.debug_reports(report, uhdev, actual_events)
- if strict:
- self.assertInputEvents(expected_events, actual_events)
- else:
- self.assertInputEventsIn(expected_events, actual_events)
- def get_usages(self, uhdev):
- def get_report_usages(report):
- application = report.application
- for field in report.fields:
- if field.usages is not None:
- for usage in field.usages:
- yield (field, usage, application)
- else:
- yield (field, field.usage, application)
- desc = uhdev.parsed_rdesc
- reports = [
- *desc.input_reports.values(),
- *desc.feature_reports.values(),
- *desc.output_reports.values(),
- ]
- for report in reports:
- for usage in get_report_usages(report):
- yield usage
- def assertName(self, uhdev, type):
- """
- Assert that the name is as we expect.
- The Wacom driver applies a number of decorations to the name
- provided by the hardware. We cannot rely on the definition of
- this assertion from the base class to work properly.
- """
- evdev = uhdev.get_evdev()
- expected_name = uhdev.name + type
- if "wacom" not in expected_name.lower():
- expected_name = "Wacom " + expected_name
- assert evdev.name == expected_name
- def test_descriptor_physicals(self):
- """
- Verify that all HID usages which should have a physical range
- actually do, and those which shouldn't don't. Also verify that
- the associated unit is correct and within a sensible range.
- """
- def usage_id(page_name, usage_name):
- page = HUT.usage_page_from_name(page_name)
- return (page.page_id << 16) | page[usage_name].usage
- required = {
- usage_id("Generic Desktop", "X"): PhysRange(
- PhysRange.CENTIMETER, 5, 150
- ),
- usage_id("Generic Desktop", "Y"): PhysRange(
- PhysRange.CENTIMETER, 5, 150
- ),
- usage_id("Digitizers", "Width"): PhysRange(
- PhysRange.CENTIMETER, 5, 150
- ),
- usage_id("Digitizers", "Height"): PhysRange(
- PhysRange.CENTIMETER, 5, 150
- ),
- usage_id("Digitizers", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),
- usage_id("Digitizers", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),
- usage_id("Digitizers", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360),
- usage_id("Wacom", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),
- usage_id("Wacom", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),
- usage_id("Wacom", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360),
- usage_id("Wacom", "X"): PhysRange(PhysRange.CENTIMETER, 5, 150),
- usage_id("Wacom", "Y"): PhysRange(PhysRange.CENTIMETER, 5, 150),
- usage_id("Wacom", "Wacom TouchRing"): PhysRange(
- PhysRange.DEGREE, 358, 360
- ),
- usage_id("Wacom", "Wacom Offset Left"): PhysRange(
- PhysRange.CENTIMETER, 0, 0.5
- ),
- usage_id("Wacom", "Wacom Offset Top"): PhysRange(
- PhysRange.CENTIMETER, 0, 0.5
- ),
- usage_id("Wacom", "Wacom Offset Right"): PhysRange(
- PhysRange.CENTIMETER, 0, 0.5
- ),
- usage_id("Wacom", "Wacom Offset Bottom"): PhysRange(
- PhysRange.CENTIMETER, 0, 0.5
- ),
- }
- for field, usage, application in self.get_usages(self.uhdev):
- if application == usage_id("Generic Desktop", "Mouse"):
- # Ignore the vestigial Mouse collection which exists
- # on Wacom tablets only for backwards compatibility.
- continue
- expect_physical = usage in required
- phys_set = field.physical_min != 0 or field.physical_max != 0
- assert phys_set == expect_physical
- unit_set = field.unit != 0
- assert unit_set == expect_physical
- if unit_set:
- assert required[usage].contains(field)
- class PenTabletTest(BaseTest.TestTablet):
- def assertName(self, uhdev):
- super().assertName(uhdev, " Pen")
- class TouchTabletTest(BaseTest.TestTablet):
- def assertName(self, uhdev):
- super().assertName(uhdev, " Finger")
- class TestOpaqueTablet(PenTabletTest):
- def create_device(self):
- return OpaqueTablet()
- def test_sanity(self):
- """
- Bring a pen into contact with the tablet, then remove it.
- Ensure that we get the basic tool/touch/motion events that should
- be sent by the driver.
- """
- uhdev = self.uhdev
- self.sync_and_assert_events(
- uhdev.event(
- 100,
- 200,
- pressure=300,
- buttons=Buttons.clear(),
- toolid=ToolID(serial=1, tooltype=1),
- proximity=ProximityState.IN_RANGE,
- ),
- [
- libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1),
- libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100),
- libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200),
- libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1),
- ],
- )
- self.sync_and_assert_events(
- uhdev.event(110, 220, pressure=0),
- [
- libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110),
- libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 220),
- libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0),
- ],
- )
- self.sync_and_assert_events(
- uhdev.event(
- 120,
- 230,
- pressure=0,
- toolid=ToolID.clear(),
- proximity=ProximityState.OUT,
- ),
- [
- libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0),
- ],
- )
- self.sync_and_assert_events(
- uhdev.event(130, 240, pressure=0), [], auto_syn=False, strict=True
- )
- def test_prop_pointer(self):
- """
- Verify that INPUT_PROP_POINTER is set and INPUT_PROP_DIRECT
- is not set on opaque devices.
- """
- evdev = self.uhdev.get_evdev()
- assert libevdev.INPUT_PROP_POINTER in evdev.properties
- assert libevdev.INPUT_PROP_DIRECT not in evdev.properties
- class TestOpaqueCTLTablet(TestOpaqueTablet):
- def create_device(self):
- return OpaqueCTLTablet()
- def test_buttons(self):
- """
- Test that the barrel buttons (side switches) work as expected.
- Press and release each button individually to verify that we get
- the expected events.
- """
- uhdev = self.uhdev
- self.sync_and_assert_events(
- uhdev.event(
- 100,
- 200,
- pressure=0,
- buttons=Buttons.clear(),
- toolid=ToolID(serial=1, tooltype=1),
- proximity=ProximityState.IN_RANGE,
- ),
- [
- libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1),
- libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100),
- libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200),
- libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
- ],
- )
- self.sync_and_assert_events(
- uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=True)),
- [
- libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1),
- libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
- ],
- )
- self.sync_and_assert_events(
- uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=False)),
- [
- libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0),
- libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
- ],
- )
- self.sync_and_assert_events(
- uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=True)),
- [
- libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 1),
- libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
- ],
- )
- self.sync_and_assert_events(
- uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=False)),
- [
- libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 0),
- libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
- ],
- )
- PTHX60_Devices = [
- {"rdesc": wacom_pth660_v145, "info": (0x3, 0x056A, 0x0357)},
- {"rdesc": wacom_pth660_v150, "info": (0x3, 0x056A, 0x0357)},
- {"rdesc": wacom_pth860_v145, "info": (0x3, 0x056A, 0x0358)},
- {"rdesc": wacom_pth860_v150, "info": (0x3, 0x056A, 0x0358)},
- {"rdesc": wacom_pth460_v105, "info": (0x3, 0x056A, 0x0392)},
- ]
- PTHX60_Names = [
- "PTH-660/v145",
- "PTH-660/v150",
- "PTH-860/v145",
- "PTH-860/v150",
- "PTH-460/v105",
- ]
- class TestPTHX60_Pen(TestOpaqueCTLTablet):
- @pytest.fixture(
- autouse=True, scope="class", params=PTHX60_Devices, ids=PTHX60_Names
- )
- def set_device_params(self, request):
- request.cls.device_params = request.param
- def create_device(self):
- return PTHX60_Pen(**self.device_params)
- @pytest.mark.xfail
- def test_descriptor_physicals(self):
- # XFAIL: Various documented errata
- super().test_descriptor_physicals()
- def test_heartbeat_spurious(self):
- """
- Test that the heartbeat report does not send spurious events.
- """
- uhdev = self.uhdev
- self.sync_and_assert_events(
- uhdev.event(
- 100,
- 200,
- pressure=300,
- buttons=Buttons.clear(),
- toolid=ToolID(serial=1, tooltype=0x822),
- proximity=ProximityState.IN_RANGE,
- ),
- [
- libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1),
- libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100),
- libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200),
- libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1),
- ],
- )
- # Exactly zero events: not even a SYN
- self.sync_and_assert_events(
- uhdev.event_heartbeat(19), [], auto_syn=False, strict=True
- )
- self.sync_and_assert_events(
- uhdev.event(110, 200, pressure=300),
- [
- libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110),
- ],
- )
- def test_empty_pad_sync(self):
- self.empty_pad_sync(num=3, denom=16, reverse=True)
- def empty_pad_sync(self, num, denom, reverse):
- """
- Test that multiple pad collections do not trigger empty syncs.
- """
- def offset_rotation(value):
- """
- Offset touchring rotation values by the same factor as the
- Linux kernel. Tablets historically don't use the same origin
- as HID, and it sometimes changes from tablet to tablet...
- """
- evdev = self.uhdev.get_evdev()
- info = evdev.absinfo[libevdev.EV_ABS.ABS_WHEEL]
- delta = info.maximum - info.minimum + 1
- if reverse:
- value = info.maximum - value
- value += num * delta // denom
- if value > info.maximum:
- value -= delta
- elif value < info.minimum:
- value += delta
- return value
- uhdev = self.uhdev
- uhdev.application = "Pad"
- evdev = uhdev.get_evdev()
- print(evdev.name)
- self.sync_and_assert_events(
- uhdev.event_pad(reportID=17, ring=0, ek0=1),
- [
- libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 1),
- libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(0)),
- libevdev.InputEvent(libevdev.EV_ABS.ABS_MISC, 15),
- ],
- )
- self.sync_and_assert_events(
- uhdev.event_pad(reportID=17, ring=1, ek0=1),
- [libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(1))],
- )
- self.sync_and_assert_events(
- uhdev.event_pad(reportID=17, ring=2, ek0=0),
- [
- libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(2)),
- libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 0),
- ],
- )
- class DirectTabletTest():
- def test_prop_direct(self):
- """
- Verify that INPUT_PROP_DIRECT is set and INPUT_PROP_POINTER
- is not set on display devices.
- """
- evdev = self.uhdev.get_evdev()
- assert libevdev.INPUT_PROP_DIRECT in evdev.properties
- assert libevdev.INPUT_PROP_POINTER not in evdev.properties
- class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest, DirectTabletTest):
- ContactIds = namedtuple("ContactIds", "contact_id, tracking_id, slot_num")
- def create_device(self):
- return test_multitouch.Digitizer(
- "DTH 2452",
- 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",
- input_info=(0x3, 0x056A, 0x0383),
- )
- def make_contact(self, contact_id=0, t=0):
- """
- Make a single touch contact that can move over time.
- Creates a touch object that has a well-known position in space that
- does not overlap with other contacts. The value of `t` may be
- incremented over time to move the point along a linear path.
- """
- x = 50 + 10 * contact_id + t * 11
- y = 100 + 100 * contact_id + t * 11
- return test_multitouch.Touch(contact_id, x, y)
- def make_contacts(self, n, t=0):
- """
- Make multiple touch contacts that can move over time.
- Returns a list of `n` touch objects that are positioned at well-known
- locations. The value of `t` may be incremented over time to move the
- points along a linear path.
- """
- return [self.make_contact(id, t) for id in range(0, n)]
- def assert_contact(self, evdev, contact_ids, t=0):
- """
- Assert properties of a contact generated by make_contact.
- """
- contact_id = contact_ids.contact_id
- tracking_id = contact_ids.tracking_id
- slot_num = contact_ids.slot_num
- x = 50 + 10 * contact_id + t * 11
- y = 100 + 100 * contact_id + t * 11
- # If the data isn't supposed to be stored in any slots, there is
- # nothing we can check for in the evdev stream.
- if slot_num is None:
- assert tracking_id == -1
- return
- assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == tracking_id
- if tracking_id != -1:
- assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_POSITION_X] == x
- assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_POSITION_Y] == y
- def assert_contacts(self, evdev, data, t=0):
- """
- Assert properties of a list of contacts generated by make_contacts.
- """
- for contact_ids in data:
- self.assert_contact(evdev, contact_ids, t)
- def test_contact_id_0(self):
- """
- Bring a finger in contact with the tablet, then hold it down and remove it.
- Ensure that even with contact ID = 0 which is usually given as an invalid
- touch event by most tablets with the exception of a few, that given the
- confidence bit is set to 1 it should process it as a valid touch to cover
- the few tablets using contact ID = 0 as a valid touch value.
- """
- uhdev = self.uhdev
- evdev = uhdev.get_evdev()
- t0 = test_multitouch.Touch(0, 50, 100)
- r = uhdev.event([t0])
- events = uhdev.next_sync_events()
- self.debug_reports(r, uhdev, events)
- slot = self.get_slot(uhdev, t0, 0)
- assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
- assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
- assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
- assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
- t0.tipswitch = False
- if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
- t0.inrange = False
- r = uhdev.event([t0])
- events = uhdev.next_sync_events()
- self.debug_reports(r, uhdev, events)
- assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
- assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
- def test_confidence_false(self):
- """
- Bring a finger in contact with the tablet with confidence set to false.
- Ensure that the confidence bit being set to false should not result in a touch event.
- """
- uhdev = self.uhdev
- _evdev = uhdev.get_evdev()
- t0 = test_multitouch.Touch(1, 50, 100)
- t0.confidence = False
- r = uhdev.event([t0])
- events = uhdev.next_sync_events()
- self.debug_reports(r, uhdev, events)
- _slot = self.get_slot(uhdev, t0, 0)
- assert not events
- def test_confidence_multitouch(self):
- """
- Bring multiple fingers in contact with the tablet, some with the
- confidence bit set, and some without.
- Ensure that all confident touches are reported and that all non-
- confident touches are ignored.
- """
- uhdev = self.uhdev
- evdev = uhdev.get_evdev()
- touches = self.make_contacts(5)
- touches[0].confidence = False
- touches[2].confidence = False
- touches[4].confidence = False
- r = uhdev.event(touches)
- events = uhdev.next_sync_events()
- self.debug_reports(r, uhdev, events)
- assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
- self.assert_contacts(
- evdev,
- [
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=None),
- self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),
- self.ContactIds(contact_id=2, tracking_id=-1, slot_num=None),
- self.ContactIds(contact_id=3, tracking_id=1, slot_num=1),
- self.ContactIds(contact_id=4, tracking_id=-1, slot_num=None),
- ],
- )
- def confidence_change_assert_playback(self, uhdev, evdev, timeline):
- """
- Assert proper behavior of contacts that move and change tipswitch /
- confidence status over time.
- Given a `timeline` list of touch states to iterate over, verify
- that the contacts move and are reported as up/down as expected
- by the state of the tipswitch and confidence bits.
- """
- t = 0
- for state in timeline:
- touches = self.make_contacts(len(state), t)
- for item in zip(touches, state):
- item[0].tipswitch = item[1][1]
- item[0].confidence = item[1][2]
- r = uhdev.event(touches)
- events = uhdev.next_sync_events()
- self.debug_reports(r, uhdev, events)
- ids = [x[0] for x in state]
- self.assert_contacts(evdev, ids, t)
- t += 1
- def test_confidence_loss_a(self):
- """
- Transition a confident contact to a non-confident contact by
- first clearing the tipswitch.
- Ensure that the driver reports the transitioned contact as
- being removed and that other contacts continue to report
- normally. This mode of confidence loss is used by the
- DTH-2452.
- """
- uhdev = self.uhdev
- evdev = uhdev.get_evdev()
- self.confidence_change_assert_playback(
- uhdev,
- evdev,
- [
- # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
- # Both fingers confidently in contact
- [
- (
- self.ContactIds(contact_id=0, tracking_id=0, slot_num=0),
- True,
- True,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- # t=1: Contact 0 == !Down + confident; Contact 1 == Down + confident
- # First finger looses confidence and clears only the tipswitch flag
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
- False,
- True,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
- # First finger has lost confidence and has both flags cleared
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
- False,
- False,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
- # First finger has lost confidence and has both flags cleared
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
- False,
- False,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- ],
- )
- def test_confidence_loss_b(self):
- """
- Transition a confident contact to a non-confident contact by
- cleraing both tipswitch and confidence bits simultaneously.
- Ensure that the driver reports the transitioned contact as
- being removed and that other contacts continue to report
- normally. This mode of confidence loss is used by some
- AES devices.
- """
- uhdev = self.uhdev
- evdev = uhdev.get_evdev()
- self.confidence_change_assert_playback(
- uhdev,
- evdev,
- [
- # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
- # Both fingers confidently in contact
- [
- (
- self.ContactIds(contact_id=0, tracking_id=0, slot_num=0),
- True,
- True,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- # t=1: Contact 0 == !Down + !confident; Contact 1 == Down + confident
- # First finger looses confidence and has both flags cleared simultaneously
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
- False,
- False,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
- # First finger has lost confidence and has both flags cleared
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
- False,
- False,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
- # First finger has lost confidence and has both flags cleared
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
- False,
- False,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- ],
- )
- def test_confidence_loss_c(self):
- """
- Transition a confident contact to a non-confident contact by
- clearing only the confidence bit.
- Ensure that the driver reports the transitioned contact as
- being removed and that other contacts continue to report
- normally.
- """
- uhdev = self.uhdev
- evdev = uhdev.get_evdev()
- self.confidence_change_assert_playback(
- uhdev,
- evdev,
- [
- # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
- # Both fingers confidently in contact
- [
- (
- self.ContactIds(contact_id=0, tracking_id=0, slot_num=0),
- True,
- True,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
- # First finger looses confidence and clears only the confidence flag
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
- True,
- False,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
- # First finger has lost confidence and has both flags cleared
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
- False,
- False,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
- # First finger has lost confidence and has both flags cleared
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
- False,
- False,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- ],
- )
- def test_confidence_gain_a(self):
- """
- Transition a contact that was always non-confident to confident.
- Ensure that the confident contact is reported normally.
- """
- uhdev = self.uhdev
- evdev = uhdev.get_evdev()
- self.confidence_change_assert_playback(
- uhdev,
- evdev,
- [
- # t=0: Contact 0 == Down + !confident; Contact 1 == Down + confident
- # Only second finger is confidently in contact
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=None),
- True,
- False,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),
- True,
- True,
- ),
- ],
- # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
- # First finger gains confidence
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=None),
- True,
- False,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),
- True,
- True,
- ),
- ],
- # t=2: Contact 0 == Down + confident; Contact 1 == Down + confident
- # First finger remains confident
- [
- (
- self.ContactIds(contact_id=0, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),
- True,
- True,
- ),
- ],
- # t=3: Contact 0 == Down + confident; Contact 1 == Down + confident
- # First finger remains confident
- [
- (
- self.ContactIds(contact_id=0, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=0, slot_num=0),
- True,
- True,
- ),
- ],
- ],
- )
- def test_confidence_gain_b(self):
- """
- Transition a contact from non-confident to confident.
- Ensure that the confident contact is reported normally.
- """
- uhdev = self.uhdev
- evdev = uhdev.get_evdev()
- self.confidence_change_assert_playback(
- uhdev,
- evdev,
- [
- # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
- # First and second finger confidently in contact
- [
- (
- self.ContactIds(contact_id=0, tracking_id=0, slot_num=0),
- True,
- True,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
- # Firtst finger looses confidence
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
- True,
- False,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- # t=2: Contact 0 == Down + confident; Contact 1 == Down + confident
- # First finger gains confidence
- [
- (
- self.ContactIds(contact_id=0, tracking_id=2, slot_num=0),
- True,
- True,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- # t=3: Contact 0 == !Down + confident; Contact 1 == Down + confident
- # First finger goes up
- [
- (
- self.ContactIds(contact_id=0, tracking_id=-1, slot_num=0),
- False,
- True,
- ),
- (
- self.ContactIds(contact_id=1, tracking_id=1, slot_num=1),
- True,
- True,
- ),
- ],
- ],
- )
|