@@ -35,6 +35,7 @@ class BtnPressed(Enum):
35
35
36
36
PRIMARY_PRESSED = libevdev .EV_KEY .BTN_STYLUS
37
37
SECONDARY_PRESSED = libevdev .EV_KEY .BTN_STYLUS2
38
+ THIRD_PRESSED = libevdev .EV_KEY .BTN_STYLUS3
38
39
39
40
40
41
class PenState (Enum ):
@@ -503,6 +504,7 @@ def assert_expected_input_events(self, evdev, button):
503
504
buttons = [
504
505
BtnPressed .PRIMARY_PRESSED ,
505
506
BtnPressed .SECONDARY_PRESSED ,
507
+ BtnPressed .THIRD_PRESSED ,
506
508
]
507
509
if button is not None :
508
510
buttons .remove (button )
@@ -787,6 +789,27 @@ def test_valid_secondary_button_pen_states(self, state_list, scribble):
787
789
button = BtnPressed .SECONDARY_PRESSED ,
788
790
)
789
791
792
+ @pytest .mark .skip_if_uhdev (
793
+ lambda uhdev : "Third Barrel Switch" not in uhdev .fields ,
794
+ "Device not compatible, missing Third Barrel Switch usage" ,
795
+ )
796
+ @pytest .mark .parametrize ("scribble" , [True , False ], ids = ["scribble" , "static" ])
797
+ @pytest .mark .parametrize (
798
+ "state_list" ,
799
+ [
800
+ pytest .param (v , id = k )
801
+ for k , v in PenState .legal_transitions_with_button ().items ()
802
+ ],
803
+ )
804
+ def test_valid_third_button_pen_states (self , state_list , scribble ):
805
+ """Rework the transition state machine by adding the secondary button."""
806
+ self ._test_states (
807
+ state_list ,
808
+ scribble ,
809
+ allow_intermediate_states = False ,
810
+ button = BtnPressed .THIRD_PRESSED ,
811
+ )
812
+
790
813
@pytest .mark .skip_if_uhdev (
791
814
lambda uhdev : "Invert" not in uhdev .fields ,
792
815
"Device not compatible, missing Invert usage" ,
@@ -1111,6 +1134,163 @@ def event(self, pen, button):
1111
1134
return rs
1112
1135
1113
1136
1137
+ class Huion_Kamvas_Pro_19_256c_006b (PenDigitizer ):
1138
+ """
1139
+ Pen that reports secondary barrel switch through secondary TipSwtich
1140
+ and 3rd button through Invert
1141
+ """
1142
+
1143
+ def __init__ (
1144
+ self ,
1145
+ name ,
1146
+ rdesc_str = None ,
1147
+ rdesc = None ,
1148
+ application = "Stylus" ,
1149
+ physical = None ,
1150
+ input_info = (BusType .USB , 0x256C , 0x006B ),
1151
+ evdev_name_suffix = None ,
1152
+ ):
1153
+ super ().__init__ (
1154
+ name , rdesc_str , rdesc , application , physical , input_info , evdev_name_suffix
1155
+ )
1156
+ self .fields .append ("Secondary Barrel Switch" )
1157
+ self .fields .append ("Third Barrel Switch" )
1158
+ self .previous_state = PenState .PEN_IS_OUT_OF_RANGE
1159
+
1160
+ def move_to (self , pen , state , button , debug = True ):
1161
+ # fill in the previous values
1162
+ if pen .current_state == PenState .PEN_IS_OUT_OF_RANGE :
1163
+ pen .restore ()
1164
+
1165
+ if debug :
1166
+ print (f"\n *** pen is moving to { state } ***" )
1167
+
1168
+ if state == PenState .PEN_IS_OUT_OF_RANGE :
1169
+ pen .backup ()
1170
+ pen .tipswitch = False
1171
+ pen .tippressure = 0
1172
+ pen .azimuth = 0
1173
+ pen .inrange = False
1174
+ pen .width = 0
1175
+ pen .height = 0
1176
+ pen .invert = False
1177
+ pen .eraser = False
1178
+ pen .xtilt = 0
1179
+ pen .ytilt = 0
1180
+ pen .twist = 0
1181
+ pen .barrelswitch = False
1182
+ pen .secondarytipswitch = False
1183
+ elif state == PenState .PEN_IS_IN_RANGE :
1184
+ pen .tipswitch = False
1185
+ pen .inrange = True
1186
+ pen .invert = False
1187
+ pen .eraser = False
1188
+ pen .barrelswitch = False
1189
+ pen .secondarytipswitch = False
1190
+ elif state == PenState .PEN_IS_IN_CONTACT :
1191
+ pen .tipswitch = True
1192
+ pen .inrange = True
1193
+ pen .invert = False
1194
+ pen .eraser = False
1195
+ pen .barrelswitch = False
1196
+ pen .secondarytipswitch = False
1197
+ elif state == PenState .PEN_IS_IN_RANGE_WITH_BUTTON :
1198
+ pen .tipswitch = False
1199
+ pen .inrange = True
1200
+ pen .eraser = False
1201
+ assert button is not None
1202
+ pen .barrelswitch = button == BtnPressed .PRIMARY_PRESSED
1203
+ pen .secondarytipswitch = button == BtnPressed .SECONDARY_PRESSED
1204
+ pen .invert = button == BtnPressed .THIRD_PRESSED
1205
+ elif state == PenState .PEN_IS_IN_CONTACT_WITH_BUTTON :
1206
+ pen .tipswitch = True
1207
+ pen .inrange = True
1208
+ pen .eraser = False
1209
+ assert button is not None
1210
+ pen .barrelswitch = button == BtnPressed .PRIMARY_PRESSED
1211
+ pen .secondarytipswitch = button == BtnPressed .SECONDARY_PRESSED
1212
+ pen .invert = button == BtnPressed .THIRD_PRESSED
1213
+ elif state == PenState .PEN_IS_IN_RANGE_WITH_ERASING_INTENT :
1214
+ pen .tipswitch = False
1215
+ pen .inrange = True
1216
+ pen .invert = True
1217
+ pen .eraser = False
1218
+ pen .barrelswitch = False
1219
+ pen .secondarytipswitch = False
1220
+ elif state == PenState .PEN_IS_ERASING :
1221
+ pen .tipswitch = False
1222
+ pen .inrange = True
1223
+ pen .invert = False
1224
+ pen .eraser = True
1225
+ pen .barrelswitch = False
1226
+ pen .secondarytipswitch = False
1227
+
1228
+ pen .current_state = state
1229
+
1230
+ def call_input_event (self , report ):
1231
+ if report [0 ] == 0x0a :
1232
+ # ensures the original second Eraser usage is null
1233
+ report [1 ] &= 0xdf
1234
+
1235
+ # ensures the original last bit is equal to bit 6 (In Range)
1236
+ if report [1 ] & 0x40 :
1237
+ report [1 ] |= 0x80
1238
+
1239
+ super ().call_input_event (report )
1240
+
1241
+ def send_intermediate_state (self , pen , state , test_button ):
1242
+ intermediate_pen = copy .copy (pen )
1243
+ self .move_to (intermediate_pen , state , test_button , debug = False )
1244
+ return super ().event (intermediate_pen , test_button )
1245
+
1246
+ def event (self , pen , button ):
1247
+ rs = []
1248
+
1249
+ # it's not possible to go between eraser mode or not without
1250
+ # going out-of-prox: the eraser mode is activated by presenting
1251
+ # the tail of the pen
1252
+ if self .previous_state in (
1253
+ PenState .PEN_IS_IN_RANGE ,
1254
+ PenState .PEN_IS_IN_RANGE_WITH_BUTTON ,
1255
+ PenState .PEN_IS_IN_CONTACT ,
1256
+ PenState .PEN_IS_IN_CONTACT_WITH_BUTTON ,
1257
+ ) and pen .current_state in (
1258
+ PenState .PEN_IS_IN_RANGE_WITH_ERASING_INTENT ,
1259
+ PenState .PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON ,
1260
+ PenState .PEN_IS_ERASING ,
1261
+ PenState .PEN_IS_ERASING_WITH_BUTTON ,
1262
+ ):
1263
+ rs .extend (
1264
+ self .send_intermediate_state (pen , PenState .PEN_IS_OUT_OF_RANGE , button )
1265
+ )
1266
+
1267
+ # same than above except from eraser to normal
1268
+ if self .previous_state in (
1269
+ PenState .PEN_IS_IN_RANGE_WITH_ERASING_INTENT ,
1270
+ PenState .PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON ,
1271
+ PenState .PEN_IS_ERASING ,
1272
+ PenState .PEN_IS_ERASING_WITH_BUTTON ,
1273
+ ) and pen .current_state in (
1274
+ PenState .PEN_IS_IN_RANGE ,
1275
+ PenState .PEN_IS_IN_RANGE_WITH_BUTTON ,
1276
+ PenState .PEN_IS_IN_CONTACT ,
1277
+ PenState .PEN_IS_IN_CONTACT_WITH_BUTTON ,
1278
+ ):
1279
+ rs .extend (
1280
+ self .send_intermediate_state (pen , PenState .PEN_IS_OUT_OF_RANGE , button )
1281
+ )
1282
+
1283
+ if self .previous_state == PenState .PEN_IS_OUT_OF_RANGE :
1284
+ if pen .current_state == PenState .PEN_IS_IN_RANGE_WITH_BUTTON :
1285
+ rs .extend (
1286
+ self .send_intermediate_state (pen , PenState .PEN_IS_IN_RANGE , button )
1287
+ )
1288
+
1289
+ rs .extend (super ().event (pen , button ))
1290
+ self .previous_state = pen .current_state
1291
+ return rs
1292
+
1293
+
1114
1294
################################################################################
1115
1295
#
1116
1296
# Windows 7 compatible devices
@@ -1312,3 +1492,14 @@ def create_device(self):
1312
1492
rdesc = "05 0d 09 02 a1 01 85 07 09 20 a1 00 09 42 09 44 09 45 15 00 25 01 75 01 95 03 81 02 95 02 81 03 09 32 95 01 81 02 95 02 81 03 75 10 95 01 35 00 a4 05 01 09 30 65 13 55 0d 46 f0 50 26 ff 7f 81 02 09 31 46 91 2d 26 ff 7f 81 02 b4 09 30 45 00 26 ff 1f 81 42 09 3d 15 81 25 7f 75 08 95 01 81 02 09 3e 15 81 25 7f 81 02 c0 c0" ,
1313
1493
input_info = (BusType .USB , 0x28BD , 0x093A ),
1314
1494
)
1495
+
1496
+
1497
+ class TestHuion_Kamvas_Pro_19_256c_006b (BaseTest .TestTablet ):
1498
+ hid_bpfs = [("Huion__Kamvas-Pro-19.bpf.o" , True )]
1499
+
1500
+ def create_device (self ):
1501
+ return Huion_Kamvas_Pro_19_256c_006b (
1502
+ "uhid test HUION Huion Tablet_GT1902" ,
1503
+ rdesc = "05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 43 09 3c 09 45 15 00 25 01 75 01 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff 7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 3f 75 10 95 01 81 02 09 3d 09 3e 15 a6 25 5a 75 08 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 75 08 95 08 81 03 85 05 09 55 25 0a 75 08 95 01 b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0" ,
1504
+ input_info = (BusType .USB , 0x256C , 0x006B ),
1505
+ )
0 commit comments