1#!/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3# -*- coding: utf-8 -*-
4#
5# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
6# Copyright (c) 2017 Red Hat, Inc.
7#
8
9from . import base
10import hidtools.hid
11from hidtools.util import BusType
12import libevdev
13import logging
14import pytest
15
16logger = logging.getLogger("hidtools.test.mouse")
17
18# workaround https://gitlab.freedesktop.org/libevdev/python-libevdev/issues/6
19try:
20    libevdev.EV_REL.REL_WHEEL_HI_RES
21except AttributeError:
22    libevdev.EV_REL.REL_WHEEL_HI_RES = libevdev.EV_REL.REL_0B
23    libevdev.EV_REL.REL_HWHEEL_HI_RES = libevdev.EV_REL.REL_0C
24
25
26class InvalidHIDCommunication(Exception):
27    pass
28
29
30class MouseData(object):
31    pass
32
33
34class BaseMouse(base.UHIDTestDevice):
35    def __init__(self, rdesc, name=None, input_info=None):
36        assert rdesc is not None
37        super().__init__(name, "Mouse", input_info=input_info, rdesc=rdesc)
38        self.left = False
39        self.right = False
40        self.middle = False
41
42    def create_report(self, x, y, buttons=None, wheels=None, reportID=None):
43        """
44        Return an input report for this device.
45
46        :param x: relative x
47        :param y: relative y
48        :param buttons: a (l, r, m) tuple of bools for the button states,
49            where ``None`` is "leave unchanged"
50        :param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for
51            the two wheels
52        :param reportID: the numeric report ID for this report, if needed
53        """
54        if buttons is not None:
55            l, r, m = buttons
56            if l is not None:
57                self.left = l
58            if r is not None:
59                self.right = r
60            if m is not None:
61                self.middle = m
62        left = self.left
63        right = self.right
64        middle = self.middle
65        # Note: the BaseMouse doesn't actually have a wheel but the
66        # create_report magic only fills in those fields exist, so let's
67        # make this generic here.
68        wheel, acpan = 0, 0
69        if wheels is not None:
70            if isinstance(wheels, tuple):
71                wheel = wheels[0]
72                acpan = wheels[1]
73            else:
74                wheel = wheels
75
76        reportID = reportID or self.default_reportID
77
78        mouse = MouseData()
79        mouse.b1 = int(left)
80        mouse.b2 = int(right)
81        mouse.b3 = int(middle)
82        mouse.x = x
83        mouse.y = y
84        mouse.wheel = wheel
85        mouse.acpan = acpan
86        return super().create_report(mouse, reportID=reportID)
87
88    def event(self, x, y, buttons=None, wheels=None):
89        """
90        Send an input event on the default report ID.
91
92        :param x: relative x
93        :param y: relative y
94        :param buttons: a (l, r, m) tuple of bools for the button states,
95            where ``None`` is "leave unchanged"
96        :param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for
97            the two wheels
98        """
99        r = self.create_report(x, y, buttons, wheels)
100        self.call_input_event(r)
101        return [r]
102
103
104class ButtonMouse(BaseMouse):
105    # fmt: off
106    report_descriptor = [
107        0x05, 0x01,  # .Usage Page (Generic Desktop)        0
108        0x09, 0x02,  # .Usage (Mouse)                       2
109        0xa1, 0x01,  # .Collection (Application)            4
110        0x09, 0x02,  # ..Usage (Mouse)                      6
111        0xa1, 0x02,  # ..Collection (Logical)               8
112        0x09, 0x01,  # ...Usage (Pointer)                   10
113        0xa1, 0x00,  # ...Collection (Physical)             12
114        0x05, 0x09,  # ....Usage Page (Button)              14
115        0x19, 0x01,  # ....Usage Minimum (1)                16
116        0x29, 0x03,  # ....Usage Maximum (3)                18
117        0x15, 0x00,  # ....Logical Minimum (0)              20
118        0x25, 0x01,  # ....Logical Maximum (1)              22
119        0x75, 0x01,  # ....Report Size (1)                  24
120        0x95, 0x03,  # ....Report Count (3)                 26
121        0x81, 0x02,  # ....Input (Data,Var,Abs)             28
122        0x75, 0x05,  # ....Report Size (5)                  30
123        0x95, 0x01,  # ....Report Count (1)                 32
124        0x81, 0x03,  # ....Input (Cnst,Var,Abs)             34
125        0x05, 0x01,  # ....Usage Page (Generic Desktop)     36
126        0x09, 0x30,  # ....Usage (X)                        38
127        0x09, 0x31,  # ....Usage (Y)                        40
128        0x15, 0x81,  # ....Logical Minimum (-127)           42
129        0x25, 0x7f,  # ....Logical Maximum (127)            44
130        0x75, 0x08,  # ....Report Size (8)                  46
131        0x95, 0x02,  # ....Report Count (2)                 48
132        0x81, 0x06,  # ....Input (Data,Var,Rel)             50
133        0xc0,        # ...End Collection                    52
134        0xc0,        # ..End Collection                     53
135        0xc0,        # .End Collection                      54
136    ]
137    # fmt: on
138
139    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
140        super().__init__(rdesc, name, input_info)
141
142    def fake_report(self, x, y, buttons):
143        if buttons is not None:
144            left, right, middle = buttons
145            if left is None:
146                left = self.left
147            if right is None:
148                right = self.right
149            if middle is None:
150                middle = self.middle
151        else:
152            left = self.left
153            right = self.right
154            middle = self.middle
155
156        button_mask = sum(1 << i for i, b in enumerate([left, right, middle]) if b)
157        x = max(-127, min(127, x))
158        y = max(-127, min(127, y))
159        x = hidtools.util.to_twos_comp(x, 8)
160        y = hidtools.util.to_twos_comp(y, 8)
161        return [button_mask, x, y]
162
163
164class WheelMouse(ButtonMouse):
165    # fmt: off
166    report_descriptor = [
167        0x05, 0x01,  # Usage Page (Generic Desktop)        0
168        0x09, 0x02,  # Usage (Mouse)                       2
169        0xa1, 0x01,  # Collection (Application)            4
170        0x05, 0x09,  # .Usage Page (Button)                6
171        0x19, 0x01,  # .Usage Minimum (1)                  8
172        0x29, 0x03,  # .Usage Maximum (3)                  10
173        0x15, 0x00,  # .Logical Minimum (0)                12
174        0x25, 0x01,  # .Logical Maximum (1)                14
175        0x95, 0x03,  # .Report Count (3)                   16
176        0x75, 0x01,  # .Report Size (1)                    18
177        0x81, 0x02,  # .Input (Data,Var,Abs)               20
178        0x95, 0x01,  # .Report Count (1)                   22
179        0x75, 0x05,  # .Report Size (5)                    24
180        0x81, 0x03,  # .Input (Cnst,Var,Abs)               26
181        0x05, 0x01,  # .Usage Page (Generic Desktop)       28
182        0x09, 0x01,  # .Usage (Pointer)                    30
183        0xa1, 0x00,  # .Collection (Physical)              32
184        0x09, 0x30,  # ..Usage (X)                         34
185        0x09, 0x31,  # ..Usage (Y)                         36
186        0x15, 0x81,  # ..Logical Minimum (-127)            38
187        0x25, 0x7f,  # ..Logical Maximum (127)             40
188        0x75, 0x08,  # ..Report Size (8)                   42
189        0x95, 0x02,  # ..Report Count (2)                  44
190        0x81, 0x06,  # ..Input (Data,Var,Rel)              46
191        0xc0,        # .End Collection                     48
192        0x09, 0x38,  # .Usage (Wheel)                      49
193        0x15, 0x81,  # .Logical Minimum (-127)             51
194        0x25, 0x7f,  # .Logical Maximum (127)              53
195        0x75, 0x08,  # .Report Size (8)                    55
196        0x95, 0x01,  # .Report Count (1)                   57
197        0x81, 0x06,  # .Input (Data,Var,Rel)               59
198        0xc0,        # End Collection                      61
199    ]
200    # fmt: on
201
202    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
203        super().__init__(rdesc, name, input_info)
204        self.wheel_multiplier = 1
205
206
207class TwoWheelMouse(WheelMouse):
208    # fmt: off
209    report_descriptor = [
210        0x05, 0x01,        # Usage Page (Generic Desktop)        0
211        0x09, 0x02,        # Usage (Mouse)                       2
212        0xa1, 0x01,        # Collection (Application)            4
213        0x09, 0x01,        # .Usage (Pointer)                    6
214        0xa1, 0x00,        # .Collection (Physical)              8
215        0x05, 0x09,        # ..Usage Page (Button)               10
216        0x19, 0x01,        # ..Usage Minimum (1)                 12
217        0x29, 0x10,        # ..Usage Maximum (16)                14
218        0x15, 0x00,        # ..Logical Minimum (0)               16
219        0x25, 0x01,        # ..Logical Maximum (1)               18
220        0x95, 0x10,        # ..Report Count (16)                 20
221        0x75, 0x01,        # ..Report Size (1)                   22
222        0x81, 0x02,        # ..Input (Data,Var,Abs)              24
223        0x05, 0x01,        # ..Usage Page (Generic Desktop)      26
224        0x16, 0x01, 0x80,  # ..Logical Minimum (-32767)          28
225        0x26, 0xff, 0x7f,  # ..Logical Maximum (32767)           31
226        0x75, 0x10,        # ..Report Size (16)                  34
227        0x95, 0x02,        # ..Report Count (2)                  36
228        0x09, 0x30,        # ..Usage (X)                         38
229        0x09, 0x31,        # ..Usage (Y)                         40
230        0x81, 0x06,        # ..Input (Data,Var,Rel)              42
231        0x15, 0x81,        # ..Logical Minimum (-127)            44
232        0x25, 0x7f,        # ..Logical Maximum (127)             46
233        0x75, 0x08,        # ..Report Size (8)                   48
234        0x95, 0x01,        # ..Report Count (1)                  50
235        0x09, 0x38,        # ..Usage (Wheel)                     52
236        0x81, 0x06,        # ..Input (Data,Var,Rel)              54
237        0x05, 0x0c,        # ..Usage Page (Consumer Devices)     56
238        0x0a, 0x38, 0x02,  # ..Usage (AC Pan)                    58
239        0x95, 0x01,        # ..Report Count (1)                  61
240        0x81, 0x06,        # ..Input (Data,Var,Rel)              63
241        0xc0,              # .End Collection                     65
242        0xc0,              # End Collection                      66
243    ]
244    # fmt: on
245
246    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
247        super().__init__(rdesc, name, input_info)
248        self.hwheel_multiplier = 1
249
250
251class MIDongleMIWirelessMouse(TwoWheelMouse):
252    # fmt: off
253    report_descriptor = [
254        0x05, 0x01,         # Usage Page (Generic Desktop)
255        0x09, 0x02,         # Usage (Mouse)
256        0xa1, 0x01,         # Collection (Application)
257        0x85, 0x01,         # .Report ID (1)
258        0x09, 0x01,         # .Usage (Pointer)
259        0xa1, 0x00,         # .Collection (Physical)
260        0x95, 0x05,         # ..Report Count (5)
261        0x75, 0x01,         # ..Report Size (1)
262        0x05, 0x09,         # ..Usage Page (Button)
263        0x19, 0x01,         # ..Usage Minimum (1)
264        0x29, 0x05,         # ..Usage Maximum (5)
265        0x15, 0x00,         # ..Logical Minimum (0)
266        0x25, 0x01,         # ..Logical Maximum (1)
267        0x81, 0x02,         # ..Input (Data,Var,Abs)
268        0x95, 0x01,         # ..Report Count (1)
269        0x75, 0x03,         # ..Report Size (3)
270        0x81, 0x01,         # ..Input (Cnst,Arr,Abs)
271        0x75, 0x08,         # ..Report Size (8)
272        0x95, 0x01,         # ..Report Count (1)
273        0x05, 0x01,         # ..Usage Page (Generic Desktop)
274        0x09, 0x38,         # ..Usage (Wheel)
275        0x15, 0x81,         # ..Logical Minimum (-127)
276        0x25, 0x7f,         # ..Logical Maximum (127)
277        0x81, 0x06,         # ..Input (Data,Var,Rel)
278        0x05, 0x0c,         # ..Usage Page (Consumer Devices)
279        0x0a, 0x38, 0x02,   # ..Usage (AC Pan)
280        0x95, 0x01,         # ..Report Count (1)
281        0x81, 0x06,         # ..Input (Data,Var,Rel)
282        0xc0,               # .End Collection
283        0x85, 0x02,         # .Report ID (2)
284        0x09, 0x01,         # .Usage (Consumer Control)
285        0xa1, 0x00,         # .Collection (Physical)
286        0x75, 0x0c,         # ..Report Size (12)
287        0x95, 0x02,         # ..Report Count (2)
288        0x05, 0x01,         # ..Usage Page (Generic Desktop)
289        0x09, 0x30,         # ..Usage (X)
290        0x09, 0x31,         # ..Usage (Y)
291        0x16, 0x01, 0xf8,   # ..Logical Minimum (-2047)
292        0x26, 0xff, 0x07,   # ..Logical Maximum (2047)
293        0x81, 0x06,         # ..Input (Data,Var,Rel)
294        0xc0,               # .End Collection
295        0xc0,               # End Collection
296        0x05, 0x0c,         # Usage Page (Consumer Devices)
297        0x09, 0x01,         # Usage (Consumer Control)
298        0xa1, 0x01,         # Collection (Application)
299        0x85, 0x03,         # .Report ID (3)
300        0x15, 0x00,         # .Logical Minimum (0)
301        0x25, 0x01,         # .Logical Maximum (1)
302        0x75, 0x01,         # .Report Size (1)
303        0x95, 0x01,         # .Report Count (1)
304        0x09, 0xcd,         # .Usage (Play/Pause)
305        0x81, 0x06,         # .Input (Data,Var,Rel)
306        0x0a, 0x83, 0x01,   # .Usage (AL Consumer Control Config)
307        0x81, 0x06,         # .Input (Data,Var,Rel)
308        0x09, 0xb5,         # .Usage (Scan Next Track)
309        0x81, 0x06,         # .Input (Data,Var,Rel)
310        0x09, 0xb6,         # .Usage (Scan Previous Track)
311        0x81, 0x06,         # .Input (Data,Var,Rel)
312        0x09, 0xea,         # .Usage (Volume Down)
313        0x81, 0x06,         # .Input (Data,Var,Rel)
314        0x09, 0xe9,         # .Usage (Volume Up)
315        0x81, 0x06,         # .Input (Data,Var,Rel)
316        0x0a, 0x25, 0x02,   # .Usage (AC Forward)
317        0x81, 0x06,         # .Input (Data,Var,Rel)
318        0x0a, 0x24, 0x02,   # .Usage (AC Back)
319        0x81, 0x06,         # .Input (Data,Var,Rel)
320        0xc0,               # End Collection
321    ]
322    # fmt: on
323    device_input_info = (BusType.USB, 0x2717, 0x003B)
324    device_name = "uhid test MI Dongle MI Wireless Mouse"
325
326    def __init__(
327        self, rdesc=report_descriptor, name=device_name, input_info=device_input_info
328    ):
329        super().__init__(rdesc, name, input_info)
330
331    def event(self, x, y, buttons=None, wheels=None):
332        # this mouse spreads the relative pointer and the mouse buttons
333        # onto 2 distinct reports
334        rs = []
335        r = self.create_report(x, y, buttons, wheels, reportID=1)
336        self.call_input_event(r)
337        rs.append(r)
338        r = self.create_report(x, y, buttons, reportID=2)
339        self.call_input_event(r)
340        rs.append(r)
341        return rs
342
343
344class ResolutionMultiplierMouse(TwoWheelMouse):
345    # fmt: off
346    report_descriptor = [
347        0x05, 0x01,        # Usage Page (Generic Desktop)        83
348        0x09, 0x02,        # Usage (Mouse)                       85
349        0xa1, 0x01,        # Collection (Application)            87
350        0x05, 0x01,        # .Usage Page (Generic Desktop)       89
351        0x09, 0x02,        # .Usage (Mouse)                      91
352        0xa1, 0x02,        # .Collection (Logical)               93
353        0x85, 0x11,        # ..Report ID (17)                    95
354        0x09, 0x01,        # ..Usage (Pointer)                   97
355        0xa1, 0x00,        # ..Collection (Physical)             99
356        0x05, 0x09,        # ...Usage Page (Button)              101
357        0x19, 0x01,        # ...Usage Minimum (1)                103
358        0x29, 0x03,        # ...Usage Maximum (3)                105
359        0x95, 0x03,        # ...Report Count (3)                 107
360        0x75, 0x01,        # ...Report Size (1)                  109
361        0x25, 0x01,        # ...Logical Maximum (1)              111
362        0x81, 0x02,        # ...Input (Data,Var,Abs)             113
363        0x95, 0x01,        # ...Report Count (1)                 115
364        0x81, 0x01,        # ...Input (Cnst,Arr,Abs)             117
365        0x09, 0x05,        # ...Usage (Vendor Usage 0x05)        119
366        0x81, 0x02,        # ...Input (Data,Var,Abs)             121
367        0x95, 0x03,        # ...Report Count (3)                 123
368        0x81, 0x01,        # ...Input (Cnst,Arr,Abs)             125
369        0x05, 0x01,        # ...Usage Page (Generic Desktop)     127
370        0x09, 0x30,        # ...Usage (X)                        129
371        0x09, 0x31,        # ...Usage (Y)                        131
372        0x95, 0x02,        # ...Report Count (2)                 133
373        0x75, 0x08,        # ...Report Size (8)                  135
374        0x15, 0x81,        # ...Logical Minimum (-127)           137
375        0x25, 0x7f,        # ...Logical Maximum (127)            139
376        0x81, 0x06,        # ...Input (Data,Var,Rel)             141
377        0xa1, 0x02,        # ...Collection (Logical)             143
378        0x85, 0x12,        # ....Report ID (18)                  145
379        0x09, 0x48,        # ....Usage (Resolution Multiplier)   147
380        0x95, 0x01,        # ....Report Count (1)                149
381        0x75, 0x02,        # ....Report Size (2)                 151
382        0x15, 0x00,        # ....Logical Minimum (0)             153
383        0x25, 0x01,        # ....Logical Maximum (1)             155
384        0x35, 0x01,        # ....Physical Minimum (1)            157
385        0x45, 0x04,        # ....Physical Maximum (4)            159
386        0xb1, 0x02,        # ....Feature (Data,Var,Abs)          161
387        0x35, 0x00,        # ....Physical Minimum (0)            163
388        0x45, 0x00,        # ....Physical Maximum (0)            165
389        0x75, 0x06,        # ....Report Size (6)                 167
390        0xb1, 0x01,        # ....Feature (Cnst,Arr,Abs)          169
391        0x85, 0x11,        # ....Report ID (17)                  171
392        0x09, 0x38,        # ....Usage (Wheel)                   173
393        0x15, 0x81,        # ....Logical Minimum (-127)          175
394        0x25, 0x7f,        # ....Logical Maximum (127)           177
395        0x75, 0x08,        # ....Report Size (8)                 179
396        0x81, 0x06,        # ....Input (Data,Var,Rel)            181
397        0xc0,              # ...End Collection                   183
398        0x05, 0x0c,        # ...Usage Page (Consumer Devices)    184
399        0x75, 0x08,        # ...Report Size (8)                  186
400        0x0a, 0x38, 0x02,  # ...Usage (AC Pan)                   188
401        0x81, 0x06,        # ...Input (Data,Var,Rel)             191
402        0xc0,              # ..End Collection                    193
403        0xc0,              # .End Collection                     194
404        0xc0,              # End Collection                      195
405    ]
406    # fmt: on
407
408    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
409        super().__init__(rdesc, name, input_info)
410        self.default_reportID = 0x11
411
412        # Feature Report 12, multiplier Feature value must be set to 0b01,
413        # i.e. 1. We should extract that from the descriptor instead
414        # of hardcoding it here, but meanwhile this will do.
415        self.set_feature_report = [0x12, 0x1]
416
417    def set_report(self, req, rnum, rtype, data):
418        if rtype != self.UHID_FEATURE_REPORT:
419            raise InvalidHIDCommunication(f"Unexpected report type: {rtype}")
420        if rnum != 0x12:
421            raise InvalidHIDCommunication(f"Unexpected report number: {rnum}")
422
423        if data != self.set_feature_report:
424            raise InvalidHIDCommunication(
425                f"Unexpected data: {data}, expected {self.set_feature_report}"
426            )
427
428        self.wheel_multiplier = 4
429
430        return 0
431
432
433class BadResolutionMultiplierMouse(ResolutionMultiplierMouse):
434    def set_report(self, req, rnum, rtype, data):
435        super().set_report(req, rnum, rtype, data)
436
437        self.wheel_multiplier = 1
438        self.hwheel_multiplier = 1
439        return 32  # EPIPE
440
441
442class ResolutionMultiplierHWheelMouse(TwoWheelMouse):
443    # fmt: off
444    report_descriptor = [
445        0x05, 0x01,         # Usage Page (Generic Desktop)        0
446        0x09, 0x02,         # Usage (Mouse)                       2
447        0xa1, 0x01,         # Collection (Application)            4
448        0x05, 0x01,         # .Usage Page (Generic Desktop)       6
449        0x09, 0x02,         # .Usage (Mouse)                      8
450        0xa1, 0x02,         # .Collection (Logical)               10
451        0x85, 0x1a,         # ..Report ID (26)                    12
452        0x09, 0x01,         # ..Usage (Pointer)                   14
453        0xa1, 0x00,         # ..Collection (Physical)             16
454        0x05, 0x09,         # ...Usage Page (Button)              18
455        0x19, 0x01,         # ...Usage Minimum (1)                20
456        0x29, 0x05,         # ...Usage Maximum (5)                22
457        0x95, 0x05,         # ...Report Count (5)                 24
458        0x75, 0x01,         # ...Report Size (1)                  26
459        0x15, 0x00,         # ...Logical Minimum (0)              28
460        0x25, 0x01,         # ...Logical Maximum (1)              30
461        0x81, 0x02,         # ...Input (Data,Var,Abs)             32
462        0x75, 0x03,         # ...Report Size (3)                  34
463        0x95, 0x01,         # ...Report Count (1)                 36
464        0x81, 0x01,         # ...Input (Cnst,Arr,Abs)             38
465        0x05, 0x01,         # ...Usage Page (Generic Desktop)     40
466        0x09, 0x30,         # ...Usage (X)                        42
467        0x09, 0x31,         # ...Usage (Y)                        44
468        0x95, 0x02,         # ...Report Count (2)                 46
469        0x75, 0x10,         # ...Report Size (16)                 48
470        0x16, 0x01, 0x80,   # ...Logical Minimum (-32767)         50
471        0x26, 0xff, 0x7f,   # ...Logical Maximum (32767)          53
472        0x81, 0x06,         # ...Input (Data,Var,Rel)             56
473        0xa1, 0x02,         # ...Collection (Logical)             58
474        0x85, 0x12,         # ....Report ID (18)                  60
475        0x09, 0x48,         # ....Usage (Resolution Multiplier)   62
476        0x95, 0x01,         # ....Report Count (1)                64
477        0x75, 0x02,         # ....Report Size (2)                 66
478        0x15, 0x00,         # ....Logical Minimum (0)             68
479        0x25, 0x01,         # ....Logical Maximum (1)             70
480        0x35, 0x01,         # ....Physical Minimum (1)            72
481        0x45, 0x0c,         # ....Physical Maximum (12)           74
482        0xb1, 0x02,         # ....Feature (Data,Var,Abs)          76
483        0x85, 0x1a,         # ....Report ID (26)                  78
484        0x09, 0x38,         # ....Usage (Wheel)                   80
485        0x35, 0x00,         # ....Physical Minimum (0)            82
486        0x45, 0x00,         # ....Physical Maximum (0)            84
487        0x95, 0x01,         # ....Report Count (1)                86
488        0x75, 0x10,         # ....Report Size (16)                88
489        0x16, 0x01, 0x80,   # ....Logical Minimum (-32767)        90
490        0x26, 0xff, 0x7f,   # ....Logical Maximum (32767)         93
491        0x81, 0x06,         # ....Input (Data,Var,Rel)            96
492        0xc0,               # ...End Collection                   98
493        0xa1, 0x02,         # ...Collection (Logical)             99
494        0x85, 0x12,         # ....Report ID (18)                  101
495        0x09, 0x48,         # ....Usage (Resolution Multiplier)   103
496        0x75, 0x02,         # ....Report Size (2)                 105
497        0x15, 0x00,         # ....Logical Minimum (0)             107
498        0x25, 0x01,         # ....Logical Maximum (1)             109
499        0x35, 0x01,         # ....Physical Minimum (1)            111
500        0x45, 0x0c,         # ....Physical Maximum (12)           113
501        0xb1, 0x02,         # ....Feature (Data,Var,Abs)          115
502        0x35, 0x00,         # ....Physical Minimum (0)            117
503        0x45, 0x00,         # ....Physical Maximum (0)            119
504        0x75, 0x04,         # ....Report Size (4)                 121
505        0xb1, 0x01,         # ....Feature (Cnst,Arr,Abs)          123
506        0x85, 0x1a,         # ....Report ID (26)                  125
507        0x05, 0x0c,         # ....Usage Page (Consumer Devices)   127
508        0x95, 0x01,         # ....Report Count (1)                129
509        0x75, 0x10,         # ....Report Size (16)                131
510        0x16, 0x01, 0x80,   # ....Logical Minimum (-32767)        133
511        0x26, 0xff, 0x7f,   # ....Logical Maximum (32767)         136
512        0x0a, 0x38, 0x02,   # ....Usage (AC Pan)                  139
513        0x81, 0x06,         # ....Input (Data,Var,Rel)            142
514        0xc0,               # ...End Collection                   144
515        0xc0,               # ..End Collection                    145
516        0xc0,               # .End Collection                     146
517        0xc0,               # End Collection                      147
518    ]
519    # fmt: on
520
521    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
522        super().__init__(rdesc, name, input_info)
523        self.default_reportID = 0x1A
524
525        # Feature Report 12, multiplier Feature value must be set to 0b0101,
526        # i.e. 5. We should extract that from the descriptor instead
527        # of hardcoding it here, but meanwhile this will do.
528        self.set_feature_report = [0x12, 0x5]
529
530    def set_report(self, req, rnum, rtype, data):
531        super().set_report(req, rnum, rtype, data)
532
533        self.wheel_multiplier = 12
534        self.hwheel_multiplier = 12
535
536        return 0
537
538
539class BaseTest:
540    class TestMouse(base.BaseTestCase.TestUhid):
541        def test_buttons(self):
542            """check for button reliability."""
543            uhdev = self.uhdev
544            evdev = uhdev.get_evdev()
545            syn_event = self.syn_event
546
547            r = uhdev.event(0, 0, (None, True, None))
548            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1)
549            events = uhdev.next_sync_events()
550            self.debug_reports(r, uhdev, events)
551            self.assertInputEventsIn((syn_event, expected_event), events)
552            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
553
554            r = uhdev.event(0, 0, (None, False, None))
555            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0)
556            events = uhdev.next_sync_events()
557            self.debug_reports(r, uhdev, events)
558            self.assertInputEventsIn((syn_event, expected_event), events)
559            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
560
561            r = uhdev.event(0, 0, (None, None, True))
562            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 1)
563            events = uhdev.next_sync_events()
564            self.debug_reports(r, uhdev, events)
565            self.assertInputEventsIn((syn_event, expected_event), events)
566            assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 1
567
568            r = uhdev.event(0, 0, (None, None, False))
569            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 0)
570            events = uhdev.next_sync_events()
571            self.debug_reports(r, uhdev, events)
572            self.assertInputEventsIn((syn_event, expected_event), events)
573            assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 0
574
575            r = uhdev.event(0, 0, (True, None, None))
576            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1)
577            events = uhdev.next_sync_events()
578            self.debug_reports(r, uhdev, events)
579            self.assertInputEventsIn((syn_event, expected_event), events)
580            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
581
582            r = uhdev.event(0, 0, (False, None, None))
583            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0)
584            events = uhdev.next_sync_events()
585            self.debug_reports(r, uhdev, events)
586            self.assertInputEventsIn((syn_event, expected_event), events)
587            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
588
589            r = uhdev.event(0, 0, (True, True, None))
590            expected_event0 = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1)
591            expected_event1 = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1)
592            events = uhdev.next_sync_events()
593            self.debug_reports(r, uhdev, events)
594            self.assertInputEventsIn(
595                (syn_event, expected_event0, expected_event1), events
596            )
597            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
598            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
599
600            r = uhdev.event(0, 0, (False, None, None))
601            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0)
602            events = uhdev.next_sync_events()
603            self.debug_reports(r, uhdev, events)
604            self.assertInputEventsIn((syn_event, expected_event), events)
605            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
606            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
607
608            r = uhdev.event(0, 0, (None, False, None))
609            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0)
610            events = uhdev.next_sync_events()
611            self.debug_reports(r, uhdev, events)
612            self.assertInputEventsIn((syn_event, expected_event), events)
613            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
614            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
615
616        def test_relative(self):
617            """Check for relative events."""
618            uhdev = self.uhdev
619
620            syn_event = self.syn_event
621
622            r = uhdev.event(0, -1)
623            expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_Y, -1)
624            events = uhdev.next_sync_events()
625            self.debug_reports(r, uhdev, events)
626            self.assertInputEvents((syn_event, expected_event), events)
627
628            r = uhdev.event(1, 0)
629            expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_X, 1)
630            events = uhdev.next_sync_events()
631            self.debug_reports(r, uhdev, events)
632            self.assertInputEvents((syn_event, expected_event), events)
633
634            r = uhdev.event(-1, 2)
635            expected_event0 = libevdev.InputEvent(libevdev.EV_REL.REL_X, -1)
636            expected_event1 = libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2)
637            events = uhdev.next_sync_events()
638            self.debug_reports(r, uhdev, events)
639            self.assertInputEvents(
640                (syn_event, expected_event0, expected_event1), events
641            )
642
643
644class TestSimpleMouse(BaseTest.TestMouse):
645    def create_device(self):
646        return ButtonMouse()
647
648    def test_rdesc(self):
649        """Check that the testsuite actually manages to format the
650        reports according to the report descriptors.
651        No kernel device is used here"""
652        uhdev = self.uhdev
653
654        event = (0, 0, (None, None, None))
655        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
656
657        event = (0, 0, (None, True, None))
658        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
659
660        event = (0, 0, (True, True, None))
661        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
662
663        event = (0, 0, (False, False, False))
664        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
665
666        event = (1, 0, (True, False, True))
667        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
668
669        event = (-1, 0, (True, False, True))
670        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
671
672        event = (-5, 5, (True, False, True))
673        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
674
675        event = (-127, 127, (True, False, True))
676        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
677
678        event = (0, -128, (True, False, True))
679        with pytest.raises(hidtools.hid.RangeError):
680            uhdev.create_report(*event)
681
682
683class TestWheelMouse(BaseTest.TestMouse):
684    def create_device(self):
685        return WheelMouse()
686
687    def is_wheel_highres(self, uhdev):
688        evdev = uhdev.get_evdev()
689        assert evdev.has(libevdev.EV_REL.REL_WHEEL)
690        return evdev.has(libevdev.EV_REL.REL_WHEEL_HI_RES)
691
692    def test_wheel(self):
693        uhdev = self.uhdev
694
695        # check if the kernel is high res wheel compatible
696        high_res_wheel = self.is_wheel_highres(uhdev)
697
698        syn_event = self.syn_event
699        # The Resolution Multiplier is applied to the HID reports, so we
700        # need to pre-multiply too.
701        mult = uhdev.wheel_multiplier
702
703        r = uhdev.event(0, 0, wheels=1 * mult)
704        expected = [syn_event]
705        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1))
706        if high_res_wheel:
707            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120))
708        events = uhdev.next_sync_events()
709        self.debug_reports(r, uhdev, events)
710        self.assertInputEvents(expected, events)
711
712        r = uhdev.event(0, 0, wheels=-1 * mult)
713        expected = [syn_event]
714        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -1))
715        if high_res_wheel:
716            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120))
717        events = uhdev.next_sync_events()
718        self.debug_reports(r, uhdev, events)
719        self.assertInputEvents(expected, events)
720
721        r = uhdev.event(-1, 2, wheels=3 * mult)
722        expected = [syn_event]
723        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
724        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
725        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 3))
726        if high_res_wheel:
727            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 360))
728        events = uhdev.next_sync_events()
729        self.debug_reports(r, uhdev, events)
730        self.assertInputEvents(expected, events)
731
732
733class TestTwoWheelMouse(TestWheelMouse):
734    def create_device(self):
735        return TwoWheelMouse()
736
737    def is_hwheel_highres(self, uhdev):
738        evdev = uhdev.get_evdev()
739        assert evdev.has(libevdev.EV_REL.REL_HWHEEL)
740        return evdev.has(libevdev.EV_REL.REL_HWHEEL_HI_RES)
741
742    def test_ac_pan(self):
743        uhdev = self.uhdev
744
745        # check if the kernel is high res wheel compatible
746        high_res_wheel = self.is_wheel_highres(uhdev)
747        high_res_hwheel = self.is_hwheel_highres(uhdev)
748        assert high_res_wheel == high_res_hwheel
749
750        syn_event = self.syn_event
751        # The Resolution Multiplier is applied to the HID reports, so we
752        # need to pre-multiply too.
753        hmult = uhdev.hwheel_multiplier
754        vmult = uhdev.wheel_multiplier
755
756        r = uhdev.event(0, 0, wheels=(0, 1 * hmult))
757        expected = [syn_event]
758        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1))
759        if high_res_hwheel:
760            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120))
761        events = uhdev.next_sync_events()
762        self.debug_reports(r, uhdev, events)
763        self.assertInputEvents(expected, events)
764
765        r = uhdev.event(0, 0, wheels=(0, -1 * hmult))
766        expected = [syn_event]
767        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, -1))
768        if high_res_hwheel:
769            expected.append(
770                libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120)
771            )
772        events = uhdev.next_sync_events()
773        self.debug_reports(r, uhdev, events)
774        self.assertInputEvents(expected, events)
775
776        r = uhdev.event(-1, 2, wheels=(0, 3 * hmult))
777        expected = [syn_event]
778        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
779        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
780        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 3))
781        if high_res_hwheel:
782            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 360))
783        events = uhdev.next_sync_events()
784        self.debug_reports(r, uhdev, events)
785        self.assertInputEvents(expected, events)
786
787        r = uhdev.event(-1, 2, wheels=(-3 * vmult, 4 * hmult))
788        expected = [syn_event]
789        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
790        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
791        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -3))
792        if high_res_wheel:
793            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -360))
794        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 4))
795        if high_res_wheel:
796            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 480))
797        events = uhdev.next_sync_events()
798        self.debug_reports(r, uhdev, events)
799        self.assertInputEvents(expected, events)
800
801
802class TestResolutionMultiplierMouse(TestTwoWheelMouse):
803    def create_device(self):
804        return ResolutionMultiplierMouse()
805
806    def is_wheel_highres(self, uhdev):
807        high_res = super().is_wheel_highres(uhdev)
808
809        if not high_res:
810            # the kernel doesn't seem to support the high res wheel mice,
811            # make sure we haven't triggered the feature
812            assert uhdev.wheel_multiplier == 1
813
814        return high_res
815
816    def test_resolution_multiplier_wheel(self):
817        uhdev = self.uhdev
818
819        if not self.is_wheel_highres(uhdev):
820            pytest.skip("Kernel not compatible, we can not trigger the conditions")
821
822        assert uhdev.wheel_multiplier > 1
823        assert 120 % uhdev.wheel_multiplier == 0
824
825    def test_wheel_with_multiplier(self):
826        uhdev = self.uhdev
827
828        if not self.is_wheel_highres(uhdev):
829            pytest.skip("Kernel not compatible, we can not trigger the conditions")
830
831        assert uhdev.wheel_multiplier > 1
832
833        syn_event = self.syn_event
834        mult = uhdev.wheel_multiplier
835
836        r = uhdev.event(0, 0, wheels=1)
837        expected = [syn_event]
838        expected.append(
839            libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult)
840        )
841        events = uhdev.next_sync_events()
842        self.debug_reports(r, uhdev, events)
843        self.assertInputEvents(expected, events)
844
845        r = uhdev.event(0, 0, wheels=-1)
846        expected = [syn_event]
847        expected.append(
848            libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120 / mult)
849        )
850        events = uhdev.next_sync_events()
851        self.debug_reports(r, uhdev, events)
852        self.assertInputEvents(expected, events)
853
854        expected = [syn_event]
855        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1))
856        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2))
857        expected.append(
858            libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult)
859        )
860
861        for _ in range(mult - 1):
862            r = uhdev.event(1, -2, wheels=1)
863            events = uhdev.next_sync_events()
864            self.debug_reports(r, uhdev, events)
865            self.assertInputEvents(expected, events)
866
867        r = uhdev.event(1, -2, wheels=1)
868        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1))
869        events = uhdev.next_sync_events()
870        self.debug_reports(r, uhdev, events)
871        self.assertInputEvents(expected, events)
872
873
874class TestBadResolutionMultiplierMouse(TestTwoWheelMouse):
875    def create_device(self):
876        return BadResolutionMultiplierMouse()
877
878    def is_wheel_highres(self, uhdev):
879        high_res = super().is_wheel_highres(uhdev)
880
881        assert uhdev.wheel_multiplier == 1
882
883        return high_res
884
885    def test_resolution_multiplier_wheel(self):
886        uhdev = self.uhdev
887
888        assert uhdev.wheel_multiplier == 1
889
890
891class TestResolutionMultiplierHWheelMouse(TestResolutionMultiplierMouse):
892    def create_device(self):
893        return ResolutionMultiplierHWheelMouse()
894
895    def is_hwheel_highres(self, uhdev):
896        high_res = super().is_hwheel_highres(uhdev)
897
898        if not high_res:
899            # the kernel doesn't seem to support the high res wheel mice,
900            # make sure we haven't triggered the feature
901            assert uhdev.hwheel_multiplier == 1
902
903        return high_res
904
905    def test_resolution_multiplier_ac_pan(self):
906        uhdev = self.uhdev
907
908        if not self.is_hwheel_highres(uhdev):
909            pytest.skip("Kernel not compatible, we can not trigger the conditions")
910
911        assert uhdev.hwheel_multiplier > 1
912        assert 120 % uhdev.hwheel_multiplier == 0
913
914    def test_ac_pan_with_multiplier(self):
915        uhdev = self.uhdev
916
917        if not self.is_hwheel_highres(uhdev):
918            pytest.skip("Kernel not compatible, we can not trigger the conditions")
919
920        assert uhdev.hwheel_multiplier > 1
921
922        syn_event = self.syn_event
923        hmult = uhdev.hwheel_multiplier
924
925        r = uhdev.event(0, 0, wheels=(0, 1))
926        expected = [syn_event]
927        expected.append(
928            libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult)
929        )
930        events = uhdev.next_sync_events()
931        self.debug_reports(r, uhdev, events)
932        self.assertInputEvents(expected, events)
933
934        r = uhdev.event(0, 0, wheels=(0, -1))
935        expected = [syn_event]
936        expected.append(
937            libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120 / hmult)
938        )
939        events = uhdev.next_sync_events()
940        self.debug_reports(r, uhdev, events)
941        self.assertInputEvents(expected, events)
942
943        expected = [syn_event]
944        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1))
945        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2))
946        expected.append(
947            libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult)
948        )
949
950        for _ in range(hmult - 1):
951            r = uhdev.event(1, -2, wheels=(0, 1))
952            events = uhdev.next_sync_events()
953            self.debug_reports(r, uhdev, events)
954            self.assertInputEvents(expected, events)
955
956        r = uhdev.event(1, -2, wheels=(0, 1))
957        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1))
958        events = uhdev.next_sync_events()
959        self.debug_reports(r, uhdev, events)
960        self.assertInputEvents(expected, events)
961
962
963class TestMiMouse(TestWheelMouse):
964    def create_device(self):
965        return MIDongleMIWirelessMouse()
966
967    def assertInputEvents(self, expected_events, effective_events):
968        # Buttons and x/y are spread over two HID reports, so we can get two
969        # event frames for this device.
970        remaining = self.assertInputEventsIn(expected_events, effective_events)
971        try:
972            remaining.remove(libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0))
973        except ValueError:
974            # If there's no SYN_REPORT in the list, continue and let the
975            # assert below print out the real error
976            pass
977        assert remaining == []
978