Skip to content

Commit

Permalink
selftests/hid: tablets: move the transitions to PenState
Browse files Browse the repository at this point in the history
Those transitions have nothing to do with `Pen`, so migrate them to
`PenState`.

The hidden agenda is to remove `Pen` and integrate it into `PenDigitizer`
so that we can tweak the events in each state to emulate firmware bugs.

Reviewed-by: Peter Hutterer <peter.hutterer@who-t.net>
Acked-by: Jiri Kosina <jkosina@suse.com>
Link: https://lore.kernel.org/r/20231206-wip-selftests-v2-5-c0350c2f5986@kernel.org
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
  • Loading branch information
Benjamin Tissoires committed Dec 7, 2023
1 parent b5edacf commit d52f520
Showing 1 changed file with 109 additions and 106 deletions.
215 changes: 109 additions & 106 deletions tools/testing/selftests/hid/tests/test_tablet.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,104 +132,8 @@ def valid_transitions(self) -> Tuple["PenState", ...]:

return tuple()


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.invert = False
self.eraser = False
self.x_tilt = 0
self.y_tilt = 0
self.twist = 0
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",
"x_tilt",
"y_tilt",
]:
setattr(self, i, getattr(self._old_values, i))

def move_to(self, state):
# fill in the previous values
if self.current_state == PenState.PEN_IS_OUT_OF_RANGE:
self._restore()

print(f"\n *** pen is moving to {state} ***")

if state == PenState.PEN_IS_OUT_OF_RANGE:
self._old_values = copy.copy(self)
self.x = 0
self.y = 0
self.tipswitch = False
self.tippressure = 0
self.azimuth = 0
self.inrange = False
self.width = 0
self.height = 0
self.invert = False
self.eraser = False
self.x_tilt = 0
self.y_tilt = 0
self.twist = 0
elif state == PenState.PEN_IS_IN_RANGE:
self.tipswitch = False
self.inrange = True
self.invert = False
self.eraser = False
elif state == PenState.PEN_IS_IN_CONTACT:
self.tipswitch = True
self.inrange = True
self.invert = False
self.eraser = False
elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
self.tipswitch = False
self.inrange = True
self.invert = True
self.eraser = False
elif state == PenState.PEN_IS_ERASING:
self.tipswitch = False
self.inrange = True
self.invert = True
self.eraser = True

self.current_state = state

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):
assert evdev.value[libevdev.EV_ABS.ABS_X] == self.x
assert evdev.value[libevdev.EV_ABS.ABS_Y] == self.y
assert self.current_state == PenState.from_evdev(evdev)

@staticmethod
def legal_transitions() -> Dict[str, Tuple[PenState, ...]]:
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
Expand All @@ -255,7 +159,7 @@ def legal_transitions() -> Dict[str, Tuple[PenState, ...]]:
}

@staticmethod
def legal_transitions_with_invert() -> Dict[str, Tuple[PenState, ...]]:
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.
Expand Down Expand Up @@ -293,7 +197,7 @@ def legal_transitions_with_invert() -> Dict[str, Tuple[PenState, ...]]:
}

@staticmethod
def tolerated_transitions() -> Dict[str, Tuple[PenState, ...]]:
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."""
Expand All @@ -306,7 +210,7 @@ def tolerated_transitions() -> Dict[str, Tuple[PenState, ...]]:
}

@staticmethod
def tolerated_transitions_with_invert() -> Dict[str, Tuple[PenState, ...]]:
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.
Expand All @@ -321,7 +225,7 @@ def tolerated_transitions_with_invert() -> Dict[str, Tuple[PenState, ...]]:
}

@staticmethod
def broken_transitions() -> Dict[str, Tuple[PenState, ...]]:
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
Expand Down Expand Up @@ -359,6 +263,102 @@ def broken_transitions() -> Dict[str, Tuple[PenState, ...]]:
}


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.invert = False
self.eraser = False
self.x_tilt = 0
self.y_tilt = 0
self.twist = 0
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",
"x_tilt",
"y_tilt",
]:
setattr(self, i, getattr(self._old_values, i))

def move_to(self, state):
# fill in the previous values
if self.current_state == PenState.PEN_IS_OUT_OF_RANGE:
self._restore()

print(f"\n *** pen is moving to {state} ***")

if state == PenState.PEN_IS_OUT_OF_RANGE:
self._old_values = copy.copy(self)
self.x = 0
self.y = 0
self.tipswitch = False
self.tippressure = 0
self.azimuth = 0
self.inrange = False
self.width = 0
self.height = 0
self.invert = False
self.eraser = False
self.x_tilt = 0
self.y_tilt = 0
self.twist = 0
elif state == PenState.PEN_IS_IN_RANGE:
self.tipswitch = False
self.inrange = True
self.invert = False
self.eraser = False
elif state == PenState.PEN_IS_IN_CONTACT:
self.tipswitch = True
self.inrange = True
self.invert = False
self.eraser = False
elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
self.tipswitch = False
self.inrange = True
self.invert = True
self.eraser = False
elif state == PenState.PEN_IS_ERASING:
self.tipswitch = False
self.inrange = True
self.invert = True
self.eraser = True

self.current_state = state

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):
assert evdev.value[libevdev.EV_ABS.ABS_X] == self.x
assert evdev.value[libevdev.EV_ABS.ABS_Y] == self.y
assert self.current_state == PenState.from_evdev(evdev)


class PenDigitizer(base.UHIDTestDevice):
def __init__(
self,
Expand Down Expand Up @@ -486,7 +486,7 @@ def _test_states(self, state_list, scribble):
@pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
@pytest.mark.parametrize(
"state_list",
[pytest.param(v, id=k) for k, v in Pen.legal_transitions().items()],
[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:
Expand All @@ -498,7 +498,10 @@ def test_valid_pen_states(self, state_list, scribble):
@pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
@pytest.mark.parametrize(
"state_list",
[pytest.param(v, id=k) for k, v in Pen.tolerated_transitions().items()],
[
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
Expand All @@ -515,7 +518,7 @@ def test_tolerated_pen_states(self, state_list, scribble):
"state_list",
[
pytest.param(v, id=k)
for k, v in Pen.legal_transitions_with_invert().items()
for k, v in PenState.legal_transitions_with_invert().items()
],
)
def test_valid_invert_pen_states(self, state_list, scribble):
Expand All @@ -535,7 +538,7 @@ def test_valid_invert_pen_states(self, state_list, scribble):
"state_list",
[
pytest.param(v, id=k)
for k, v in Pen.tolerated_transitions_with_invert().items()
for k, v in PenState.tolerated_transitions_with_invert().items()
],
)
def test_tolerated_invert_pen_states(self, state_list, scribble):
Expand All @@ -553,7 +556,7 @@ def test_tolerated_invert_pen_states(self, state_list, scribble):
@pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
@pytest.mark.parametrize(
"state_list",
[pytest.param(v, id=k) for k, v in Pen.broken_transitions().items()],
[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.
Expand Down

0 comments on commit d52f520

Please sign in to comment.