Skip to content

Commit 74452d6

Browse files
author
Benjamin Tissoires
committed
selftests/hid: tablets: add variants of states with buttons
Turns out that there are transitions that are unlikely to happen: for example, having both the tip switch and a button being changed at the same time (in the same report) would require either a very talented and precise user or a very bad hardware with a very low sampling rate. So instead of manually building the button test by hand and forgetting about some cases, let's reuse the state machine and transitions we have. This patch only adds the states and the valid transitions. The actual tests will be replaced later. 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 83912f8 commit 74452d6

File tree

1 file changed

+160
-13
lines changed

1 file changed

+160
-13
lines changed

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

Lines changed: 160 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,72 @@ class ToolType(Enum):
3030
RUBBER = libevdev.EV_KEY.BTN_TOOL_RUBBER
3131

3232

33+
class BtnPressed(Enum):
34+
"""Represents whether a button is pressed on the stylus"""
35+
36+
PRIMARY_PRESSED = libevdev.EV_KEY.BTN_STYLUS
37+
SECONDARY_PRESSED = libevdev.EV_KEY.BTN_STYLUS2
38+
39+
3340
class PenState(Enum):
3441
"""Pen states according to Microsoft reference:
3542
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
36-
"""
3743
38-
PEN_IS_OUT_OF_RANGE = BtnTouch.UP, None
39-
PEN_IS_IN_RANGE = BtnTouch.UP, ToolType.PEN
40-
PEN_IS_IN_CONTACT = BtnTouch.DOWN, ToolType.PEN
41-
PEN_IS_IN_RANGE_WITH_ERASING_INTENT = BtnTouch.UP, ToolType.RUBBER
42-
PEN_IS_ERASING = BtnTouch.DOWN, ToolType.RUBBER
44+
We extend it with the various buttons when we need to check them.
45+
"""
4346

44-
def __init__(self, touch: BtnTouch, tool: Optional[ToolType]):
47+
PEN_IS_OUT_OF_RANGE = BtnTouch.UP, None, None
48+
PEN_IS_IN_RANGE = BtnTouch.UP, ToolType.PEN, None
49+
PEN_IS_IN_RANGE_WITH_BUTTON = BtnTouch.UP, ToolType.PEN, BtnPressed.PRIMARY_PRESSED
50+
PEN_IS_IN_RANGE_WITH_SECOND_BUTTON = (
51+
BtnTouch.UP,
52+
ToolType.PEN,
53+
BtnPressed.SECONDARY_PRESSED,
54+
)
55+
PEN_IS_IN_CONTACT = BtnTouch.DOWN, ToolType.PEN, None
56+
PEN_IS_IN_CONTACT_WITH_BUTTON = (
57+
BtnTouch.DOWN,
58+
ToolType.PEN,
59+
BtnPressed.PRIMARY_PRESSED,
60+
)
61+
PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON = (
62+
BtnTouch.DOWN,
63+
ToolType.PEN,
64+
BtnPressed.SECONDARY_PRESSED,
65+
)
66+
PEN_IS_IN_RANGE_WITH_ERASING_INTENT = BtnTouch.UP, ToolType.RUBBER, None
67+
PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON = (
68+
BtnTouch.UP,
69+
ToolType.RUBBER,
70+
BtnPressed.PRIMARY_PRESSED,
71+
)
72+
PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_SECOND_BUTTON = (
73+
BtnTouch.UP,
74+
ToolType.RUBBER,
75+
BtnPressed.SECONDARY_PRESSED,
76+
)
77+
PEN_IS_ERASING = BtnTouch.DOWN, ToolType.RUBBER, None
78+
PEN_IS_ERASING_WITH_BUTTON = (
79+
BtnTouch.DOWN,
80+
ToolType.RUBBER,
81+
BtnPressed.PRIMARY_PRESSED,
82+
)
83+
PEN_IS_ERASING_WITH_SECOND_BUTTON = (
84+
BtnTouch.DOWN,
85+
ToolType.RUBBER,
86+
BtnPressed.SECONDARY_PRESSED,
87+
)
88+
89+
def __init__(self, touch: BtnTouch, tool: Optional[ToolType], button: Optional[BtnPressed]):
4590
self.touch = touch
4691
self.tool = tool
92+
self.button = button
4793

4894
@classmethod
4995
def from_evdev(cls, evdev) -> "PenState":
5096
touch = BtnTouch(evdev.value[libevdev.EV_KEY.BTN_TOUCH])
5197
tool = None
98+
button = None
5299
if (
53100
evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
54101
and not evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
@@ -65,7 +112,17 @@ def from_evdev(cls, evdev) -> "PenState":
65112
):
66113
raise ValueError("2 tools are not allowed")
67114

68-
return cls((touch, tool))
115+
# we take only the highest button in account
116+
for b in [libevdev.EV_KEY.BTN_STYLUS, libevdev.EV_KEY.BTN_STYLUS2]:
117+
if bool(evdev.value[b]):
118+
button = b
119+
120+
# the kernel tends to insert an EV_SYN once removing the tool, so
121+
# the button will be released after
122+
if tool is None:
123+
button = None
124+
125+
return cls((touch, tool, button))
69126

70127
def apply(self, events) -> "PenState":
71128
if libevdev.EV_SYN.SYN_REPORT in events:
@@ -74,6 +131,8 @@ def apply(self, events) -> "PenState":
74131
touch_found = False
75132
tool = self.tool
76133
tool_found = False
134+
button = self.button
135+
button_found = False
77136

78137
for ev in events:
79138
if ev == libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH):
@@ -88,12 +147,22 @@ def apply(self, events) -> "PenState":
88147
if tool_found:
89148
raise ValueError(f"duplicated BTN_TOOL_* in {events}")
90149
tool_found = True
91-
if ev.value:
92-
tool = ToolType(ev.code)
93-
else:
94-
tool = None
150+
tool = ToolType(ev.code) if ev.value else None
151+
elif ev in (
152+
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS),
153+
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2),
154+
):
155+
if button_found:
156+
raise ValueError(f"duplicated BTN_STYLUS* in {events}")
157+
button_found = True
158+
button = ev.code if ev.value else None
95159

96-
new_state = PenState((touch, tool))
160+
# the kernel tends to insert an EV_SYN once removing the tool, so
161+
# the button will be released after
162+
if tool is None:
163+
button = None
164+
165+
new_state = PenState((touch, tool, button))
97166
assert (
98167
new_state in self.valid_transitions()
99168
), f"moving from {self} to {new_state} is forbidden"
@@ -109,21 +178,29 @@ def valid_transitions(self) -> Tuple["PenState", ...]:
109178
return (
110179
PenState.PEN_IS_OUT_OF_RANGE,
111180
PenState.PEN_IS_IN_RANGE,
181+
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
182+
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
112183
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
113184
PenState.PEN_IS_IN_CONTACT,
185+
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
186+
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
114187
PenState.PEN_IS_ERASING,
115188
)
116189

117190
if self == PenState.PEN_IS_IN_RANGE:
118191
return (
119192
PenState.PEN_IS_IN_RANGE,
193+
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
194+
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
120195
PenState.PEN_IS_OUT_OF_RANGE,
121196
PenState.PEN_IS_IN_CONTACT,
122197
)
123198

124199
if self == PenState.PEN_IS_IN_CONTACT:
125200
return (
126201
PenState.PEN_IS_IN_CONTACT,
202+
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
203+
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
127204
PenState.PEN_IS_IN_RANGE,
128205
PenState.PEN_IS_OUT_OF_RANGE,
129206
)
@@ -142,6 +219,38 @@ def valid_transitions(self) -> Tuple["PenState", ...]:
142219
PenState.PEN_IS_OUT_OF_RANGE,
143220
)
144221

222+
if self == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
223+
return (
224+
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
225+
PenState.PEN_IS_IN_RANGE,
226+
PenState.PEN_IS_OUT_OF_RANGE,
227+
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
228+
)
229+
230+
if self == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
231+
return (
232+
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
233+
PenState.PEN_IS_IN_CONTACT,
234+
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
235+
PenState.PEN_IS_OUT_OF_RANGE,
236+
)
237+
238+
if self == PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON:
239+
return (
240+
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
241+
PenState.PEN_IS_IN_RANGE,
242+
PenState.PEN_IS_OUT_OF_RANGE,
243+
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
244+
)
245+
246+
if self == PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON:
247+
return (
248+
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
249+
PenState.PEN_IS_IN_CONTACT,
250+
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
251+
PenState.PEN_IS_OUT_OF_RANGE,
252+
)
253+
145254
return tuple()
146255

147256
@staticmethod
@@ -376,26 +485,64 @@ def move_to(self, pen, state):
376485
pen.xtilt = 0
377486
pen.ytilt = 0
378487
pen.twist = 0
488+
pen.barrelswitch = False
489+
pen.secondarybarrelswitch = False
379490
elif state == PenState.PEN_IS_IN_RANGE:
380491
pen.tipswitch = False
381492
pen.inrange = True
382493
pen.invert = False
383494
pen.eraser = False
495+
pen.barrelswitch = False
496+
pen.secondarybarrelswitch = False
384497
elif state == PenState.PEN_IS_IN_CONTACT:
385498
pen.tipswitch = True
386499
pen.inrange = True
387500
pen.invert = False
388501
pen.eraser = False
502+
pen.barrelswitch = False
503+
pen.secondarybarrelswitch = False
504+
elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
505+
pen.tipswitch = False
506+
pen.inrange = True
507+
pen.invert = False
508+
pen.eraser = False
509+
pen.barrelswitch = True
510+
pen.secondarybarrelswitch = False
511+
elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
512+
pen.tipswitch = True
513+
pen.inrange = True
514+
pen.invert = False
515+
pen.eraser = False
516+
pen.barrelswitch = True
517+
pen.secondarybarrelswitch = False
518+
elif state == PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON:
519+
pen.tipswitch = False
520+
pen.inrange = True
521+
pen.invert = False
522+
pen.eraser = False
523+
pen.barrelswitch = False
524+
pen.secondarybarrelswitch = True
525+
elif state == PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON:
526+
pen.tipswitch = True
527+
pen.inrange = True
528+
pen.invert = False
529+
pen.eraser = False
530+
pen.barrelswitch = False
531+
pen.secondarybarrelswitch = True
389532
elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
390533
pen.tipswitch = False
391534
pen.inrange = True
392535
pen.invert = True
393536
pen.eraser = False
537+
pen.barrelswitch = False
538+
pen.secondarybarrelswitch = False
394539
elif state == PenState.PEN_IS_ERASING:
395540
pen.tipswitch = False
396541
pen.inrange = True
397542
pen.invert = False
398543
pen.eraser = True
544+
pen.barrelswitch = False
545+
pen.secondarybarrelswitch = False
399546

400547
pen.current_state = state
401548

0 commit comments

Comments
 (0)