Skip to content

Commit ab9b829

Browse files
author
Benjamin Tissoires
committed
selftests/hid: tablets: be stricter for some transitions
To accommodate for legacy devices, we rely on the last state of a transition to be valid: for example when we test PEN_IS_OUT_OF_RANGE to PEN_IS_IN_CONTACT, any "normal" device that reports an InRange bit would insert a PEN_IS_IN_RANGE state between the 2. This is of course valid, but this solution prevents to detect false releases emitted by some firmware: when pressing an "eraser mode" button, they might send an extra PEN_IS_OUT_OF_RANGE that we may want to filter. So define 2 sets of transitions: one that is the ideal behavior, and one that is OK, it won't break user space, but we have serious doubts if we are doing the right thing. And depending on the test, either ask only for valid transitions, or tolerate weird ones. Reviewed-by: Peter Hutterer <[email protected]> Acked-by: Jiri Kosina <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Benjamin Tissoires <[email protected]>
1 parent 76df1f7 commit ab9b829

File tree

1 file changed

+113
-19
lines changed

1 file changed

+113
-19
lines changed

tools/testing/selftests/hid/tests/test_tablet.py

Lines changed: 113 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import libevdev
1414
import logging
1515
import pytest
16-
from typing import Dict, Optional, Tuple
16+
from typing import Dict, List, Optional, Tuple
1717

1818
logger = logging.getLogger("hidtools.test.tablet")
1919

@@ -124,7 +124,7 @@ def from_evdev(cls, evdev) -> "PenState":
124124

125125
return cls((touch, tool, button))
126126

127-
def apply(self, events) -> "PenState":
127+
def apply(self, events: List[libevdev.InputEvent], strict: bool) -> "PenState":
128128
if libevdev.EV_SYN.SYN_REPORT in events:
129129
raise ValueError("EV_SYN is in the event sequence")
130130
touch = self.touch
@@ -163,13 +163,97 @@ def apply(self, events) -> "PenState":
163163
button = None
164164

165165
new_state = PenState((touch, tool, button))
166-
assert (
167-
new_state in self.valid_transitions()
168-
), f"moving from {self} to {new_state} is forbidden"
166+
if strict:
167+
assert (
168+
new_state in self.valid_transitions()
169+
), f"moving from {self} to {new_state} is forbidden"
170+
else:
171+
assert (
172+
new_state in self.historically_tolerated_transitions()
173+
), f"moving from {self} to {new_state} is forbidden"
169174

170175
return new_state
171176

172177
def valid_transitions(self) -> Tuple["PenState", ...]:
178+
"""Following the state machine in the URL above.
179+
180+
Note that those transitions are from the evdev point of view, not HID"""
181+
if self == PenState.PEN_IS_OUT_OF_RANGE:
182+
return (
183+
PenState.PEN_IS_OUT_OF_RANGE,
184+
PenState.PEN_IS_IN_RANGE,
185+
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
186+
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
187+
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
188+
PenState.PEN_IS_IN_CONTACT,
189+
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
190+
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
191+
PenState.PEN_IS_ERASING,
192+
)
193+
194+
if self == PenState.PEN_IS_IN_RANGE:
195+
return (
196+
PenState.PEN_IS_IN_RANGE,
197+
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
198+
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
199+
PenState.PEN_IS_OUT_OF_RANGE,
200+
PenState.PEN_IS_IN_CONTACT,
201+
)
202+
203+
if self == PenState.PEN_IS_IN_CONTACT:
204+
return (
205+
PenState.PEN_IS_IN_CONTACT,
206+
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
207+
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
208+
PenState.PEN_IS_IN_RANGE,
209+
)
210+
211+
if self == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
212+
return (
213+
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
214+
PenState.PEN_IS_OUT_OF_RANGE,
215+
PenState.PEN_IS_ERASING,
216+
)
217+
218+
if self == PenState.PEN_IS_ERASING:
219+
return (
220+
PenState.PEN_IS_ERASING,
221+
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
222+
)
223+
224+
if self == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
225+
return (
226+
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
227+
PenState.PEN_IS_IN_RANGE,
228+
PenState.PEN_IS_OUT_OF_RANGE,
229+
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
230+
)
231+
232+
if self == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
233+
return (
234+
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
235+
PenState.PEN_IS_IN_CONTACT,
236+
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
237+
)
238+
239+
if self == PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON:
240+
return (
241+
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
242+
PenState.PEN_IS_IN_RANGE,
243+
PenState.PEN_IS_OUT_OF_RANGE,
244+
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
245+
)
246+
247+
if self == PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON:
248+
return (
249+
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
250+
PenState.PEN_IS_IN_CONTACT,
251+
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
252+
)
253+
254+
return tuple()
255+
256+
def historically_tolerated_transitions(self) -> Tuple["PenState", ...]:
173257
"""Following the state machine in the URL above, with a couple of addition
174258
for skipping the in-range state, due to historical reasons.
175259
@@ -693,10 +777,14 @@ def post(self, uhdev, pen):
693777
self.debug_reports(r, uhdev, events)
694778
return events
695779

696-
def validate_transitions(self, from_state, pen, evdev, events):
780+
def validate_transitions(
781+
self, from_state, pen, evdev, events, allow_intermediate_states
782+
):
697783
# check that the final state is correct
698784
pen.assert_expected_input_events(evdev)
699785

786+
state = from_state
787+
700788
# check that the transitions are valid
701789
sync_events = []
702790
while libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT) in events:
@@ -706,12 +794,12 @@ def validate_transitions(self, from_state, pen, evdev, events):
706794
events = events[idx + 1 :]
707795

708796
# now check for a valid transition
709-
from_state = from_state.apply(sync_events)
797+
state = state.apply(sync_events, not allow_intermediate_states)
710798

711799
if events:
712-
from_state = from_state.apply(sync_events)
800+
state = state.apply(sync_events, not allow_intermediate_states)
713801

714-
def _test_states(self, state_list, scribble):
802+
def _test_states(self, state_list, scribble, allow_intermediate_states):
715803
"""Internal method to test against a list of
716804
transition between states.
717805
state_list is a list of PenState objects
@@ -726,7 +814,9 @@ def _test_states(self, state_list, scribble):
726814
p = Pen(50, 60)
727815
uhdev.move_to(p, PenState.PEN_IS_OUT_OF_RANGE)
728816
events = self.post(uhdev, p)
729-
self.validate_transitions(cur_state, p, evdev, events)
817+
self.validate_transitions(
818+
cur_state, p, evdev, events, allow_intermediate_states
819+
)
730820

