xref: /linux/tools/testing/selftests/hid/tests/test_tablet.py (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
1#!/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3# -*- coding: utf-8 -*-
4#
5# Copyright (c) 2021 Benjamin Tissoires <benjamin.tissoires@gmail.com>
6# Copyright (c) 2021 Red Hat, Inc.
7#
8
9from . import base
10import copy
11from enum import Enum
12from hidtools.util import BusType
13import libevdev
14import logging
15import pytest
16from typing import Dict, List, Optional, Tuple
17
18logger = logging.getLogger("hidtools.test.tablet")
19
20
21class BtnTouch(Enum):
22    """Represents whether the BTN_TOUCH event is set to True or False"""
23
24    DOWN = True
25    UP = False
26
27
28class ToolType(Enum):
29    PEN = libevdev.EV_KEY.BTN_TOOL_PEN
30    RUBBER = libevdev.EV_KEY.BTN_TOOL_RUBBER
31
32
33class 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    THIRD_PRESSED = libevdev.EV_KEY.BTN_STYLUS3
39
40
41class PenState(Enum):
42    """Pen states according to Microsoft reference:
43    https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
44
45    We extend it with the various buttons when we need to check them.
46    """
47
48    PEN_IS_OUT_OF_RANGE = BtnTouch.UP, None, False
49    PEN_IS_IN_RANGE = BtnTouch.UP, ToolType.PEN, False
50    PEN_IS_IN_RANGE_WITH_BUTTON = BtnTouch.UP, ToolType.PEN, True
51    PEN_IS_IN_CONTACT = BtnTouch.DOWN, ToolType.PEN, False
52    PEN_IS_IN_CONTACT_WITH_BUTTON = BtnTouch.DOWN, ToolType.PEN, True
53    PEN_IS_IN_RANGE_WITH_ERASING_INTENT = BtnTouch.UP, ToolType.RUBBER, False
54    PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON = BtnTouch.UP, ToolType.RUBBER, True
55    PEN_IS_ERASING = BtnTouch.DOWN, ToolType.RUBBER, False
56    PEN_IS_ERASING_WITH_BUTTON = BtnTouch.DOWN, ToolType.RUBBER, True
57
58    def __init__(
59        self, touch: BtnTouch, tool: Optional[ToolType], button: Optional[bool]
60    ):
61        self.touch = touch  # type: ignore
62        self.tool = tool  # type: ignore
63        self.button = button  # type: ignore
64
65    @classmethod
66    def from_evdev(cls, evdev, test_button) -> "PenState":
67        touch = BtnTouch(evdev.value[libevdev.EV_KEY.BTN_TOUCH])
68        tool = None
69        button = False
70        if (
71            evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
72            and not evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
73        ):
74            tool = ToolType(libevdev.EV_KEY.BTN_TOOL_RUBBER)
75        elif (
76            evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
77            and not evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
78        ):
79            tool = ToolType(libevdev.EV_KEY.BTN_TOOL_PEN)
80        elif (
81            evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
82            or evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
83        ):
84            raise ValueError("2 tools are not allowed")
85
86        # we take only the provided button into account
87        if test_button is not None:
88            button = bool(evdev.value[test_button.value])
89
90        # the kernel tends to insert an EV_SYN once removing the tool, so
91        # the button will be released after
92        if tool is None:
93            button = False
94
95        return cls((touch, tool, button))  # type: ignore
96
97    def apply(
98        self, events: List[libevdev.InputEvent], strict: bool, test_button: BtnPressed
99    ) -> "PenState":
100        if libevdev.EV_SYN.SYN_REPORT in events:
101            raise ValueError("EV_SYN is in the event sequence")
102        touch = self.touch
103        touch_found = False
104        tool = self.tool
105        tool_found = False
106        button = self.button
107        button_found = False
108
109        for ev in events:
110            if ev == libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH):
111                if touch_found:
112                    raise ValueError(f"duplicated BTN_TOUCH in {events}")
113                touch_found = True
114                touch = BtnTouch(ev.value)
115            elif ev in (
116                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN),
117                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_RUBBER),
118            ):
119                if tool_found:
120                    raise ValueError(f"duplicated BTN_TOOL_* in {events}")
121                tool_found = True
122                tool = ToolType(ev.code) if ev.value else None
123            elif test_button is not None and ev in (test_button.value,):
124                if button_found:
125                    raise ValueError(f"duplicated BTN_STYLUS* in {events}")
126                button_found = True
127                button = bool(ev.value)
128
129        # the kernel tends to insert an EV_SYN once removing the tool, so
130        # the button will be released after
131        if tool is None:
132            button = False
133
134        new_state = PenState((touch, tool, button))  # type: ignore
135        if strict:
136            assert (
137                new_state in self.valid_transitions()
138            ), f"moving from {self} to {new_state} is forbidden"
139        else:
140            assert (
141                new_state in self.historically_tolerated_transitions()
142            ), f"moving from {self} to {new_state} is forbidden"
143
144        return new_state
145
146    def valid_transitions(self) -> Tuple["PenState", ...]:
147        """Following the state machine in the URL above.
148
149        Note that those transitions are from the evdev point of view, not HID"""
150        if self == PenState.PEN_IS_OUT_OF_RANGE:
151            return (
152                PenState.PEN_IS_OUT_OF_RANGE,
153                PenState.PEN_IS_IN_RANGE,
154                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
155                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
156                PenState.PEN_IS_IN_CONTACT,
157                PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
158                PenState.PEN_IS_ERASING,
159            )
160
161        if self == PenState.PEN_IS_IN_RANGE:
162            return (
163                PenState.PEN_IS_IN_RANGE,
164                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
165                PenState.PEN_IS_OUT_OF_RANGE,
166                PenState.PEN_IS_IN_CONTACT,
167            )
168
169        if self == PenState.PEN_IS_IN_CONTACT:
170            return (
171                PenState.PEN_IS_IN_CONTACT,
172                PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
173                PenState.PEN_IS_IN_RANGE,
174            )
175
176        if self == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
177            return (
178                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
179                PenState.PEN_IS_OUT_OF_RANGE,
180                PenState.PEN_IS_ERASING,
181            )
182
183        if self == PenState.PEN_IS_ERASING:
184            return (
185                PenState.PEN_IS_ERASING,
186                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
187            )
188
189        if self == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
190            return (
191                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
192                PenState.PEN_IS_IN_RANGE,
193                PenState.PEN_IS_OUT_OF_RANGE,
194                PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
195            )
196
197        if self == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
198            return (
199                PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
200                PenState.PEN_IS_IN_CONTACT,
201                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
202            )
203
204        return tuple()
205
206    def historically_tolerated_transitions(self) -> Tuple["PenState", ...]:
207        """Following the state machine in the URL above, with a couple of addition
208        for skipping the in-range state, due to historical reasons.
209
210        Note that those transitions are from the evdev point of view, not HID"""
211        if self == PenState.PEN_IS_OUT_OF_RANGE:
212            return (
213                PenState.PEN_IS_OUT_OF_RANGE,
214                PenState.PEN_IS_IN_RANGE,
215                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
216                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
217                PenState.PEN_IS_IN_CONTACT,
218                PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
219                PenState.PEN_IS_ERASING,
220            )
221
222        if self == PenState.PEN_IS_IN_RANGE:
223            return (
224                PenState.PEN_IS_IN_RANGE,
225                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
226                PenState.PEN_IS_OUT_OF_RANGE,
227                PenState.PEN_IS_IN_CONTACT,
228            )
229
230        if self == PenState.PEN_IS_IN_CONTACT:
231            return (
232                PenState.PEN_IS_IN_CONTACT,
233                PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
234                PenState.PEN_IS_IN_RANGE,
235                PenState.PEN_IS_OUT_OF_RANGE,
236            )
237
238        if self == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
239            return (
240                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
241                PenState.PEN_IS_OUT_OF_RANGE,
242                PenState.PEN_IS_ERASING,
243            )
244
245        if self == PenState.PEN_IS_ERASING:
246            return (
247                PenState.PEN_IS_ERASING,
248                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
249                PenState.PEN_IS_OUT_OF_RANGE,
250            )
251
252        if self == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
253            return (
254                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
255                PenState.PEN_IS_IN_RANGE,
256                PenState.PEN_IS_OUT_OF_RANGE,
257                PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
258            )
259
260        if self == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
261            return (
262                PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
263                PenState.PEN_IS_IN_CONTACT,
264                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
265                PenState.PEN_IS_OUT_OF_RANGE,
266            )
267
268        return tuple()
269
270    @staticmethod
271    def legal_transitions() -> Dict[str, Tuple["PenState", ...]]:
272        """This is the first half of the Windows Pen Implementation state machine:
273        we don't have Invert nor Erase bits, so just move in/out-of-range or proximity.
274        https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
275        """
276        return {
277            "in-range": (PenState.PEN_IS_IN_RANGE,),
278            "in-range -> out-of-range": (
279                PenState.PEN_IS_IN_RANGE,
280                PenState.PEN_IS_OUT_OF_RANGE,
281            ),
282            "in-range -> touch": (PenState.PEN_IS_IN_RANGE, PenState.PEN_IS_IN_CONTACT),
283            "in-range -> touch -> release": (
284                PenState.PEN_IS_IN_RANGE,
285                PenState.PEN_IS_IN_CONTACT,
286                PenState.PEN_IS_IN_RANGE,
287            ),
288            "in-range -> touch -> release -> out-of-range": (
289                PenState.PEN_IS_IN_RANGE,
290                PenState.PEN_IS_IN_CONTACT,
291                PenState.PEN_IS_IN_RANGE,
292                PenState.PEN_IS_OUT_OF_RANGE,
293            ),
294        }
295
296    @staticmethod
297    def legal_transitions_with_invert() -> Dict[str, Tuple["PenState", ...]]:
298        """This is the second half of the Windows Pen Implementation state machine:
299        we now have Invert and Erase bits, so move in/out or proximity with the intend
300        to erase.
301        https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
302        """
303        return {
304            "hover-erasing": (PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,),
305            "hover-erasing -> out-of-range": (
306                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
307                PenState.PEN_IS_OUT_OF_RANGE,
308            ),
309            "hover-erasing -> erase": (
310                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
311                PenState.PEN_IS_ERASING,
312            ),
313            "hover-erasing -> erase -> release": (
314                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
315                PenState.PEN_IS_ERASING,
316                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
317            ),
318            "hover-erasing -> erase -> release -> out-of-range": (
319                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
320                PenState.PEN_IS_ERASING,
321                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
322                PenState.PEN_IS_OUT_OF_RANGE,
323            ),
324            "hover-erasing -> in-range": (
325                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
326                PenState.PEN_IS_IN_RANGE,
327            ),
328            "in-range -> hover-erasing": (
329                PenState.PEN_IS_IN_RANGE,
330                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
331            ),
332        }
333
334    @staticmethod
335    def legal_transitions_with_button() -> Dict[str, Tuple["PenState", ...]]:
336        """We revisit the Windows Pen Implementation state machine:
337        we now have a button.
338        """
339        return {
340            "hover-button": (PenState.PEN_IS_IN_RANGE_WITH_BUTTON,),
341            "hover-button -> out-of-range": (
342                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
343                PenState.PEN_IS_OUT_OF_RANGE,
344            ),
345            "in-range -> button-press": (
346                PenState.PEN_IS_IN_RANGE,
347                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
348            ),
349            "in-range -> button-press -> button-release": (
350                PenState.PEN_IS_IN_RANGE,
351                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
352                PenState.PEN_IS_IN_RANGE,
353            ),
354            "in-range -> touch -> button-press -> button-release": (
355                PenState.PEN_IS_IN_RANGE,
356                PenState.PEN_IS_IN_CONTACT,
357                PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
358                PenState.PEN_IS_IN_CONTACT,
359            ),
360            "in-range -> touch -> button-press -> release -> button-release": (
361                PenState.PEN_IS_IN_RANGE,
362                PenState.PEN_IS_IN_CONTACT,
363                PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
364                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
365                PenState.PEN_IS_IN_RANGE,
366            ),
367            "in-range -> button-press -> touch -> release -> button-release": (
368                PenState.PEN_IS_IN_RANGE,
369                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
370                PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
371                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
372                PenState.PEN_IS_IN_RANGE,
373            ),
374            "in-range -> button-press -> touch -> button-release -> release": (
375                PenState.PEN_IS_IN_RANGE,
376                PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
377                PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
378                PenState.PEN_IS_IN_CONTACT,
379                PenState.PEN_IS_IN_RANGE,
380            ),
381        }
382
383    @staticmethod
384    def tolerated_transitions() -> Dict[str, Tuple["PenState", ...]]:
385        """This is not adhering to the Windows Pen Implementation state machine
386        but we should expect the kernel to behave properly, mostly for historical
387        reasons."""
388        return {
389            "direct-in-contact": (PenState.PEN_IS_IN_CONTACT,),
390            "direct-in-contact -> out-of-range": (
391                PenState.PEN_IS_IN_CONTACT,
392                PenState.PEN_IS_OUT_OF_RANGE,
393            ),
394        }
395
396    @staticmethod
397    def tolerated_transitions_with_invert() -> Dict[str, Tuple["PenState", ...]]:
398        """This is the second half of the Windows Pen Implementation state machine:
399        we now have Invert and Erase bits, so move in/out or proximity with the intend
400        to erase.
401        https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
402        """
403        return {
404            "direct-erase": (PenState.PEN_IS_ERASING,),
405            "direct-erase -> out-of-range": (
406                PenState.PEN_IS_ERASING,
407                PenState.PEN_IS_OUT_OF_RANGE,
408            ),
409        }
410
411    @staticmethod
412    def broken_transitions() -> Dict[str, Tuple["PenState", ...]]:
413        """Those tests are definitely not part of the Windows specification.
414        However, a half broken device might export those transitions.
415        For example, a pen that has the eraser button might wobble between
416        touching and erasing if the tablet doesn't enforce the Windows
417        state machine."""
418        return {
419            "in-range -> touch -> erase -> hover-erase": (
420                PenState.PEN_IS_IN_RANGE,
421                PenState.PEN_IS_IN_CONTACT,
422                PenState.PEN_IS_ERASING,
423                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
424            ),
425            "in-range -> erase -> hover-erase": (
426                PenState.PEN_IS_IN_RANGE,
427                PenState.PEN_IS_ERASING,
428                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
429            ),
430            "hover-erase -> erase -> touch -> in-range": (
431                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
432                PenState.PEN_IS_ERASING,
433                PenState.PEN_IS_IN_CONTACT,
434                PenState.PEN_IS_IN_RANGE,
435            ),
436            "hover-erase -> touch -> in-range": (
437                PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
438                PenState.PEN_IS_IN_CONTACT,
439                PenState.PEN_IS_IN_RANGE,
440            ),
441            "touch -> erase -> touch -> erase": (
442                PenState.PEN_IS_IN_CONTACT,
443                PenState.PEN_IS_ERASING,
444                PenState.PEN_IS_IN_CONTACT,
445                PenState.PEN_IS_ERASING,
446            ),
447        }
448
449
450class Pen(object):
451    def __init__(self, x, y):
452        self.x = x
453        self.y = y
454        self.tipswitch = False
455        self.tippressure = 15
456        self.azimuth = 0
457        self.inrange = False
458        self.width = 10
459        self.height = 10
460        self.barrelswitch = False
461        self.secondarybarrelswitch = False
462        self.invert = False
463        self.eraser = False
464        self.xtilt = 1
465        self.ytilt = 1
466        self.twist = 1
467        self._old_values = None
468        self.current_state = None
469
470    def restore(self):
471        if self._old_values is not None:
472            for i in [
473                "x",
474                "y",
475                "tippressure",
476                "azimuth",
477                "width",
478                "height",
479                "twist",
480                "xtilt",
481                "ytilt",
482            ]:
483                setattr(self, i, getattr(self._old_values, i))
484
485    def backup(self):
486        self._old_values = copy.copy(self)
487
488    def __assert_axis(self, evdev, axis, value):
489        if (
490            axis == libevdev.EV_KEY.BTN_TOOL_RUBBER
491            and evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER] is None
492        ):
493            return
494
495        assert (
496            evdev.value[axis] == value
497        ), f"assert evdev.value[{axis}] ({evdev.value[axis]}) != {value}"
498
499    def assert_expected_input_events(self, evdev, button):
500        assert evdev.value[libevdev.EV_ABS.ABS_X] == self.x
501        assert evdev.value[libevdev.EV_ABS.ABS_Y] == self.y
502
503        # assert no other buttons than the tested ones are set
504        buttons = [
505            BtnPressed.PRIMARY_PRESSED,
506            BtnPressed.SECONDARY_PRESSED,
507            BtnPressed.THIRD_PRESSED,
508        ]
509        if button is not None:
510            buttons.remove(button)
511        for b in buttons:
512            assert evdev.value[b.value] is None or evdev.value[b.value] == False
513
514        assert self.current_state == PenState.from_evdev(evdev, button)
515
516
517class PenDigitizer(base.UHIDTestDevice):
518    def __init__(
519        self,
520        name,
521        rdesc_str=None,
522        rdesc=None,
523        application="Pen",
524        physical="Stylus",
525        input_info=(BusType.USB, 1, 2),
526        evdev_name_suffix=None,
527    ):
528        super().__init__(name, application, rdesc_str, rdesc, input_info)
529        self.physical = physical
530        self.cur_application = application
531        if evdev_name_suffix is not None:
532            self.name += evdev_name_suffix
533
534        self.fields = []
535        for r in self.parsed_rdesc.input_reports.values():
536            if r.application_name == self.application:
537                physicals = [f.physical_name for f in r]
538                if self.physical not in physicals and None not in physicals:
539                    continue
540                self.fields = [f.usage_name for f in r]
541
542    def move_to(self, pen, state, button):
543        # fill in the previous values
544        if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
545            pen.restore()
546
547        print(f"\n  *** pen is moving to {state} ***")
548
549        if state == PenState.PEN_IS_OUT_OF_RANGE:
550            pen.backup()
551            pen.x = 0
552            pen.y = 0
553            pen.tipswitch = False
554            pen.tippressure = 0
555            pen.azimuth = 0
556            pen.inrange = False
557            pen.width = 0
558            pen.height = 0
559            pen.invert = False
560            pen.eraser = False
561            pen.xtilt = 0
562            pen.ytilt = 0
563            pen.twist = 0
564            pen.barrelswitch = False
565            pen.secondarybarrelswitch = False
566        elif state == PenState.PEN_IS_IN_RANGE:
567            pen.tipswitch = False
568            pen.inrange = True
569            pen.invert = False
570            pen.eraser = False
571            pen.barrelswitch = False
572            pen.secondarybarrelswitch = False
573        elif state == PenState.PEN_IS_IN_CONTACT:
574            pen.tipswitch = True
575            pen.inrange = True
576            pen.invert = False
577            pen.eraser = False
578            pen.barrelswitch = False
579            pen.secondarybarrelswitch = False
580        elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
581            pen.tipswitch = False
582            pen.inrange = True
583            pen.invert = False
584            pen.eraser = False
585            assert button is not None
586            pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
587            pen.secondarybarrelswitch = button == BtnPressed.SECONDARY_PRESSED
588        elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
589            pen.tipswitch = True
590            pen.inrange = True
591            pen.invert = False
592            pen.eraser = False
593            assert button is not None
594            pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
595            pen.secondarybarrelswitch = button == BtnPressed.SECONDARY_PRESSED
596        elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
597            pen.tipswitch = False
598            pen.inrange = True
599            pen.invert = True
600            pen.eraser = False
601            pen.barrelswitch = False
602            pen.secondarybarrelswitch = False
603        elif state == PenState.PEN_IS_ERASING:
604            pen.tipswitch = False
605            pen.inrange = True
606            pen.invert = False
607            pen.eraser = True
608            pen.barrelswitch = False
609            pen.secondarybarrelswitch = False
610
611        pen.current_state = state
612
613    def event(self, pen, button):
614        rs = []
615        r = self.create_report(application=self.cur_application, data=pen)
616        self.call_input_event(r)
617        rs.append(r)
618        return rs
619
620    def get_report(self, req, rnum, rtype):
621        if rtype != self.UHID_FEATURE_REPORT:
622            return (1, [])
623
624        rdesc = None
625        for v in self.parsed_rdesc.feature_reports.values():
626            if v.report_ID == rnum:
627                rdesc = v
628
629        if rdesc is None:
630            return (1, [])
631
632        return (1, [])
633
634    def set_report(self, req, rnum, rtype, data):
635        if rtype != self.UHID_FEATURE_REPORT:
636            return 1
637
638        rdesc = None
639        for v in self.parsed_rdesc.feature_reports.values():
640            if v.report_ID == rnum:
641                rdesc = v
642
643        if rdesc is None:
644            return 1
645
646        return 1
647
648
649class BaseTest:
650    class TestTablet(base.BaseTestCase.TestUhid):
651        def create_device(self):
652            raise Exception("please reimplement me in subclasses")
653
654        def post(self, uhdev, pen, test_button):
655            r = uhdev.event(pen, test_button)
656            events = uhdev.next_sync_events()
657            self.debug_reports(r, uhdev, events)
658            return events
659
660        def validate_transitions(
661            self, from_state, pen, evdev, events, allow_intermediate_states, button
662        ):
663            # check that the final state is correct
664            pen.assert_expected_input_events(evdev, button)
665
666            state = from_state
667
668            # check that the transitions are valid
669            sync_events = []
670            while libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT) in events:
671                # split the first EV_SYN from the list
672                idx = events.index(libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT))
673                sync_events = events[:idx]
674                events = events[idx + 1 :]
675
676                # now check for a valid transition
677                state = state.apply(sync_events, not allow_intermediate_states, button)
678
679            if events:
680                state = state.apply(sync_events, not allow_intermediate_states, button)
681
682        def _test_states(
683            self, state_list, scribble, allow_intermediate_states, button=None
684        ):
685            """Internal method to test against a list of
686            transition between states.
687            state_list is a list of PenState objects
688            scribble is a boolean which tells if we need
689            to wobble a little the X,Y coordinates of the pen
690            between each state transition."""
691            uhdev = self.uhdev
692            evdev = uhdev.get_evdev()
693
694            cur_state = PenState.PEN_IS_OUT_OF_RANGE
695
696            p = Pen(50, 60)
697            uhdev.move_to(p, PenState.PEN_IS_OUT_OF_RANGE, button)
698            events = self.post(uhdev, p, button)
699            self.validate_transitions(
700                cur_state, p, evdev, events, allow_intermediate_states, button
701            )
702
703            cur_state = p.current_state
704
705            for state in state_list:
706                if scribble and cur_state != PenState.PEN_IS_OUT_OF_RANGE:
707                    p.x += 1
708                    p.y -= 1
709                    events = self.post(uhdev, p, button)
710                    self.validate_transitions(
711                        cur_state, p, evdev, events, allow_intermediate_states, button
712                    )
713                    assert len(events) >= 3  # X, Y, SYN
714                uhdev.move_to(p, state, button)
715                if scribble and state != PenState.PEN_IS_OUT_OF_RANGE:
716                    p.x += 1
717                    p.y -= 1
718                events = self.post(uhdev, p, button)
719                self.validate_transitions(
720                    cur_state, p, evdev, events, allow_intermediate_states, button
721                )
722                cur_state = p.current_state
723
724        @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
725        @pytest.mark.parametrize(
726            "state_list",
727            [pytest.param(v, id=k) for k, v in PenState.legal_transitions().items()],
728        )
729        def test_valid_pen_states(self, state_list, scribble):
730            """This is the first half of the Windows Pen Implementation state machine:
731            we don't have Invert nor Erase bits, so just move in/out-of-range or proximity.
732            https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
733            """
734            self._test_states(state_list, scribble, allow_intermediate_states=False)
735
736        @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
737        @pytest.mark.parametrize(
738            "state_list",
739            [
740                pytest.param(v, id=k)
741                for k, v in PenState.tolerated_transitions().items()
742            ],
743        )
744        def test_tolerated_pen_states(self, state_list, scribble):
745            """This is not adhering to the Windows Pen Implementation state machine
746            but we should expect the kernel to behave properly, mostly for historical
747            reasons."""
748            self._test_states(state_list, scribble, allow_intermediate_states=True)
749
750        @pytest.mark.skip_if_uhdev(
751            lambda uhdev: "Barrel Switch" not in uhdev.fields,
752            "Device not compatible, missing Barrel Switch usage",
753        )
754        @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
755        @pytest.mark.parametrize(
756            "state_list",
757            [
758                pytest.param(v, id=k)
759                for k, v in PenState.legal_transitions_with_button().items()
760            ],
761        )
762        def test_valid_primary_button_pen_states(self, state_list, scribble):
763            """Rework the transition state machine by adding the primary button."""
764            self._test_states(
765                state_list,
766                scribble,
767                allow_intermediate_states=False,
768                button=BtnPressed.PRIMARY_PRESSED,
769            )
770
771        @pytest.mark.skip_if_uhdev(
772            lambda uhdev: "Secondary Barrel Switch" not in uhdev.fields,
773            "Device not compatible, missing Secondary Barrel Switch usage",
774        )
775        @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
776        @pytest.mark.parametrize(
777            "state_list",
778            [
779                pytest.param(v, id=k)
780                for k, v in PenState.legal_transitions_with_button().items()
781            ],
782        )
783        def test_valid_secondary_button_pen_states(self, state_list, scribble):
784            """Rework the transition state machine by adding the secondary button."""
785            self._test_states(
786                state_list,
787                scribble,
788                allow_intermediate_states=False,
789                button=BtnPressed.SECONDARY_PRESSED,
790            )
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
813        @pytest.mark.skip_if_uhdev(
814            lambda uhdev: "Invert" not in uhdev.fields,
815            "Device not compatible, missing Invert usage",
816        )
817        @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
818        @pytest.mark.parametrize(
819            "state_list",
820            [
821                pytest.param(v, id=k)
822                for k, v in PenState.legal_transitions_with_invert().items()
823            ],
824        )
825        def test_valid_invert_pen_states(self, state_list, scribble):
826            """This is the second half of the Windows Pen Implementation state machine:
827            we now have Invert and Erase bits, so move in/out or proximity with the intend
828            to erase.
829            https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
830            """
831            self._test_states(state_list, scribble, allow_intermediate_states=False)
832
833        @pytest.mark.skip_if_uhdev(
834            lambda uhdev: "Invert" not in uhdev.fields,
835            "Device not compatible, missing Invert usage",
836        )
837        @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
838        @pytest.mark.parametrize(
839            "state_list",
840            [
841                pytest.param(v, id=k)
842                for k, v in PenState.tolerated_transitions_with_invert().items()
843            ],
844        )
845        def test_tolerated_invert_pen_states(self, state_list, scribble):
846            """This is the second half of the Windows Pen Implementation state machine:
847            we now have Invert and Erase bits, so move in/out or proximity with the intend
848            to erase.
849            https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
850            """
851            self._test_states(state_list, scribble, allow_intermediate_states=True)
852
853        @pytest.mark.skip_if_uhdev(
854            lambda uhdev: "Invert" not in uhdev.fields,
855            "Device not compatible, missing Invert usage",
856        )
857        @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
858        @pytest.mark.parametrize(
859            "state_list",
860            [pytest.param(v, id=k) for k, v in PenState.broken_transitions().items()],
861        )
862        def test_tolerated_broken_pen_states(self, state_list, scribble):
863            """Those tests are definitely not part of the Windows specification.
864            However, a half broken device might export those transitions.
865            For example, a pen that has the eraser button might wobble between
866            touching and erasing if the tablet doesn't enforce the Windows
867            state machine."""
868            self._test_states(state_list, scribble, allow_intermediate_states=True)
869
870
871class GXTP_pen(PenDigitizer):
872    def event(self, pen, test_button):
873        if not hasattr(self, "prev_tip_state"):
874            self.prev_tip_state = False
875
876        internal_pen = copy.copy(pen)
877
878        # bug in the controller: when the pen touches the
879        # surface, in-range stays to 1, but when
880        # the pen moves in-range gets reverted to 0
881        if pen.tipswitch and self.prev_tip_state:
882            internal_pen.inrange = False
883
884        self.prev_tip_state = pen.tipswitch
885
886        # another bug in the controller: when the pen is
887        # inverted, invert is set to 1, but as soon as
888        # the pen touches the surface, eraser is correctly
889        # set to 1 but invert is released
890        if pen.eraser:
891            internal_pen.invert = False
892
893        return super().event(internal_pen, test_button)
894
895
896class USIPen(PenDigitizer):
897    pass
898
899
900class XPPen_ArtistPro16Gen2_28bd_095b(PenDigitizer):
901    """
902    Pen with two buttons and a rubber end, but which reports
903    the second button as an eraser
904    """
905
906    def __init__(
907        self,
908        name,
909        rdesc_str=None,
910        rdesc=None,
911        application="Pen",
912        physical="Stylus",
913        input_info=(BusType.USB, 0x28BD, 0x095B),
914        evdev_name_suffix=None,
915    ):
916        super().__init__(
917            name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix
918        )
919        self.fields.append("Secondary Barrel Switch")
920
921    def move_to(self, pen, state, button):
922        # fill in the previous values
923        if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
924            pen.restore()
925
926        print(f"\n  *** pen is moving to {state} ***")
927
928        if state == PenState.PEN_IS_OUT_OF_RANGE:
929            pen.backup()
930            pen.x = 0
931            pen.y = 0
932            pen.tipswitch = False
933            pen.tippressure = 0
934            pen.azimuth = 0
935            pen.inrange = False
936            pen.width = 0
937            pen.height = 0
938            pen.invert = False
939            pen.eraser = False
940            pen.xtilt = 0
941            pen.ytilt = 0
942            pen.twist = 0
943            pen.barrelswitch = False
944        elif state == PenState.PEN_IS_IN_RANGE:
945            pen.tipswitch = False
946            pen.inrange = True
947            pen.invert = False
948            pen.eraser = False
949            pen.barrelswitch = False
950        elif state == PenState.PEN_IS_IN_CONTACT:
951            pen.tipswitch = True
952            pen.inrange = True
953            pen.invert = False
954            pen.eraser = False
955            pen.barrelswitch = False
956        elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
957            pen.tipswitch = False
958            pen.inrange = True
959            pen.invert = False
960            assert button is not None
961            pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
962            pen.eraser = button == BtnPressed.SECONDARY_PRESSED
963        elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
964            pen.tipswitch = True
965            pen.inrange = True
966            pen.invert = False
967            assert button is not None
968            pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
969            pen.eraser = button == BtnPressed.SECONDARY_PRESSED
970        elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
971            pen.tipswitch = False
972            pen.inrange = True
973            pen.invert = True
974            pen.eraser = False
975            pen.barrelswitch = False
976        elif state == PenState.PEN_IS_ERASING:
977            pen.tipswitch = True
978            pen.inrange = True
979            pen.invert = True
980            pen.eraser = False
981            pen.barrelswitch = False
982
983        pen.current_state = state
984
985    def event(self, pen, test_button):
986        import math
987
988        pen_copy = copy.copy(pen)
989        width = 13.567
990        height = 8.480
991        tip_height = 0.055677699
992        hx = tip_height * (32767 / width)
993        hy = tip_height * (32767 / height)
994        if pen_copy.xtilt != 0:
995            pen_copy.x += round(hx * math.sin(math.radians(pen_copy.xtilt)))
996        if pen_copy.ytilt != 0:
997            pen_copy.y += round(hy * math.sin(math.radians(pen_copy.ytilt)))
998
999        return super().event(pen_copy, test_button)
1000
1001
1002class XPPen_Artist24_28bd_093a(PenDigitizer):
1003    """
1004    Pen that reports secondary barrel switch through eraser
1005    """
1006
1007    def __init__(
1008        self,
1009        name,
1010        rdesc_str=None,
1011        rdesc=None,
1012        application="Pen",
1013        physical="Stylus",
1014        input_info=(BusType.USB, 0x28BD, 0x093A),
1015        evdev_name_suffix=None,
1016    ):
1017        super().__init__(
1018            name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix
1019        )
1020        self.fields.append("Secondary Barrel Switch")
1021        self.previous_state = PenState.PEN_IS_OUT_OF_RANGE
1022
1023    def move_to(self, pen, state, button, debug=True):
1024        # fill in the previous values
1025        if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
1026            pen.restore()
1027
1028        if debug:
1029            print(f"\n  *** pen is moving to {state} ***")
1030
1031        if state == PenState.PEN_IS_OUT_OF_RANGE:
1032            pen.backup()
1033            pen.tipswitch = False
1034            pen.tippressure = 0
1035            pen.azimuth = 0
1036            pen.inrange = False
1037            pen.width = 0
1038            pen.height = 0
1039            pen.invert = False
1040            pen.eraser = False
1041            pen.xtilt = 0
1042            pen.ytilt = 0
1043            pen.twist = 0
1044            pen.barrelswitch = False
1045        elif state == PenState.PEN_IS_IN_RANGE:
1046            pen.tipswitch = False
1047            pen.inrange = True
1048            pen.invert = False
1049            pen.eraser = False
1050            pen.barrelswitch = False
1051        elif state == PenState.PEN_IS_IN_CONTACT:
1052            pen.tipswitch = True
1053            pen.inrange = True
1054            pen.invert = False
1055            pen.eraser = False
1056            pen.barrelswitch = False
1057        elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
1058            pen.tipswitch = False
1059            pen.inrange = True
1060            pen.invert = False
1061            assert button is not None
1062            pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
1063            pen.eraser = button == BtnPressed.SECONDARY_PRESSED
1064        elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
1065            pen.tipswitch = True
1066            pen.inrange = True
1067            pen.invert = False
1068            assert button is not None
1069            pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
1070            pen.eraser = button == BtnPressed.SECONDARY_PRESSED
1071
1072        pen.current_state = state
1073
1074    def send_intermediate_state(self, pen, state, button):
1075        intermediate_pen = copy.copy(pen)
1076        self.move_to(intermediate_pen, state, button, debug=False)
1077        return super().event(intermediate_pen, button)
1078
1079    def event(self, pen, button):
1080        rs = []
1081
1082        # the pen reliably sends in-range events in a normal case (non emulation of eraser mode)
1083        if self.previous_state == PenState.PEN_IS_IN_CONTACT:
1084            if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
1085                rs.extend(
1086                    self.send_intermediate_state(pen, PenState.PEN_IS_IN_RANGE, button)
1087                )
1088
1089        if button == BtnPressed.SECONDARY_PRESSED:
1090            if self.previous_state == PenState.PEN_IS_IN_RANGE:
1091                if pen.current_state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
1092                    rs.extend(
1093                        self.send_intermediate_state(
1094                            pen, PenState.PEN_IS_OUT_OF_RANGE, button
1095                        )
1096                    )
1097
1098            if self.previous_state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
1099                if pen.current_state == PenState.PEN_IS_IN_RANGE:
1100                    rs.extend(
1101                        self.send_intermediate_state(
1102                            pen, PenState.PEN_IS_OUT_OF_RANGE, button
1103                        )
1104                    )
1105
1106            if self.previous_state == PenState.PEN_IS_IN_CONTACT:
1107                if pen.current_state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
1108                    rs.extend(
1109                        self.send_intermediate_state(
1110                            pen, PenState.PEN_IS_OUT_OF_RANGE, button
1111                        )
1112                    )
1113                    rs.extend(
1114                        self.send_intermediate_state(
1115                            pen, PenState.PEN_IS_IN_RANGE_WITH_BUTTON, button
1116                        )
1117                    )
1118
1119            if self.previous_state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
1120                if pen.current_state == PenState.PEN_IS_IN_CONTACT:
1121                    rs.extend(
1122                        self.send_intermediate_state(
1123                            pen, PenState.PEN_IS_OUT_OF_RANGE, button
1124                        )
1125                    )
1126                    rs.extend(
1127                        self.send_intermediate_state(
1128                            pen, PenState.PEN_IS_IN_RANGE, button
1129                        )
1130                    )
1131
1132        rs.extend(super().event(pen, button))
1133        self.previous_state = pen.current_state
1134        return rs
1135
1136
1137class 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
1294################################################################################
1295#
1296# Windows 7 compatible devices
1297#
1298################################################################################
1299# class TestEgalax_capacitive_0eef_7224(BaseTest.TestTablet):
1300#     def create_device(self):
1301#         return PenDigitizer('uhid test egalax-capacitive_0eef_7224',
1302#                             rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
1303#                             input_info=(BusType.USB, 0x0eef, 0x7224),
1304#                             evdev_name_suffix=' Touchscreen')
1305#
1306#
1307# class TestEgalax_capacitive_0eef_72fa(BaseTest.TestTablet):
1308#     def create_device(self):
1309#         return PenDigitizer('uhid test egalax-capacitive_0eef_72fa',
1310#                             rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 72 22 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 87 13 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 72 22 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 87 13 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
1311#                             input_info=(BusType.USB, 0x0eef, 0x72fa),
1312#                             evdev_name_suffix=' Touchscreen')
1313#
1314#
1315# class TestEgalax_capacitive_0eef_7336(BaseTest.TestTablet):
1316#     def create_device(self):
1317#         return PenDigitizer('uhid test egalax-capacitive_0eef_7336',
1318#                             rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 c1 20 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c2 18 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 c1 20 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c2 18 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
1319#                             input_info=(BusType.USB, 0x0eef, 0x7336),
1320#                             evdev_name_suffix=' Touchscreen')
1321#
1322#
1323# class TestEgalax_capacitive_0eef_7337(BaseTest.TestTablet):
1324#     def create_device(self):
1325#         return PenDigitizer('uhid test egalax-capacitive_0eef_7337',
1326#                             rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 ae 17 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c3 0e 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 ae 17 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c3 0e 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
1327#                             input_info=(BusType.USB, 0x0eef, 0x7337),
1328#                             evdev_name_suffix=' Touchscreen')
1329#
1330#
1331# class TestEgalax_capacitive_0eef_7349(BaseTest.TestTablet):
1332#     def create_device(self):
1333#         return PenDigitizer('uhid test egalax-capacitive_0eef_7349',
1334#                             rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
1335#                             input_info=(BusType.USB, 0x0eef, 0x7349),
1336#                             evdev_name_suffix=' Touchscreen')
1337#
1338#
1339# class TestEgalax_capacitive_0eef_73f4(BaseTest.TestTablet):
1340#     def create_device(self):
1341#         return PenDigitizer('uhid test egalax-capacitive_0eef_73f4',
1342#                             rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 96 4e 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 23 2c 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 96 4e 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 23 2c 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
1343#                             input_info=(BusType.USB, 0x0eef, 0x73f4),
1344#                             evdev_name_suffix=' Touchscreen')
1345#
1346#  bogus: BTN_TOOL_PEN is not emitted
1347# class TestIrtouch_6615_0070(BaseTest.TestTablet):
1348#     def create_device(self):
1349#         return PenDigitizer('uhid test irtouch_6615_0070',
1350#                             rdesc='05 01 09 02 a1 01 85 10 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 09 30 09 31 15 00 26 ff 7f 75 10 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 30 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 05 0d 09 54 15 00 26 02 00 75 08 95 01 81 02 85 03 09 55 15 00 26 ff 00 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 02 09 52 09 53 15 00 26 ff 00 75 08 95 02 b1 02 c0 05 0d 09 02 a1 01 85 20 09 20 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 85 01 06 00 ff 09 01 75 08 95 01 b1 02 c0 c0',
1351#                             input_info=(BusType.USB, 0x6615, 0x0070))
1352
1353
1354class TestNexio_1870_0100(BaseTest.TestTablet):
1355    def create_device(self):
1356        return PenDigitizer(
1357            "uhid test nexio_1870_0100",
1358            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 02 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 40 81 00 19 01 29 40 91 00 c0",
1359            input_info=(BusType.USB, 0x1870, 0x0100),
1360        )
1361
1362
1363class TestNexio_1870_010d(BaseTest.TestTablet):
1364    def create_device(self):
1365        return PenDigitizer(
1366            "uhid test nexio_1870_010d",
1367            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0",
1368            input_info=(BusType.USB, 0x1870, 0x010D),
1369        )
1370
1371
1372class TestNexio_1870_0119(BaseTest.TestTablet):
1373    def create_device(self):
1374        return PenDigitizer(
1375            "uhid test nexio_1870_0119",
1376            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0",
1377            input_info=(BusType.USB, 0x1870, 0x0119),
1378        )
1379
1380
1381################################################################################
1382#
1383# Windows 8 compatible devices
1384#
1385################################################################################
1386
1387# bogus: application is 'undefined'
1388# class Testatmel_03eb_8409(BaseTest.TestTablet):
1389#     def create_device(self):
1390#         return PenDigitizer('uhid test atmel_03eb_8409', rdesc='05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 00 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 46 18 06 26 77 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0')
1391
1392
1393class Testatmel_03eb_840b(BaseTest.TestTablet):
1394    def create_device(self):
1395        return PenDigitizer(
1396            "uhid test atmel_03eb_840b",
1397            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 00 0a 26 ff 0f 09 30 81 02 46 a0 05 26 ff 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0",
1398        )
1399
1400
1401class Testn_trig_1b96_0c01(BaseTest.TestTablet):
1402    def create_device(self):
1403        return PenDigitizer(
1404            "uhid test n_trig_1b96_0c01",
1405            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
1406        )
1407
1408
1409class Testn_trig_1b96_0c03(BaseTest.TestTablet):
1410    def create_device(self):
1411        return PenDigitizer(
1412            "uhid test n_trig_1b96_0c03",
1413            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
1414        )
1415
1416
1417class Testn_trig_1b96_0f00(BaseTest.TestTablet):
1418    def create_device(self):
1419        return PenDigitizer(
1420            "uhid test n_trig_1b96_0f00",
1421            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
1422        )
1423
1424
1425class Testn_trig_1b96_0f04(BaseTest.TestTablet):
1426    def create_device(self):
1427        return PenDigitizer(
1428            "uhid test n_trig_1b96_0f04",
1429            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
1430        )
1431
1432
1433class Testn_trig_1b96_1000(BaseTest.TestTablet):
1434    def create_device(self):
1435        return PenDigitizer(
1436            "uhid test n_trig_1b96_1000",
1437            rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
1438        )
1439
1440
1441class TestGXTP_27c6_0113(BaseTest.TestTablet):
1442    def create_device(self):
1443        return GXTP_pen(
1444            "uhid test GXTP_27c6_0113",
1445            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 55 0e 65 11 35 00 15 00 09 42 25 01 75 01 95 01 81 02 95 07 81 01 95 01 75 08 09 51 81 02 75 10 05 01 26 00 14 46 1f 07 09 30 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 95 07 81 01 95 01 75 08 09 51 81 02 75 10 05 01 26 00 14 46 1f 07 09 30 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 95 07 81 01 95 01 75 08 09 51 81 02 75 10 05 01 26 00 14 46 1f 07 09 30 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 14 75 10 55 0e 65 11 09 30 35 00 46 1f 07 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 14 75 10 55 0e 65 11 09 30 35 00 46 1f 07 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 54 15 00 25 7f 75 08 95 01 81 02 85 02 09 55 95 01 25 0a b1 02 85 03 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 85 08 09 20 a1 00 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 04 81 02 95 01 81 03 09 32 81 02 95 02 81 03 95 01 75 08 09 51 81 02 05 01 09 30 75 10 95 01 a4 55 0e 65 11 35 00 26 00 14 46 1f 07 81 42 09 31 26 80 0c 46 77 04 81 42 b4 05 0d 09 30 26 ff 0f 81 02 09 3d 65 14 55 0e 36 d8 dc 46 28 23 16 d8 dc 26 28 23 81 02 09 3e 81 02 c0 c0 06 f0 ff 09 01 a1 01 85 0e 09 01 15 00 25 ff 75 08 95 40 91 02 09 01 15 00 25 ff 75 08 95 40 81 02 c0 05 01 09 06 a1 01 85 04 05 07 09 e3 15 00 25 01 75 01 95 01 81 02 95 07 81 03 c0",
1446        )
1447
1448
1449################################################################################
1450#
1451# Windows 8 compatible devices with USI Pen
1452#
1453################################################################################
1454
1455
1456class TestElan_04f3_2A49(BaseTest.TestTablet):
1457    def create_device(self):
1458        return USIPen(
1459            "uhid test Elan_04f3_2A49",
1460            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 54 25 7f 96 01 00 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 16 00 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 16 00 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 20 09 01 91 02 c0 06 00 ff 09 01 a1 01 85 06 09 03 75 08 95 12 91 02 09 04 75 08 95 03 b1 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0 05 0d 09 02 a1 01 85 07 35 00 09 20 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0f 65 11 46 26 01 26 1c 48 81 42 09 31 46 a6 00 26 bc 2f 81 42 b4 05 0d 09 30 26 00 10 81 02 75 08 95 01 09 3b 25 64 81 42 09 38 15 00 25 02 81 02 09 5c 26 ff 00 81 02 09 5e 81 02 09 70 a1 02 15 01 25 06 09 72 09 73 09 74 09 75 09 76 09 77 81 20 09 5b 25 ff 75 40 81 02 c0 06 00 ff 75 08 95 02 09 01 81 02 c0 05 0d 85 60 09 81 a1 02 09 38 75 08 95 01 15 00 25 02 81 02 09 81 15 01 25 04 09 82 09 83 09 84 09 85 81 20 c0 85 61 09 5c a1 02 15 00 26 ff 00 75 08 95 01 09 38 b1 02 09 5c 26 ff 00 b1 02 09 5d 75 01 95 01 25 01 b1 02 95 07 b1 03 c0 85 62 09 5e a1 02 09 38 15 00 25 02 75 08 95 01 b1 02 09 5e 26 ff 00 b1 02 09 5f 75 01 25 01 b1 02 75 07 b1 03 c0 85 63 09 70 a1 02 75 08 95 01 15 00 25 02 09 38 b1 02 09 70 a1 02 25 06 09 72 09 73 09 74 09 75 09 76 09 77 b1 20 c0 09 71 75 01 25 01 b1 02 75 07 b1 03 c0 85 64 09 80 15 00 25 ff 75 40 95 01 b1 02 85 65 09 44 a1 02 09 38 75 08 95 01 25 02 b1 02 15 01 25 03 09 44 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 5a a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 45 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 c0 85 66 75 08 95 01 05 0d 09 90 a1 02 09 38 25 02 b1 02 09 91 75 10 26 ff 0f b1 02 09 92 75 40 25 ff b1 02 05 06 09 2a 75 08 26 ff 00 a1 02 09 2d b1 02 09 2e b1 02 c0 c0 85 67 05 06 09 2b a1 02 05 0d 25 02 09 38 b1 02 05 06 09 2b a1 02 09 2d 26 ff 00 b1 02 09 2e b1 02 c0 c0 85 68 06 00 ff 09 01 a1 02 05 0d 09 38 75 08 95 01 25 02 b1 02 06 00 ff 09 01 75 10 27 ff ff 00 00 b1 02 c0 85 69 05 0d 09 38 75 08 95 01 15 00 25 02 b1 02 c0 06 00 ff 09 81 a1 01 85 17 75 08 95 1f 09 05 81 02 c0",
1461            input_info=(BusType.I2C, 0x04F3, 0x2A49),
1462        )
1463
1464
1465class TestGoodix_27c6_0e00(BaseTest.TestTablet):
1466    def create_device(self):
1467        return USIPen(
1468            "uhid test Elan_04f3_2A49",
1469            rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 55 0e 65 11 35 00 15 00 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 75 08 09 51 95 01 81 02 05 01 26 04 20 75 10 55 0e 65 11 09 30 35 00 46 e6 09 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 75 08 09 51 95 01 81 02 05 01 26 04 20 75 10 55 0e 65 11 09 30 35 00 46 e6 09 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 54 15 00 25 7f 75 08 95 01 81 02 85 02 09 55 95 01 25 0a b1 02 85 03 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 09 20 a1 00 85 08 05 01 a4 09 30 35 00 46 e6 09 15 00 26 04 20 55 0d 65 13 75 10 95 01 81 02 09 31 46 9a 06 26 60 15 81 02 b4 05 0d 09 38 95 01 75 08 15 00 25 01 81 02 09 30 75 10 26 ff 0f 81 02 09 31 81 02 09 42 09 44 09 5a 09 3c 09 45 09 32 75 01 95 06 25 01 81 02 95 02 81 03 09 3d 55 0e 65 14 36 d8 dc 46 28 23 16 d8 dc 26 28 23 95 01 75 10 81 02 09 3e 81 02 09 41 15 00 27 a0 8c 00 00 35 00 47 a0 8c 00 00 81 02 05 20 0a 53 04 65 00 16 01 f8 26 ff 07 75 10 95 01 81 02 0a 54 04 81 02 0a 55 04 81 02 0a 57 04 81 02 0a 58 04 81 02 0a 59 04 81 02 0a 72 04 81 02 0a 73 04 81 02 0a 74 04 81 02 05 0d 09 3b 15 00 25 64 75 08 81 02 09 5b 25 ff 75 40 81 02 06 00 ff 09 5b 75 20 81 02 05 0d 09 5c 26 ff 00 75 08 81 02 09 5e 81 02 09 70 a1 02 15 01 25 06 09 72 09 73 09 74 09 75 09 76 09 77 81 20 c0 06 00 ff 09 01 15 00 27 ff ff 00 00 75 10 95 01 81 02 85 09 09 81 a1 02 09 81 15 01 25 04 09 82 09 83 09 84 09 85 81 20 c0 85 10 09 5c a1 02 15 00 25 01 75 08 95 01 09 38 b1 02 09 5c 26 ff 00 b1 02 09 5d 75 01 95 01 25 01 b1 02 95 07 b1 03 c0 85 11 09 5e a1 02 09 38 15 00 25 01 75 08 95 01 b1 02 09 5e 26 ff 00 b1 02 09 5f 75 01 25 01 b1 02 75 07 b1 03 c0 85 12 09 70 a1 02 75 08 95 01 15 00 25 01 09 38 b1 02 09 70 a1 02 25 06 09 72 09 73 09 74 09 75 09 76 09 77 b1 20 c0 09 71 75 01 25 01 b1 02 75 07 b1 03 c0 85 13 09 80 15 00 25 ff 75 40 95 01 b1 02 85 14 09 44 a1 02 09 38 75 08 95 01 25 01 b1 02 15 01 25 03 09 44 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 5a a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 45 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 c0 85 15 75 08 95 01 05 0d 09 90 a1 02 09 38 25 01 b1 02 09 91 75 10 26 ff 0f b1 02 09 92 75 40 25 ff b1 02 05 06 09 2a 75 08 26 ff 00 a1 02 09 2d b1 02 09 2e b1 02 c0 c0 85 16 05 06 09 2b a1 02 05 0d 25 01 09 38 b1 02 05 06 09 2b a1 02 09 2d 26 ff 00 b1 02 09 2e b1 02 c0 c0 85 17 06 00 ff 09 01 a1 02 05 0d 09 38 75 08 95 01 25 01 b1 02 06 00 ff 09 01 75 10 27 ff ff 00 00 b1 02 c0 85 18 05 0d 09 38 75 08 95 01 15 00 25 01 b1 02 c0 c0 06 f0 ff 09 01 a1 01 85 0e 09 01 15 00 25 ff 75 08 95 40 91 02 09 01 15 00 25 ff 75 08 95 40 81 02 c0",
1470            input_info=(BusType.I2C, 0x27C6, 0x0E00),
1471        )
1472
1473
1474class TestXPPen_ArtistPro16Gen2_28bd_095b(BaseTest.TestTablet):
1475    hid_bpfs = [("XPPen__ArtistPro16Gen2.bpf.o", True)]
1476
1477    def create_device(self):
1478        dev = XPPen_ArtistPro16Gen2_28bd_095b(
1479            "uhid test XPPen Artist Pro 16 Gen2 28bd 095b",
1480            rdesc="05 0d 09 02 a1 01 85 07 09 20 a1 00 09 42 09 44 09 45 09 3c 15 00 25 01 75 01 95 04 81 02 95 01 81 03 09 32 15 00 25 01 95 01 81 02 95 02 81 03 75 10 95 01 35 00 a4 05 01 09 30 65 13 55 0d 46 ff 34 26 ff 7f 81 02 09 31 46 20 21 26 ff 7f 81 02 b4 09 30 45 00 26 ff 3f 81 42 09 3d 15 81 25 7f 75 08 95 01 81 02 09 3e 15 81 25 7f 81 02 c0 c0",
1481            input_info=(BusType.USB, 0x28BD, 0x095B),
1482        )
1483        return dev
1484
1485
1486class TestXPPen_Artist24_28bd_093a(BaseTest.TestTablet):
1487    hid_bpfs = [("XPPen__Artist24.bpf.o", True)]
1488
1489    def create_device(self):
1490        return XPPen_Artist24_28bd_093a(
1491            "uhid test XPPen Artist 24 28bd 093a",
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",
1493            input_info=(BusType.USB, 0x28BD, 0x093A),
1494        )
1495
1496
1497class 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        )
1506