#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2021 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2021 Red Hat, Inc.
#
from .
import base
import copy
from enum
import Enum
from hidtools.util
import BusType
from .base
import HidBpf
import libevdev
import logging
import pytest
from typing
import Dict, List, Optional, Tuple
logger = logging.getLogger(
"hidtools.test.tablet")
class BtnTouch(Enum):
"""Represents whether the BTN_TOUCH event is set to True or False"""
DOWN =
True
UP =
False
class ToolType(Enum):
PEN = libevdev.EV_KEY.BTN_TOOL_PEN
RUBBER = libevdev.EV_KEY.BTN_TOOL_RUBBER
class BtnPressed(Enum):
"""Represents whether a button is pressed on the stylus"""
PRIMARY_PRESSED = libevdev.EV_KEY.BTN_STYLUS
SECONDARY_PRESSED = libevdev.EV_KEY.BTN_STYLUS2
THIRD_PRESSED = libevdev.EV_KEY.BTN_STYLUS3
class PenState(Enum):
"""Pen states according to Microsoft reference:
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
We extend it
with the various buttons when we need to check them.
"""
PEN_IS_OUT_OF_RANGE = BtnTouch.UP,
None,
False
PEN_IS_IN_RANGE = BtnTouch.UP, ToolType.PEN,
False
PEN_IS_IN_RANGE_WITH_BUTTON = BtnTouch.UP, ToolType.PEN,
True
PEN_IS_IN_CONTACT = BtnTouch.DOWN, ToolType.PEN,
False
PEN_IS_IN_CONTACT_WITH_BUTTON = BtnTouch.DOWN, ToolType.PEN,
True
PEN_IS_IN_RANGE_WITH_ERASING_INTENT = BtnTouch.UP, ToolType.RUBBER,
False
PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON = BtnTouch.UP, ToolType.RUBBER,
True
PEN_IS_ERASING = BtnTouch.DOWN, ToolType.RUBBER,
False
PEN_IS_ERASING_WITH_BUTTON = BtnTouch.DOWN, ToolType.RUBBER,
True
def __init__(
self, touch: BtnTouch, tool: Optional[ToolType], button: Optional[bool]
):
self.touch = touch
# type: ignore
self.tool = tool
# type: ignore
self.button = button
# type: ignore
@classmethod
def from_evdev(cls, evdev, test_button) ->
"PenState":
touch = BtnTouch(evdev.value[libevdev.EV_KEY.BTN_TOUCH])
tool =
None
button =
False
if (
evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
and not evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
):
tool = ToolType(libevdev.EV_KEY.BTN_TOOL_RUBBER)
elif (
evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
and not evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
):
tool = ToolType(libevdev.EV_KEY.BTN_TOOL_PEN)
elif (
evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
or evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
):
raise ValueError(
"2 tools are not allowed")
# we take only the provided button into account
if test_button
is not None:
button = bool(evdev.value[test_button.value])
# the kernel tends to insert an EV_SYN once removing the tool, so
# the button will be released after
if tool
is None:
button =
False
return cls((touch, tool, button))
# type: ignore
def apply(
self, events: List[libevdev.InputEvent], strict: bool, test_button: BtnPressed
) ->
"PenState":
if libevdev.EV_SYN.SYN_REPORT
in events:
raise ValueError(
"EV_SYN is in the event sequence")
touch = self.touch
touch_found =
False
tool = self.tool
tool_found =
False
button = self.button
button_found =
False
for ev
in events:
if ev == libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH):
if touch_found:
raise ValueError(f
"duplicated BTN_TOUCH in {events}")
touch_found =
True
touch = BtnTouch(ev.value)
elif ev
in (
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_RUBBER),
):
if tool_found:
raise ValueError(f
"duplicated BTN_TOOL_* in {events}")
tool_found =
True
tool = ToolType(ev.code)
if ev.value
else None
elif test_button
is not None and ev
in (test_button.value,):
if button_found:
raise ValueError(f
"duplicated BTN_STYLUS* in {events}")
button_found =
True
button = bool(ev.value)
# the kernel tends to insert an EV_SYN once removing the tool, so
# the button will be released after
if tool
is None:
button =
False
new_state = PenState((touch, tool, button))
# type: ignore
if strict:
assert (
new_state
in self.valid_transitions()
), f
"moving from {self} to {new_state} is forbidden"
else:
assert (
new_state
in self.historically_tolerated_transitions()
), f
"moving from {self} to {new_state} is forbidden"
return new_state
def valid_transitions(self) -> Tuple[
"PenState", ...]:
"""Following the state machine in the URL above.
Note that those transitions are
from the evdev point of view,
not HID
"""
if self == PenState.PEN_IS_OUT_OF_RANGE:
return (
PenState.PEN_IS_OUT_OF_RANGE,
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
PenState.PEN_IS_ERASING,
)
if self == PenState.PEN_IS_IN_RANGE:
return (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_OUT_OF_RANGE,
PenState.PEN_IS_IN_CONTACT,
)
if self == PenState.PEN_IS_IN_CONTACT:
return (
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
PenState.PEN_IS_IN_RANGE,
)
if self == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
return (
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_OUT_OF_RANGE,
PenState.PEN_IS_ERASING,
)
if self == PenState.PEN_IS_ERASING:
return (
PenState.PEN_IS_ERASING,
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
)
if self == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
return (
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_OUT_OF_RANGE,
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
)
if self == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
return (
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
)
return tuple()
def historically_tolerated_transitions(self) -> Tuple[
"PenState", ...]:
"""Following the state machine in the URL above, with a couple of addition
for skipping the in-range state, due to historical reasons.
Note that those transitions are
from the evdev point of view,
not HID
"""
if self == PenState.PEN_IS_OUT_OF_RANGE:
return (
PenState.PEN_IS_OUT_OF_RANGE,
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
PenState.PEN_IS_ERASING,
)
if self == PenState.PEN_IS_IN_RANGE:
return (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_OUT_OF_RANGE,
PenState.PEN_IS_IN_CONTACT,
)
if self == PenState.PEN_IS_IN_CONTACT:
return (
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_OUT_OF_RANGE,
)
if self == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
return (
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_OUT_OF_RANGE,
PenState.PEN_IS_ERASING,
)
if self == PenState.PEN_IS_ERASING:
return (
PenState.PEN_IS_ERASING,
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_OUT_OF_RANGE,
)
if self == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
return (
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_OUT_OF_RANGE,
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
)
if self == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
return (
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_OUT_OF_RANGE,
)
return tuple()
@staticmethod
def legal_transitions() -> Dict[str, Tuple[
"PenState", ...]]:
"""This is the first half of the Windows Pen Implementation state machine:
we don
't have Invert nor Erase bits, so just move in/out-of-range or proximity.
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
"""
return {
"in-range": (PenState.PEN_IS_IN_RANGE,),
"in-range -> out-of-range": (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_OUT_OF_RANGE,
),
"in-range -> touch": (PenState.PEN_IS_IN_RANGE, PenState.PEN_IS_IN_CONTACT),
"in-range -> touch -> release": (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_RANGE,
),
"in-range -> touch -> release -> out-of-range": (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_OUT_OF_RANGE,
),
}
@staticmethod
def legal_transitions_with_invert() -> Dict[str, Tuple[
"PenState", ...]]:
"""This is the second half of the Windows Pen Implementation state machine:
we now have Invert
and Erase bits, so move
in/out
or proximity
with the intend
to erase.
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
"""
return {
"hover-erasing": (PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,),
"hover-erasing -> out-of-range": (
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_OUT_OF_RANGE,
),
"hover-erasing -> erase": (
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_ERASING,
),
"hover-erasing -> erase -> release": (
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_ERASING,
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
),
"hover-erasing -> erase -> release -> out-of-range": (
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_ERASING,
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_OUT_OF_RANGE,
),
"hover-erasing -> in-range": (
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_IN_RANGE,
),
"in-range -> hover-erasing": (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
),
}
@staticmethod
def legal_transitions_with_button() -> Dict[str, Tuple[
"PenState", ...]]:
"""We revisit the Windows Pen Implementation state machine:
we now have a button.
"""
return {
"hover-button": (PenState.PEN_IS_IN_RANGE_WITH_BUTTON,),
"hover-button -> out-of-range": (
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_OUT_OF_RANGE,
),
"in-range -> button-press": (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
),
"in-range -> button-press -> button-release": (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_IN_RANGE,
),
"in-range -> touch -> button-press -> button-release": (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
PenState.PEN_IS_IN_CONTACT,
),
"in-range -> touch -> button-press -> release -> button-release": (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_IN_RANGE,
),
"in-range -> button-press -> touch -> release -> button-release": (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_IN_RANGE,
),
"in-range -> button-press -> touch -> button-release -> release": (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_RANGE,
),
}
@staticmethod
def tolerated_transitions() -> Dict[str, Tuple[
"PenState", ...]]:
"""This is not adhering to the Windows Pen Implementation state machine
but we should expect the kernel to behave properly, mostly
for historical
reasons.
"""
return {
"direct-in-contact": (PenState.PEN_IS_IN_CONTACT,),
"direct-in-contact -> out-of-range": (
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_OUT_OF_RANGE,
),
}
@staticmethod
def tolerated_transitions_with_invert() -> Dict[str, Tuple[
"PenState", ...]]:
"""This is the second half of the Windows Pen Implementation state machine:
we now have Invert
and Erase bits, so move
in/out
or proximity
with the intend
to erase.
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
"""
return {
"direct-erase": (PenState.PEN_IS_ERASING,),
"direct-erase -> out-of-range": (
PenState.PEN_IS_ERASING,
PenState.PEN_IS_OUT_OF_RANGE,
),
}
@staticmethod
def broken_transitions() -> Dict[str, Tuple[
"PenState", ...]]:
"""Those tests are definitely not part of the Windows specification.
However, a half broken device might export those transitions.
For example, a pen that has the eraser button might wobble between
touching
and erasing
if the tablet doesn
't enforce the Windows
state machine.
"""
return {
"in-range -> touch -> erase -> hover-erase": (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_ERASING,
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
),
"in-range -> erase -> hover-erase": (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_ERASING,
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
),
"hover-erase -> erase -> touch -> in-range": (
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_ERASING,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_RANGE,
),
"hover-erase -> touch -> in-range": (
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_RANGE,
),
"touch -> erase -> touch -> erase": (
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_ERASING,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_ERASING,
),
}
class Pen(object):
def __init__(self, x, y):
self.x = x
self.y = y
self.tipswitch =
False
self.tippressure = 15
self.azimuth = 0
self.inrange =
False
self.width = 10
self.height = 10
self.barrelswitch =
False
self.secondarybarrelswitch =
False
self.invert =
False
self.eraser =
False
self.xtilt = 1
self.ytilt = 1
self.twist = 1
self._old_values =
None
self.current_state =
None
def restore(self):
if self._old_values
is not None:
for i
in [
"x",
"y",
"tippressure",
"azimuth",
"width",
"height",
"twist",
"xtilt",
"ytilt",
]:
setattr(self, i, getattr(self._old_values, i))
def backup(self):
self._old_values = copy.copy(self)
def __assert_axis(self, evdev, axis, value):
if (
axis == libevdev.EV_KEY.BTN_TOOL_RUBBER
and evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
is None
):
return
assert (
evdev.value[axis] == value
), f
"assert evdev.value[{axis}] ({evdev.value[axis]}) != {value}"
def assert_expected_input_events(self, evdev, button):
assert evdev.value[libevdev.EV_ABS.ABS_X] == self.x
assert evdev.value[libevdev.EV_ABS.ABS_Y] == self.y
# assert no other buttons than the tested ones are set
buttons = [
BtnPressed.PRIMARY_PRESSED,
BtnPressed.SECONDARY_PRESSED,
BtnPressed.THIRD_PRESSED,
]
if button
is not None:
buttons.remove(button)
for b
in buttons:
assert evdev.value[b.value]
is None or evdev.value[b.value] ==
False
assert self.current_state == PenState.from_evdev(evdev, button)
class PenDigitizer(base.UHIDTestDevice):
def __init__(
self,
name,
rdesc_str=
None,
rdesc=
None,
application=
"Pen",
physical=
"Stylus",
input_info=(BusType.USB, 1, 2),
evdev_name_suffix=
None,
):
super().__init__(name, application, rdesc_str, rdesc, input_info)
self.physical = physical
self.cur_application = application
if evdev_name_suffix
is not None:
self.name += evdev_name_suffix
self.fields = []
for r
in self.parsed_rdesc.input_reports.values():
if r.application_name == self.application:
physicals = [f.physical_name
for f
in r]
if self.physical
not in physicals
and None not in physicals:
continue
self.fields = [f.usage_name
for f
in r]
def move_to(self, pen, state, button):
# fill in the previous values
if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
pen.restore()
print(f
"\n *** pen is moving to {state} ***")
if state == PenState.PEN_IS_OUT_OF_RANGE:
pen.backup()
pen.x = 0
pen.y = 0
pen.tipswitch =
False
pen.tippressure = 0
pen.azimuth = 0
pen.inrange =
False
pen.width = 0
pen.height = 0
pen.invert =
False
pen.eraser =
False
pen.xtilt = 0
pen.ytilt = 0
pen.twist = 0
pen.barrelswitch =
False
pen.secondarybarrelswitch =
False
elif state == PenState.PEN_IS_IN_RANGE:
pen.tipswitch =
False
pen.inrange =
True
pen.invert =
False
pen.eraser =
False
pen.barrelswitch =
False
pen.secondarybarrelswitch =
False
elif state == PenState.PEN_IS_IN_CONTACT:
pen.tipswitch =
True
pen.inrange =
True
pen.invert =
False
pen.eraser =
False
pen.barrelswitch =
False
pen.secondarybarrelswitch =
False
elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
pen.tipswitch =
False
pen.inrange =
True
pen.invert =
False
pen.eraser =
False
assert button
is not None
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
pen.secondarybarrelswitch = button == BtnPressed.SECONDARY_PRESSED
elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
pen.tipswitch =
True
pen.inrange =
True
pen.invert =
False
pen.eraser =
False
assert button
is not None
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
pen.secondarybarrelswitch = button == BtnPressed.SECONDARY_PRESSED
elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
pen.tipswitch =
False
pen.inrange =
True
pen.invert =
True
pen.eraser =
False
pen.barrelswitch =
False
pen.secondarybarrelswitch =
False
elif state == PenState.PEN_IS_ERASING:
pen.tipswitch =
False
pen.inrange =
True
pen.invert =
False
pen.eraser =
True
pen.barrelswitch =
False
pen.secondarybarrelswitch =
False
pen.current_state = state
def event(self, pen, button):
rs = []
r = self.create_report(application=self.cur_application, data=pen)
self.call_input_event(r)
rs.append(r)
return rs
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, [])
return (1, [])
def set_report(self, req, rnum, rtype, data):
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
return 1
class BaseTest:
class TestTablet(base.BaseTestCase.TestUhid):
def create_device(self):
raise Exception(
"please reimplement me in subclasses")
def post(self, uhdev, pen, test_button):
r = uhdev.event(pen, test_button)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
return events
def validate_transitions(
self, from_state, pen, evdev, events, allow_intermediate_states, button
):
# check that the final state is correct
pen.assert_expected_input_events(evdev, button)
state = from_state
# check that the transitions are valid
sync_events = []
while libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT)
in events:
# split the first EV_SYN from the list
idx = events.index(libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT))
sync_events = events[:idx]
events = events[idx + 1 :]
# now check for a valid transition
state = state.apply(sync_events,
not allow_intermediate_states, button)
if events:
state = state.apply(sync_events,
not allow_intermediate_states, button)
def _test_states(
self, state_list, scribble, allow_intermediate_states, button=
None
):
"""Internal method to test against a list of
transition between states.
state_list
is a list of PenState objects
scribble
is a boolean which tells
if we need
to wobble a little the X,Y coordinates of the pen
between each state transition.
"""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
cur_state = PenState.PEN_IS_OUT_OF_RANGE
p = Pen(50, 60)
uhdev.move_to(p, PenState.PEN_IS_OUT_OF_RANGE, button)
events = self.post(uhdev, p, button)
self.validate_transitions(
cur_state, p, evdev, events, allow_intermediate_states, button
)
cur_state = p.current_state
for state
in state_list:
if scribble
and cur_state != PenState.PEN_IS_OUT_OF_RANGE:
p.x += 1
p.y -= 1
events = self.post(uhdev, p, button)
self.validate_transitions(
cur_state, p, evdev, events, allow_intermediate_states, button
)
assert len(events) >= 3
# X, Y, SYN
uhdev.move_to(p, state, button)
if scribble
and state != PenState.PEN_IS_OUT_OF_RANGE:
p.x += 1
p.y -= 1
events = self.post(uhdev, p, button)
self.validate_transitions(
cur_state, p, evdev, events, allow_intermediate_states, button
)
cur_state = p.current_state
@pytest.mark.parametrize(
"scribble", [
True,
False], ids=[
"scribble",
"static"])
@pytest.mark.parametrize(
"state_list",
[pytest.param(v, id=k)
for k, v
in PenState.legal_transitions().items()],
)
def test_valid_pen_states(self, state_list, scribble):
"""This is the first half of the Windows Pen Implementation state machine:
we don
't have Invert nor Erase bits, so just move in/out-of-range or proximity.
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
"""
self._test_states(state_list, scribble, allow_intermediate_states=
False)
@pytest.mark.parametrize(
"scribble", [
True,
False], ids=[
"scribble",
"static"])
@pytest.mark.parametrize(
"state_list",
[
pytest.param(v, id=k)
for k, v
in PenState.tolerated_transitions().items()
],
)
def test_tolerated_pen_states(self, state_list, scribble):
"""This is not adhering to the Windows Pen Implementation state machine
but we should expect the kernel to behave properly, mostly
for historical
reasons.
"""
self._test_states(state_list, scribble, allow_intermediate_states=
True)
@pytest.mark.skip_if_uhdev(
lambda uhdev:
"Barrel Switch" not in uhdev.fields,
"Device not compatible, missing Barrel Switch usage",
)
@pytest.mark.parametrize(
"scribble", [
True,
False], ids=[
"scribble",
"static"])
@pytest.mark.parametrize(
"state_list",
[
pytest.param(v, id=k)
for k, v
in PenState.legal_transitions_with_button().items()
],
)
def test_valid_primary_button_pen_states(self, state_list, scribble):
"""Rework the transition state machine by adding the primary button."""
self._test_states(
state_list,
scribble,
allow_intermediate_states=
False,
button=BtnPressed.PRIMARY_PRESSED,
)
@pytest.mark.skip_if_uhdev(
lambda uhdev:
"Secondary Barrel Switch" not in uhdev.fields,
"Device not compatible, missing Secondary Barrel Switch usage",
)
@pytest.mark.parametrize(
"scribble", [
True,
False], ids=[
"scribble",
"static"])
@pytest.mark.parametrize(
"state_list",
[
pytest.param(v, id=k)
for k, v
in PenState.legal_transitions_with_button().items()
],
)
def test_valid_secondary_button_pen_states(self, state_list, scribble):
"""Rework the transition state machine by adding the secondary button."""
self._test_states(
state_list,
scribble,
allow_intermediate_states=
False,
button=BtnPressed.SECONDARY_PRESSED,
)
@pytest.mark.skip_if_uhdev(
lambda uhdev:
"Third Barrel Switch" not in uhdev.fields,
"Device not compatible, missing Third Barrel Switch usage",
)
@pytest.mark.parametrize(
"scribble", [
True,
False], ids=[
"scribble",
"static"])
@pytest.mark.parametrize(
"state_list",
[
pytest.param(v, id=k)
for k, v
in PenState.legal_transitions_with_button().items()
],
)
def test_valid_third_button_pen_states(self, state_list, scribble):
"""Rework the transition state machine by adding the secondary button."""
self._test_states(
state_list,
scribble,
allow_intermediate_states=
False,
button=BtnPressed.THIRD_PRESSED,
)
@pytest.mark.skip_if_uhdev(
lambda uhdev:
"Invert" not in uhdev.fields,
"Device not compatible, missing Invert usage",
)
@pytest.mark.parametrize(
"scribble", [
True,
False], ids=[
"scribble",
"static"])
@pytest.mark.parametrize(
"state_list",
[
pytest.param(v, id=k)
for k, v
in PenState.legal_transitions_with_invert().items()
],
)
def test_valid_invert_pen_states(self, state_list, scribble):
"""This is the second half of the Windows Pen Implementation state machine:
we now have Invert
and Erase bits, so move
in/out
or proximity
with the intend
to erase.
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
"""
self._test_states(state_list, scribble, allow_intermediate_states=
False)
@pytest.mark.skip_if_uhdev(
lambda uhdev:
"Invert" not in uhdev.fields,
"Device not compatible, missing Invert usage",
)
@pytest.mark.parametrize(
"scribble", [
True,
False], ids=[
"scribble",
"static"])
@pytest.mark.parametrize(
"state_list",
[
pytest.param(v, id=k)
for k, v
in PenState.tolerated_transitions_with_invert().items()
],
)
def test_tolerated_invert_pen_states(self, state_list, scribble):
"""This is the second half of the Windows Pen Implementation state machine:
we now have Invert
and Erase bits, so move
in/out
or proximity
with the intend
to erase.
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
"""
self._test_states(state_list, scribble, allow_intermediate_states=
True)
@pytest.mark.skip_if_uhdev(
lambda uhdev:
"Invert" not in uhdev.fields,
"Device not compatible, missing Invert usage",
)
@pytest.mark.parametrize(
"scribble", [
True,
False], ids=[
"scribble",
"static"])
@pytest.mark.parametrize(
"state_list",
[pytest.param(v, id=k)
for k, v
in PenState.broken_transitions().items()],
)
def test_tolerated_broken_pen_states(self, state_list, scribble):
"""Those tests are definitely not part of the Windows specification.
However, a half broken device might export those transitions.
For example, a pen that has the eraser button might wobble between
touching
and erasing
if the tablet doesn
't enforce the Windows
state machine.
"""
self._test_states(state_list, scribble, allow_intermediate_states=
True)
class GXTP_pen(PenDigitizer):
def event(self, pen, test_button):
if not hasattr(self,
"prev_tip_state"):
self.prev_tip_state =
False
internal_pen = copy.copy(pen)
# bug in the controller: when the pen touches the
# surface, in-range stays to 1, but when
# the pen moves in-range gets reverted to 0
if pen.tipswitch
and self.prev_tip_state:
internal_pen.inrange =
False
self.prev_tip_state = pen.tipswitch
# another bug in the controller: when the pen is
# inverted, invert is set to 1, but as soon as
# the pen touches the surface, eraser is correctly
# set to 1 but invert is released
if pen.eraser:
internal_pen.invert =
False
return super().event(internal_pen, test_button)
class USIPen(PenDigitizer):
pass
class XPPen_ArtistPro16Gen2_28bd_095b(PenDigitizer):
"""
Pen
with two buttons
and a rubber end, but which reports
the second button
as an eraser
"""
def __init__(
self,
name,
rdesc_str=
None,
rdesc=
None,
application=
"Pen",
physical=
"Stylus",
input_info=(BusType.USB, 0x28BD, 0x095B),
evdev_name_suffix=
None,
):
super().__init__(
name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix
)
self.fields.append(
"Secondary Barrel Switch")
def move_to(self, pen, state, button):
# fill in the previous values
if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
pen.restore()
print(f
"\n *** pen is moving to {state} ***")
if state == PenState.PEN_IS_OUT_OF_RANGE:
pen.backup()
pen.x = 0
pen.y = 0
pen.tipswitch =
False
pen.tippressure = 0
pen.azimuth = 0
pen.inrange =
False
pen.width = 0
pen.height = 0
pen.invert =
False
pen.eraser =
False
pen.xtilt = 0
pen.ytilt = 0
pen.twist = 0
pen.barrelswitch =
False
elif state == PenState.PEN_IS_IN_RANGE:
pen.tipswitch =
False
pen.inrange =
True
pen.invert =
False
pen.eraser =
False
pen.barrelswitch =
False
elif state == PenState.PEN_IS_IN_CONTACT:
pen.tipswitch =
True
pen.inrange =
True
pen.invert =
False
pen.eraser =
False
pen.barrelswitch =
False
elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
pen.tipswitch =
False
pen.inrange =
True
pen.invert =
False
assert button
is not None
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
pen.eraser = button == BtnPressed.SECONDARY_PRESSED
elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
pen.tipswitch =
True
pen.inrange =
True
pen.invert =
False
assert button
is not None
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
pen.eraser = button == BtnPressed.SECONDARY_PRESSED
elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
pen.tipswitch =
False
pen.inrange =
True
pen.invert =
True
pen.eraser =
False
pen.barrelswitch =
False
elif state == PenState.PEN_IS_ERASING:
pen.tipswitch =
True
pen.inrange =
True
pen.invert =
True
pen.eraser =
False
pen.barrelswitch =
False
pen.current_state = state
def event(self, pen, test_button):
import math
pen_copy = copy.copy(pen)
width = 13.567
height = 8.480
tip_height = 0.055677699
hx = tip_height * (32767 / width)
hy = tip_height * (32767 / height)
if pen_copy.xtilt != 0:
pen_copy.x += round(hx * math.sin(math.radians(pen_copy.xtilt)))
if pen_copy.ytilt != 0:
pen_copy.y += round(hy * math.sin(math.radians(pen_copy.ytilt)))
return super().event(pen_copy, test_button)
class XPPen_Artist24_28bd_093a(PenDigitizer):
"""
Pen that reports secondary barrel switch through eraser
"""
def __init__(
self,
name,
rdesc_str=
None,
rdesc=
None,
application=
"Pen",
physical=
"Stylus",
input_info=(BusType.USB, 0x28BD, 0x093A),
evdev_name_suffix=
None,
):
super().__init__(
name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix
)
self.fields.append(
"Secondary Barrel Switch")
self.previous_state = PenState.PEN_IS_OUT_OF_RANGE
def move_to(self, pen, state, button, debug=
True):
# fill in the previous values
if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
pen.restore()
if debug:
print(f
"\n *** pen is moving to {state} ***")
if state == PenState.PEN_IS_OUT_OF_RANGE:
pen.backup()
pen.tipswitch =
False
pen.tippressure = 0
pen.azimuth = 0
pen.inrange =
False
pen.width = 0
pen.height = 0
pen.invert =
False
pen.eraser =
False
pen.xtilt = 0
pen.ytilt = 0
pen.twist = 0
pen.barrelswitch =
False
elif state == PenState.PEN_IS_IN_RANGE:
pen.tipswitch =
False
pen.inrange =
True
pen.invert =
False
pen.eraser =
False
pen.barrelswitch =
False
elif state == PenState.PEN_IS_IN_CONTACT:
pen.tipswitch =
True
pen.inrange =
True
pen.invert =
False
pen.eraser =
False
pen.barrelswitch =
False
elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
pen.tipswitch =
False
pen.inrange =
True
pen.invert =
False
assert button
is not None
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
pen.eraser = button == BtnPressed.SECONDARY_PRESSED
elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
pen.tipswitch =
True
pen.inrange =
True
pen.invert =
False
assert button
is not None
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
pen.eraser = button == BtnPressed.SECONDARY_PRESSED
pen.current_state = state
def send_intermediate_state(self, pen, state, button):
intermediate_pen = copy.copy(pen)
self.move_to(intermediate_pen, state, button, debug=
False)
return super().event(intermediate_pen, button)
def event(self, pen, button):
rs = []
# the pen reliably sends in-range events in a normal case (non emulation of eraser mode)
if self.previous_state == PenState.PEN_IS_IN_CONTACT:
if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
rs.extend(
self.send_intermediate_state(pen, PenState.PEN_IS_IN_RANGE, button)
)
if button == BtnPressed.SECONDARY_PRESSED:
if self.previous_state == PenState.PEN_IS_IN_RANGE:
if pen.current_state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
rs.extend(
self.send_intermediate_state(
pen, PenState.PEN_IS_OUT_OF_RANGE, button
)
)
if self.previous_state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
if pen.current_state == PenState.PEN_IS_IN_RANGE:
rs.extend(
self.send_intermediate_state(
pen, PenState.PEN_IS_OUT_OF_RANGE, button
)
)
if self.previous_state == PenState.PEN_IS_IN_CONTACT:
if pen.current_state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
rs.extend(
self.send_intermediate_state(
pen, PenState.PEN_IS_OUT_OF_RANGE, button
)
)
rs.extend(
self.send_intermediate_state(
pen, PenState.PEN_IS_IN_RANGE_WITH_BUTTON, button
)
)
if self.previous_state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
if pen.current_state == PenState.PEN_IS_IN_CONTACT:
rs.extend(
self.send_intermediate_state(
pen, PenState.PEN_IS_OUT_OF_RANGE, button
)
)
rs.extend(
self.send_intermediate_state(
pen, PenState.PEN_IS_IN_RANGE, button
)
)
rs.extend(super().event(pen, button))
self.previous_state = pen.current_state
return rs
class Huion_Kamvas_Pro_19_256c_006b(PenDigitizer):
"""
Pen that reports secondary barrel switch through secondary TipSwtich
and 3rd button through Invert
"""
def __init__(
self,
name,
rdesc_str=
None,
rdesc=
None,
application=
"Stylus",
physical=
None,
input_info=(BusType.USB, 0x256C, 0x006B),
evdev_name_suffix=
None,
):
super().__init__(
name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix
)
self.fields.append(
"Secondary Barrel Switch")
self.fields.append(
"Third Barrel Switch")
self.previous_state = PenState.PEN_IS_OUT_OF_RANGE
def move_to(self, pen, state, button, debug=
True):
# fill in the previous values
if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
pen.restore()
if debug:
print(f
"\n *** pen is moving to {state} ***")
if state == PenState.PEN_IS_OUT_OF_RANGE:
pen.backup()
pen.tipswitch =
False
pen.tippressure = 0
pen.azimuth = 0
pen.inrange =
False
pen.width = 0
pen.height = 0
pen.invert =
False
pen.eraser =
False
pen.xtilt = 0
pen.ytilt = 0
pen.twist = 0
pen.barrelswitch =
False
pen.secondarytipswitch =
False
elif state == PenState.PEN_IS_IN_RANGE:
pen.tipswitch =
False
pen.inrange =
True
pen.invert =
False
pen.eraser =
False
pen.barrelswitch =
False
pen.secondarytipswitch =
False
elif state == PenState.PEN_IS_IN_CONTACT:
pen.tipswitch =
True
pen.inrange =
True
pen.invert =
False
pen.eraser =
False
pen.barrelswitch =
False
pen.secondarytipswitch =
False
elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
pen.tipswitch =
False
pen.inrange =
True
pen.eraser =
False
assert button
is not None
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
pen.secondarytipswitch = button == BtnPressed.SECONDARY_PRESSED
pen.invert = button == BtnPressed.THIRD_PRESSED
elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
pen.tipswitch =
True
pen.inrange =
True
pen.eraser =
False
assert button
is not None
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
pen.secondarytipswitch = button == BtnPressed.SECONDARY_PRESSED
pen.invert = button == BtnPressed.THIRD_PRESSED
elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
pen.tipswitch =
False
pen.inrange =
True
pen.invert =
True
pen.eraser =
False
pen.barrelswitch =
False
pen.secondarytipswitch =
False
elif state == PenState.PEN_IS_ERASING:
pen.tipswitch =
False
pen.inrange =
True
pen.invert =
False
pen.eraser =
True
pen.barrelswitch =
False
pen.secondarytipswitch =
False
pen.current_state = state
def call_input_event(self, report):
if report[0] == 0x0A:
# ensures the original second Eraser usage is null
report[1] &= 0xDF
# ensures the original last bit is equal to bit 6 (In Range)
if report[1] & 0x40:
report[1] |= 0x80
super().call_input_event(report)
def send_intermediate_state(self, pen, state, test_button):
intermediate_pen = copy.copy(pen)
self.move_to(intermediate_pen, state, test_button, debug=
False)
return super().event(intermediate_pen, test_button)
def event(self, pen, button):
rs = []
# it's not possible to go between eraser mode or not without
# going out-of-prox: the eraser mode is activated by presenting
# the tail of the pen
if self.previous_state
in (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
)
and pen.current_state
in (
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON,
PenState.PEN_IS_ERASING,
PenState.PEN_IS_ERASING_WITH_BUTTON,
):
rs.extend(
self.send_intermediate_state(pen, PenState.PEN_IS_OUT_OF_RANGE, button)
)
# same than above except from eraser to normal
if self.previous_state
in (
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON,
PenState.PEN_IS_ERASING,
PenState.PEN_IS_ERASING_WITH_BUTTON,
)
and pen.current_state
in (
PenState.PEN_IS_IN_RANGE,
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
PenState.PEN_IS_IN_CONTACT,
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
):
rs.extend(
self.send_intermediate_state(pen, PenState.PEN_IS_OUT_OF_RANGE, button)
)
if self.previous_state == PenState.PEN_IS_OUT_OF_RANGE:
if pen.current_state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
rs.extend(
self.send_intermediate_state(pen, PenState.PEN_IS_IN_RANGE, button)
)
rs.extend(super().event(pen, button))
self.previous_state = pen.current_state
return rs
################################################################################
#
# Windows 7 compatible devices
#
################################################################################
# class TestEgalax_capacitive_0eef_7224(BaseTest.TestTablet):
# def create_device(self):
# return PenDigitizer('uhid test egalax-capacitive_0eef_7224',
# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
# input_info=(BusType.USB, 0x0eef, 0x7224),
# evdev_name_suffix=' Touchscreen')
#
#
# class TestEgalax_capacitive_0eef_72fa(BaseTest.TestTablet):
# def create_device(self):
# return PenDigitizer('uhid test egalax-capacitive_0eef_72fa',
# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 72 22 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 87 13 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 72 22 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 87 13 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
# input_info=(BusType.USB, 0x0eef, 0x72fa),
# evdev_name_suffix=' Touchscreen')
#
#
# class TestEgalax_capacitive_0eef_7336(BaseTest.TestTablet):
# def create_device(self):
# return PenDigitizer('uhid test egalax-capacitive_0eef_7336',
# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 c1 20 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c2 18 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 c1 20 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c2 18 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
# input_info=(BusType.USB, 0x0eef, 0x7336),
# evdev_name_suffix=' Touchscreen')
#
#
# class TestEgalax_capacitive_0eef_7337(BaseTest.TestTablet):
# def create_device(self):
# return PenDigitizer('uhid test egalax-capacitive_0eef_7337',
# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 ae 17 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c3 0e 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 ae 17 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c3 0e 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
# input_info=(BusType.USB, 0x0eef, 0x7337),
# evdev_name_suffix=' Touchscreen')
#
#
# class TestEgalax_capacitive_0eef_7349(BaseTest.TestTablet):
# def create_device(self):
# return PenDigitizer('uhid test egalax-capacitive_0eef_7349',
# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
# input_info=(BusType.USB, 0x0eef, 0x7349),
# evdev_name_suffix=' Touchscreen')
#
#
# class TestEgalax_capacitive_0eef_73f4(BaseTest.TestTablet):
# def create_device(self):
# return PenDigitizer('uhid test egalax-capacitive_0eef_73f4',
# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 96 4e 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 23 2c 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 96 4e 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 23 2c 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
# input_info=(BusType.USB, 0x0eef, 0x73f4),
# evdev_name_suffix=' Touchscreen')
#
# bogus: BTN_TOOL_PEN is not emitted
# class TestIrtouch_6615_0070(BaseTest.TestTablet):
# def create_device(self):
# return PenDigitizer('uhid test irtouch_6615_0070',
# rdesc='05 01 09 02 a1 01 85 10 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 09 30 09 31 15 00 26 ff 7f 75 10 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 30 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 05 0d 09 54 15 00 26 02 00 75 08 95 01 81 02 85 03 09 55 15 00 26 ff 00 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 02 09 52 09 53 15 00 26 ff 00 75 08 95 02 b1 02 c0 05 0d 09 02 a1 01 85 20 09 20 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 85 01 06 00 ff 09 01 75 08 95 01 b1 02 c0 c0',
# input_info=(BusType.USB, 0x6615, 0x0070))
class TestNexio_1870_0100(BaseTest.TestTablet):
def create_device(self):
return PenDigitizer(
"uhid test nexio_1870_0100",
rdesc=
"05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 02 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 40 81 00 19 01 29 40 91 00 c0",
input_info=(BusType.USB, 0x1870, 0x0100),
)
class TestNexio_1870_010d(BaseTest.TestTablet):
def create_device(self):
return PenDigitizer(
"uhid test nexio_1870_010d",
rdesc=
"05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0",
input_info=(BusType.USB, 0x1870, 0x010D),
)
class TestNexio_1870_0119(BaseTest.TestTablet):
def create_device(self):
return PenDigitizer(
"uhid test nexio_1870_0119",
rdesc=
"05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0",
input_info=(BusType.USB, 0x1870, 0x0119),
)
################################################################################
#
# Windows 8 compatible devices
#
################################################################################
# bogus: application is 'undefined'
# class Testatmel_03eb_8409(BaseTest.TestTablet):
# def create_device(self):
--> --------------------
--> maximum size reached
--> --------------------