@@ -35,6 +35,7 @@ class BtnPressed(Enum):
3535
3636 PRIMARY_PRESSED = libevdev .EV_KEY .BTN_STYLUS
3737 SECONDARY_PRESSED = libevdev .EV_KEY .BTN_STYLUS2
38+ THIRD_PRESSED = libevdev .EV_KEY .BTN_STYLUS3
3839
3940
4041class PenState (Enum ):
@@ -503,6 +504,7 @@ def assert_expected_input_events(self, evdev, button):
503504 buttons = [
504505 BtnPressed .PRIMARY_PRESSED ,
505506 BtnPressed .SECONDARY_PRESSED ,
507+ BtnPressed .THIRD_PRESSED ,
506508 ]
507509 if button is not None :
508510 buttons .remove (button )
@@ -787,6 +789,27 @@ def test_valid_secondary_button_pen_states(self, state_list, scribble):
787789 button = BtnPressed .SECONDARY_PRESSED ,
788790 )
789791
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+
790813 @pytest .mark .skip_if_uhdev (
791814 lambda uhdev : "Invert" not in uhdev .fields ,
792815 "Device not compatible, missing Invert usage" ,
@@ -1111,6 +1134,163 @@ def event(self, pen, button):
11111134 return rs
11121135
11131136
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+
11141294################################################################################
11151295#
11161296# Windows 7 compatible devices
@@ -1312,3 +1492,14 @@ def create_device(self):
13121492 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" ,
13131493 input_info = (BusType .USB , 0x28BD , 0x093A ),
13141494 )
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