1313import libevdev
1414import logging
1515import pytest
16- from typing import Dict , Optional , Tuple
16+ from typing import Dict , List , Optional , Tuple
1717
1818logger = 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
864958class GXTP_pen (PenDigitizer ):
0 commit comments