@@ -30,25 +30,72 @@ class ToolType(Enum):
30
30
RUBBER = libevdev .EV_KEY .BTN_TOOL_RUBBER
31
31
32
32
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
+
33
40
class PenState (Enum ):
34
41
"""Pen states according to Microsoft reference:
35
42
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
36
- """
37
43
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
+ """
43
46
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 ]):
45
90
self .touch = touch
46
91
self .tool = tool
92
+ self .button = button
47
93
48
94
@classmethod
49
95
def from_evdev (cls , evdev ) -> "PenState" :
50
96
touch = BtnTouch (evdev .value [libevdev .EV_KEY .BTN_TOUCH ])
51
97
tool = None
98
+ button = None
52
99
if (
53
100
evdev .value [libevdev .EV_KEY .BTN_TOOL_RUBBER ]
54
101
and not evdev .value [libevdev .EV_KEY .BTN_TOOL_PEN ]
@@ -65,7 +112,17 @@ def from_evdev(cls, evdev) -> "PenState":
65
112
):
66
113
raise ValueError ("2 tools are not allowed" )
67
114
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 ))
69
126
70
127
def apply (self , events ) -> "PenState" :
71
128
if libevdev .EV_SYN .SYN_REPORT in events :
@@ -74,6 +131,8 @@ def apply(self, events) -> "PenState":
74
131
touch_found = False
75
132
tool = self .tool
76
133
tool_found = False
134
+ button = self .button
135
+ button_found = False
77
136
78
137
for ev in events :
79
138
if ev == libevdev .InputEvent (libevdev .EV_KEY .BTN_TOUCH ):
@@ -88,12 +147,22 @@ def apply(self, events) -> "PenState":
88
147
if tool_found :
89
148
raise ValueError (f"duplicated BTN_TOOL_* in { events } " )
90
149
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
95
159
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 ))
97
166
assert (
98
167
new_state in self .valid_transitions ()
99
168
), f"moving from { self } to { new_state } is forbidden"
@@ -109,21 +178,29 @@ def valid_transitions(self) -> Tuple["PenState", ...]:
109
178
return (
110
179
PenState .PEN_IS_OUT_OF_RANGE ,
111
180
PenState .PEN_IS_IN_RANGE ,
181
+ PenState .PEN_IS_IN_RANGE_WITH_BUTTON ,
182
+ PenState .PEN_IS_IN_RANGE_WITH_SECOND_BUTTON ,
112
183
PenState .PEN_IS_IN_RANGE_WITH_ERASING_INTENT ,
113
184
PenState .PEN_IS_IN_CONTACT ,
185
+ PenState .PEN_IS_IN_CONTACT_WITH_BUTTON ,
186
+ PenState .PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON ,
114
187
PenState .PEN_IS_ERASING ,
115
188
)
116
189
117
190
if self == PenState .PEN_IS_IN_RANGE :
118
191
return (
119
192
PenState .PEN_IS_IN_RANGE ,
193
+ PenState .PEN_IS_IN_RANGE_WITH_BUTTON ,
194
+ PenState .PEN_IS_IN_RANGE_WITH_SECOND_BUTTON ,
120
195
PenState .PEN_IS_OUT_OF_RANGE ,
121
196
PenState .PEN_IS_IN_CONTACT ,
122
197
)
123
198
124
199
if self == PenState .PEN_IS_IN_CONTACT :
125
200
return (
126
201
PenState .PEN_IS_IN_CONTACT ,
202
+ PenState .PEN_IS_IN_CONTACT_WITH_BUTTON ,
203
+ PenState .PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON ,
127
204
PenState .PEN_IS_IN_RANGE ,
128
205
PenState .PEN_IS_OUT_OF_RANGE ,
129
206
)
@@ -142,6 +219,38 @@ def valid_transitions(self) -> Tuple["PenState", ...]:
142
219
PenState .PEN_IS_OUT_OF_RANGE ,
143
220
)
144
221
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
+
145
254
return tuple ()
146
255
147
256
@staticmethod
@@ -376,26 +485,64 @@ def move_to(self, pen, state):
376
485
pen .xtilt = 0
377
486
pen .ytilt = 0
378
487
pen .twist = 0
488
+ pen .barrelswitch = False
489
+ pen .secondarybarrelswitch = False
379
490
elif state == PenState .PEN_IS_IN_RANGE :
380
491
pen .tipswitch = False
381
492
pen .inrange = True
382
493
pen .invert = False
383
494
pen .eraser = False
495
+ pen .barrelswitch = False
496
+ pen .secondarybarrelswitch = False
384
497
elif state == PenState .PEN_IS_IN_CONTACT :
385
498
pen .tipswitch = True
386
499
pen .inrange = True
387
500
pen .invert = False
388
501
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
389
532
elif state == PenState .PEN_IS_IN_RANGE_WITH_ERASING_INTENT :
390
533
pen .tipswitch = False
391
534
pen .inrange = True
392
535
pen .invert = True
393
536
pen .eraser = False
537
+ pen .barrelswitch = False
538
+ pen .secondarybarrelswitch = False
394
539
elif state == PenState .PEN_IS_ERASING :
395
540
pen .tipswitch = False
396
541
pen .inrange = True
397
542
pen .invert = False
398
543
pen .eraser = True
544
+ pen .barrelswitch = False
545
+ pen .secondarybarrelswitch = False
399
546
400
547
pen .current_state = state
401
548
0 commit comments