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