13
13
import libevdev
14
14
import logging
15
15
import pytest
16
- from typing import Dict , Optional , Tuple
16
+ from typing import Dict , List , Optional , Tuple
17
17
18
18
logger = logging .getLogger ("hidtools.test.tablet" )
19
19
@@ -124,7 +124,7 @@ def from_evdev(cls, evdev) -> "PenState":
124
124
125
125
return cls ((touch , tool , button ))
126
126
127
- def apply (self , events ) -> "PenState" :
127
+ def apply (self , events : List [ libevdev . InputEvent ], strict : bool ) -> "PenState" :
128
128
if libevdev .EV_SYN .SYN_REPORT in events :
129
129
raise ValueError ("EV_SYN is in the event sequence" )
130
130
touch = self .touch
@@ -163,13 +163,97 @@ def apply(self, events) -> "PenState":
163
163
button = None
164
164
165
165
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"
169
174
170
175
return new_state
171
176
172
177
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" , ...]:
173
257
"""Following the state machine in the URL above, with a couple of addition
174
258
for skipping the in-range state, due to historical reasons.
175
259
@@ -693,10 +777,14 @@ def post(self, uhdev, pen):
693
777
self .debug_reports (r , uhdev , events )
694
778
return events
695
779
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
+ ):
697
783
# check that the final state is correct
698
784
pen .assert_expected_input_events (evdev )
699
785
786
+ state = from_state
787
+
700
788
# check that the transitions are valid
701
789
sync_events = []
702
790
while libevdev .InputEvent (libevdev .EV_SYN .SYN_REPORT ) in events :
@@ -706,12 +794,12 @@ def validate_transitions(self, from_state, pen, evdev, events):
706
794
events = events [idx + 1 :]
707
795
708
796
# now check for a valid transition
709
- from_state = from_state .apply (sync_events )
797
+ state = state .apply (sync_events , not allow_intermediate_states )
710
798
711
799
if events :
712
- from_state = from_state .apply (sync_events )
800
+ state = state .apply (sync_events , not allow_intermediate_states )
713
801
714
- def _test_states (self , state_list , scribble ):
802
+ def _test_states (self , state_list , scribble , allow_intermediate_states ):
715
803
"""Internal method to test against a list of
716
804
transition between states.
717
805
state_list is a list of PenState objects
@@ -726,7 +814,9 @@ def _test_states(self, state_list, scribble):
726
814
p = Pen (50 , 60 )
727
815
uhdev .move_to (p , PenState .PEN_IS_OUT_OF_RANGE )
728
816
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
+ )
730
820
731
821
cur_state = p .current_state
732
822
@@ -735,14 +825,18 @@ def _test_states(self, state_list, scribble):
735
825
p .x += 1
736
826
p .y -= 1
737
827
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
+ )
739
831
assert len (events ) >= 3 # X, Y, SYN
740
832
uhdev .move_to (p , state )
741
833
if scribble and state != PenState .PEN_IS_OUT_OF_RANGE :
742
834
p .x += 1
743
835
p .y -= 1
744
836
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
+ )
746
840
cur_state = p .current_state
747
841
748
842
@pytest .mark .parametrize ("scribble" , [True , False ], ids = ["scribble" , "static" ])
@@ -755,7 +849,7 @@ def test_valid_pen_states(self, state_list, scribble):
755
849
we don't have Invert nor Erase bits, so just move in/out-of-range or proximity.
756
850
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
757
851
"""
758
- self ._test_states (state_list , scribble )
852
+ self ._test_states (state_list , scribble , allow_intermediate_states = False )
759
853
760
854
@pytest .mark .parametrize ("scribble" , [True , False ], ids = ["scribble" , "static" ])
761
855
@pytest .mark .parametrize (
@@ -769,7 +863,7 @@ def test_tolerated_pen_states(self, state_list, scribble):
769
863
"""This is not adhering to the Windows Pen Implementation state machine
770
864
but we should expect the kernel to behave properly, mostly for historical
771
865
reasons."""
772
- self ._test_states (state_list , scribble )
866
+ self ._test_states (state_list , scribble , allow_intermediate_states = True )
773
867
774
868
@pytest .mark .skip_if_uhdev (
775
869
lambda uhdev : "Barrel Switch" not in uhdev .fields ,
@@ -785,7 +879,7 @@ def test_tolerated_pen_states(self, state_list, scribble):
785
879
)
786
880
def test_valid_primary_button_pen_states (self , state_list , scribble ):
787
881
"""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 )
789
883
790
884
@pytest .mark .skip_if_uhdev (
791
885
lambda uhdev : "Secondary Barrel Switch" not in uhdev .fields ,
@@ -801,7 +895,7 @@ def test_valid_primary_button_pen_states(self, state_list, scribble):
801
895
)
802
896
def test_valid_secondary_button_pen_states (self , state_list , scribble ):
803
897
"""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 )
805
899
806
900
@pytest .mark .skip_if_uhdev (
807
901
lambda uhdev : "Invert" not in uhdev .fields ,
@@ -821,7 +915,7 @@ def test_valid_invert_pen_states(self, state_list, scribble):
821
915
to erase.
822
916
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
823
917
"""
824
- self ._test_states (state_list , scribble )
918
+ self ._test_states (state_list , scribble , allow_intermediate_states = False )
825
919
826
920
@pytest .mark .skip_if_uhdev (
827
921
lambda uhdev : "Invert" not in uhdev .fields ,
@@ -841,7 +935,7 @@ def test_tolerated_invert_pen_states(self, state_list, scribble):
841
935
to erase.
842
936
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
843
937
"""
844
- self ._test_states (state_list , scribble )
938
+ self ._test_states (state_list , scribble , allow_intermediate_states = True )
845
939
846
940
@pytest .mark .skip_if_uhdev (
847
941
lambda uhdev : "Invert" not in uhdev .fields ,
@@ -858,7 +952,7 @@ def test_tolerated_broken_pen_states(self, state_list, scribble):
858
952
For example, a pen that has the eraser button might wobble between
859
953
touching and erasing if the tablet doesn't enforce the Windows
860
954
state machine."""
861
- self ._test_states (state_list , scribble )
955
+ self ._test_states (state_list , scribble , allow_intermediate_states = True )
862
956
863
957
864
958
class GXTP_pen (PenDigitizer ):
0 commit comments