@@ -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+
3340class 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