731821
cur_state = p.current_state
732822

@@ -735,14 +825,18 @@ def _test_states(self, state_list, scribble):
735825
p.x += 1
736826
p.y -= 1
737827
events = self.post(uhdev, p)
738-
self.validate_transitions(cur_state, p, evdev, events)
828+
self.validate_transitions(
829+
cur_state, p, evdev, events, allow_intermediate_states
830+
)
739831
assert len(events) >= 3 # X, Y, SYN
740832
uhdev.move_to(p, state)
741833
if scribble and state != PenState.PEN_IS_OUT_OF_RANGE:
742834
p.x += 1
743835
p.y -= 1
744836
events = self.post(uhdev, p)
745-
self.validate_transitions(cur_state, p, evdev, events)
837+
self.validate_transitions(
838+
cur_state, p, evdev, events, allow_intermediate_states
839+
)
746840
cur_state = p.current_state
747841

748842
@pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
@@ -755,7 +849,7 @@ def test_valid_pen_states(self, state_list, scribble):
755849
we don't have Invert nor Erase bits, so just move in/out-of-range or proximity.
756850
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
757851
"""
758-
self._test_states(state_list, scribble)
852+
self._test_states(state_list, scribble, allow_intermediate_states=False)
759853

760854
@pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
761855
@pytest.mark.parametrize(
@@ -769,7 +863,7 @@ def test_tolerated_pen_states(self, state_list, scribble):
769863
"""This is not adhering to the Windows Pen Implementation state machine
770864
but we should expect the kernel to behave properly, mostly for historical
771865
reasons."""
772-
self._test_states(state_list, scribble)
866+
self._test_states(state_list, scribble, allow_intermediate_states=True)
773867

774868
@pytest.mark.skip_if_uhdev(
775869
lambda uhdev: "Barrel Switch" not in uhdev.fields,
@@ -785,7 +879,7 @@ def test_tolerated_pen_states(self, state_list, scribble):
785879
)
786880
def test_valid_primary_button_pen_states(self, state_list, scribble):
787881
"""Rework the transition state machine by adding the primary button."""
788-
self._test_states(state_list, scribble)
882+
self._test_states(state_list, scribble, allow_intermediate_states=False)
789883

790884
@pytest.mark.skip_if_uhdev(
791885
lambda uhdev: "Secondary Barrel Switch" not in uhdev.fields,
@@ -801,7 +895,7 @@ def test_valid_primary_button_pen_states(self, state_list, scribble):
801895
)
802896
def test_valid_secondary_button_pen_states(self, state_list, scribble):
803897
"""Rework the transition state machine by adding the secondary button."""
804-
self._test_states(state_list, scribble)
898+
self._test_states(state_list, scribble, allow_intermediate_states=False)
805899

806900
@pytest.mark.skip_if_uhdev(
807901
lambda uhdev: "Invert" not in uhdev.fields,
@@ -821,7 +915,7 @@ def test_valid_invert_pen_states(self, state_list, scribble):
821915
to erase.
822916
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
823917
"""
824-
self._test_states(state_list, scribble)
918+
self._test_states(state_list, scribble, allow_intermediate_states=False)
825919

826920
@pytest.mark.skip_if_uhdev(
827921
lambda uhdev: "Invert" not in uhdev.fields,
@@ -841,7 +935,7 @@ def test_tolerated_invert_pen_states(self, state_list, scribble):
841935
to erase.
842936
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
843937
"""
844-
self._test_states(state_list, scribble)
938+
self._test_states(state_list, scribble, allow_intermediate_states=True)
845939

846940
@pytest.mark.skip_if_uhdev(
847941
lambda uhdev: "Invert" not in uhdev.fields,
@@ -858,7 +952,7 @@ def test_tolerated_broken_pen_states(self, state_list, scribble):
858952
For example, a pen that has the eraser button might wobble between
859953
touching and erasing if the tablet doesn't enforce the Windows
860954
state machine."""
861-
self._test_states(state_list, scribble)
955+
self._test_states(state_list, scribble, allow_intermediate_states=True)
862956

863957

864958
class GXTP_pen(PenDigitizer):

0 commit comments

Comments
 (0)