Skip to content

Commit 51de9ee

Browse files
author
Benjamin Tissoires
committed
selftests/hid: add Huion Kamvas Pro 19 tests
This tablets gets a lot of things wrong: - the secondary button is reported through Secondary Tip Switch - the third button is reported through Invert We need to add some out of proximity intermediate state when moving back and forth with the eraser mode as it can only be triggered by physically returning the pen, meaning that the tolerated transitions can never happen. Link: https://lore.kernel.org/r/[email protected] Reviewed-by: Peter Hutterer <[email protected]> Signed-off-by: Benjamin Tissoires <[email protected]>
1 parent 1b2c3ca commit 51de9ee

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed

tools/testing/selftests/hid/tests/test_tablet.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

4041
class 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

Comments
 (0)