File Index
Files and directories
Sign in
FILE

test_mouse.py

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/hid/tests/test_mouse.py PYTHON
   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            left, right, middle = buttons
  56            if left is not None:
  57                self.left = left
  58            if right is not None:
  59                self.right = right
  60            if middle is not None:
  61                self.middle = middle
  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 BadReportDescriptorMouse(BaseMouse):
 443    """
 444    This "device" was one autogenerated by syzbot. There are a lot of issues in
 445    it, and the most problematic is that it declares features that have no
 446    size.
 447
 448    This leads to report->size being set to 0 and can mess up with usbhid
 449    internals.  Fortunately, uhid merely passes the incoming buffer, without
 450    touching it so a buffer of size 0 will be translated to [] without
 451    triggering a kernel oops.
 452
 453    Because the report descriptor is wrong, no input are created, and we need
 454    to tweak a little bit the parameters to make it look correct.
 455    """
 456
 457    # fmt: off
 458    report_descriptor = [
 459        0x96, 0x01, 0x00,              # Report Count (1)                    0
 460        0x06, 0x01, 0x00,              # Usage Page (Generic Desktop)        3
 461        # 0x03, 0x00, 0x00, 0x00, 0x00,  # Ignored by the kernel somehow
 462        0x2a, 0x90, 0xa0,              # Usage Maximum (41104)               6
 463        0x27, 0x00, 0x00, 0x00, 0x00,  # Logical Maximum (0)                 9
 464        0xb3, 0x81, 0x3e, 0x25, 0x03,  # Feature (Cnst,Arr,Abs,Vol)          14
 465        0x1b, 0xdd, 0xe8, 0x40, 0x50,  # Usage Minimum (1346431197)          19
 466        0x3b, 0x5d, 0x8c, 0x3d, 0xda,  # Designator Index                    24
 467    ]
 468    # fmt: on
 469
 470    def __init__(
 471        self, rdesc=report_descriptor, name=None, input_info=(3, 0x045E, 0x07DA)
 472    ):
 473        super().__init__(rdesc, name, input_info)
 474        self.high_resolution_report_called = False
 475
 476    def get_evdev(self, application=None):
 477        assert self._input_nodes is None
 478        return (
 479            "Ok"  # should be a list or None, but both would fail, so abusing the system
 480        )
 481
 482    def next_sync_events(self, application=None):
 483        # there are no evdev nodes, so no events
 484        return []
 485
 486    def is_ready(self):
 487        # we wait for the SET_REPORT command to come
 488        return self.high_resolution_report_called
 489
 490    def set_report(self, req, rnum, rtype, data):
 491        if rtype != self.UHID_FEATURE_REPORT:
 492            raise InvalidHIDCommunication(f"Unexpected report type: {rtype}")
 493        if rnum != 0x0:
 494            raise InvalidHIDCommunication(f"Unexpected report number: {rnum}")
 495
 496        if len(data) != 1:
 497            raise InvalidHIDCommunication(f"Unexpected data: {data}, expected '[0]'")
 498
 499        self.high_resolution_report_called = True
 500
 501        return 0
 502
 503
 504class ResolutionMultiplierHWheelMouse(TwoWheelMouse):
 505    # fmt: off
 506    report_descriptor = [
 507        0x05, 0x01,         # Usage Page (Generic Desktop)        0
 508        0x09, 0x02,         # Usage (Mouse)                       2
 509        0xa1, 0x01,         # Collection (Application)            4
 510        0x05, 0x01,         # .Usage Page (Generic Desktop)       6
 511        0x09, 0x02,         # .Usage (Mouse)                      8
 512        0xa1, 0x02,         # .Collection (Logical)               10
 513        0x85, 0x1a,         # ..Report ID (26)                    12
 514        0x09, 0x01,         # ..Usage (Pointer)                   14
 515        0xa1, 0x00,         # ..Collection (Physical)             16
 516        0x05, 0x09,         # ...Usage Page (Button)              18
 517        0x19, 0x01,         # ...Usage Minimum (1)                20
 518        0x29, 0x05,         # ...Usage Maximum (5)                22
 519        0x95, 0x05,         # ...Report Count (5)                 24
 520        0x75, 0x01,         # ...Report Size (1)                  26
 521        0x15, 0x00,         # ...Logical Minimum (0)              28
 522        0x25, 0x01,         # ...Logical Maximum (1)              30
 523        0x81, 0x02,         # ...Input (Data,Var,Abs)             32
 524        0x75, 0x03,         # ...Report Size (3)                  34
 525        0x95, 0x01,         # ...Report Count (1)                 36
 526        0x81, 0x01,         # ...Input (Cnst,Arr,Abs)             38
 527        0x05, 0x01,         # ...Usage Page (Generic Desktop)     40
 528        0x09, 0x30,         # ...Usage (X)                        42
 529        0x09, 0x31,         # ...Usage (Y)                        44
 530        0x95, 0x02,         # ...Report Count (2)                 46
 531        0x75, 0x10,         # ...Report Size (16)                 48
 532        0x16, 0x01, 0x80,   # ...Logical Minimum (-32767)         50
 533        0x26, 0xff, 0x7f,   # ...Logical Maximum (32767)          53
 534        0x81, 0x06,         # ...Input (Data,Var,Rel)             56
 535        0xa1, 0x02,         # ...Collection (Logical)             58
 536        0x85, 0x12,         # ....Report ID (18)                  60
 537        0x09, 0x48,         # ....Usage (Resolution Multiplier)   62
 538        0x95, 0x01,         # ....Report Count (1)                64
 539        0x75, 0x02,         # ....Report Size (2)                 66
 540        0x15, 0x00,         # ....Logical Minimum (0)             68
 541        0x25, 0x01,         # ....Logical Maximum (1)             70
 542        0x35, 0x01,         # ....Physical Minimum (1)            72
 543        0x45, 0x0c,         # ....Physical Maximum (12)           74
 544        0xb1, 0x02,         # ....Feature (Data,Var,Abs)          76
 545        0x85, 0x1a,         # ....Report ID (26)                  78
 546        0x09, 0x38,         # ....Usage (Wheel)                   80
 547        0x35, 0x00,         # ....Physical Minimum (0)            82
 548        0x45, 0x00,         # ....Physical Maximum (0)            84
 549        0x95, 0x01,         # ....Report Count (1)                86
 550        0x75, 0x10,         # ....Report Size (16)                88
 551        0x16, 0x01, 0x80,   # ....Logical Minimum (-32767)        90
 552        0x26, 0xff, 0x7f,   # ....Logical Maximum (32767)         93
 553        0x81, 0x06,         # ....Input (Data,Var,Rel)            96
 554        0xc0,               # ...End Collection                   98
 555        0xa1, 0x02,         # ...Collection (Logical)             99
 556        0x85, 0x12,         # ....Report ID (18)                  101
 557        0x09, 0x48,         # ....Usage (Resolution Multiplier)   103
 558        0x75, 0x02,         # ....Report Size (2)                 105
 559        0x15, 0x00,         # ....Logical Minimum (0)             107
 560        0x25, 0x01,         # ....Logical Maximum (1)             109
 561        0x35, 0x01,         # ....Physical Minimum (1)            111
 562        0x45, 0x0c,         # ....Physical Maximum (12)           113
 563        0xb1, 0x02,         # ....Feature (Data,Var,Abs)          115
 564        0x35, 0x00,         # ....Physical Minimum (0)            117
 565        0x45, 0x00,         # ....Physical Maximum (0)            119
 566        0x75, 0x04,         # ....Report Size (4)                 121
 567        0xb1, 0x01,         # ....Feature (Cnst,Arr,Abs)          123
 568        0x85, 0x1a,         # ....Report ID (26)                  125
 569        0x05, 0x0c,         # ....Usage Page (Consumer Devices)   127
 570        0x95, 0x01,         # ....Report Count (1)                129
 571        0x75, 0x10,         # ....Report Size (16)                131
 572        0x16, 0x01, 0x80,   # ....Logical Minimum (-32767)        133
 573        0x26, 0xff, 0x7f,   # ....Logical Maximum (32767)         136
 574        0x0a, 0x38, 0x02,   # ....Usage (AC Pan)                  139
 575        0x81, 0x06,         # ....Input (Data,Var,Rel)            142
 576        0xc0,               # ...End Collection                   144
 577        0xc0,               # ..End Collection                    145
 578        0xc0,               # .End Collection                     146
 579        0xc0,               # End Collection                      147
 580    ]
 581    # fmt: on
 582
 583    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
 584        super().__init__(rdesc, name, input_info)
 585        self.default_reportID = 0x1A
 586
 587        # Feature Report 12, multiplier Feature value must be set to 0b0101,
 588        # i.e. 5. We should extract that from the descriptor instead
 589        # of hardcoding it here, but meanwhile this will do.
 590        self.set_feature_report = [0x12, 0x5]
 591
 592    def set_report(self, req, rnum, rtype, data):
 593        super().set_report(req, rnum, rtype, data)
 594
 595        self.wheel_multiplier = 12
 596        self.hwheel_multiplier = 12
 597
 598        return 0
 599
 600
 601class BaseTest:
 602    class TestMouse(base.BaseTestCase.TestUhid):
 603        def test_buttons(self):
 604            """check for button reliability."""
 605            uhdev = self.uhdev
 606            evdev = uhdev.get_evdev()
 607            syn_event = self.syn_event
 608
 609            r = uhdev.event(0, 0, (None, True, None))
 610            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1)
 611            events = uhdev.next_sync_events()
 612            self.debug_reports(r, uhdev, events)
 613            self.assertInputEventsIn((syn_event, expected_event), events)
 614            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
 615
 616            r = uhdev.event(0, 0, (None, False, None))
 617            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0)
 618            events = uhdev.next_sync_events()
 619            self.debug_reports(r, uhdev, events)
 620            self.assertInputEventsIn((syn_event, expected_event), events)
 621            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
 622
 623            r = uhdev.event(0, 0, (None, None, True))
 624            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 1)
 625            events = uhdev.next_sync_events()
 626            self.debug_reports(r, uhdev, events)
 627            self.assertInputEventsIn((syn_event, expected_event), events)
 628            assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 1
 629
 630            r = uhdev.event(0, 0, (None, None, False))
 631            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 0)
 632            events = uhdev.next_sync_events()
 633            self.debug_reports(r, uhdev, events)
 634            self.assertInputEventsIn((syn_event, expected_event), events)
 635            assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 0
 636
 637            r = uhdev.event(0, 0, (True, None, None))
 638            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1)
 639            events = uhdev.next_sync_events()
 640            self.debug_reports(r, uhdev, events)
 641            self.assertInputEventsIn((syn_event, expected_event), events)
 642            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
 643
 644            r = uhdev.event(0, 0, (False, None, None))
 645            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0)
 646            events = uhdev.next_sync_events()
 647            self.debug_reports(r, uhdev, events)
 648            self.assertInputEventsIn((syn_event, expected_event), events)
 649            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
 650
 651            r = uhdev.event(0, 0, (True, True, None))
 652            expected_event0 = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1)
 653            expected_event1 = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1)
 654            events = uhdev.next_sync_events()
 655            self.debug_reports(r, uhdev, events)
 656            self.assertInputEventsIn(
 657                (syn_event, expected_event0, expected_event1), events
 658            )
 659            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
 660            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
 661
 662            r = uhdev.event(0, 0, (False, None, None))
 663            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0)
 664            events = uhdev.next_sync_events()
 665            self.debug_reports(r, uhdev, events)
 666            self.assertInputEventsIn((syn_event, expected_event), events)
 667            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
 668            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
 669
 670            r = uhdev.event(0, 0, (None, False, None))
 671            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0)
 672            events = uhdev.next_sync_events()
 673            self.debug_reports(r, uhdev, events)
 674            self.assertInputEventsIn((syn_event, expected_event), events)
 675            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
 676            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
 677
 678        def test_relative(self):
 679            """Check for relative events."""
 680            uhdev = self.uhdev
 681
 682            syn_event = self.syn_event
 683
 684            r = uhdev.event(0, -1)
 685            expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_Y, -1)
 686            events = uhdev.next_sync_events()
 687            self.debug_reports(r, uhdev, events)
 688            self.assertInputEvents((syn_event, expected_event), events)
 689
 690            r = uhdev.event(1, 0)
 691            expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_X, 1)
 692            events = uhdev.next_sync_events()
 693            self.debug_reports(r, uhdev, events)
 694            self.assertInputEvents((syn_event, expected_event), events)
 695
 696            r = uhdev.event(-1, 2)
 697            expected_event0 = libevdev.InputEvent(libevdev.EV_REL.REL_X, -1)
 698            expected_event1 = libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2)
 699            events = uhdev.next_sync_events()
 700            self.debug_reports(r, uhdev, events)
 701            self.assertInputEvents(
 702                (syn_event, expected_event0, expected_event1), events
 703            )
 704
 705
 706class TestSimpleMouse(BaseTest.TestMouse):
 707    def create_device(self):
 708        return ButtonMouse()
 709
 710    def test_rdesc(self):
 711        """Check that the testsuite actually manages to format the
 712        reports according to the report descriptors.
 713        No kernel device is used here"""
 714        uhdev = self.uhdev
 715
 716        event = (0, 0, (None, None, None))
 717        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
 718
 719        event = (0, 0, (None, True, None))
 720        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
 721
 722        event = (0, 0, (True, True, None))
 723        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
 724
 725        event = (0, 0, (False, False, False))
 726        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
 727
 728        event = (1, 0, (True, False, True))
 729        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
 730
 731        event = (-1, 0, (True, False, True))
 732        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
 733
 734        event = (-5, 5, (True, False, True))
 735        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
 736
 737        event = (-127, 127, (True, False, True))
 738        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
 739
 740        event = (0, -128, (True, False, True))
 741        with pytest.raises(hidtools.hid.RangeError):
 742            uhdev.create_report(*event)
 743
 744
 745class TestWheelMouse(BaseTest.TestMouse):
 746    def create_device(self):
 747        return WheelMouse()
 748
 749    def is_wheel_highres(self, uhdev):
 750        evdev = uhdev.get_evdev()
 751        assert evdev.has(libevdev.EV_REL.REL_WHEEL)
 752        return evdev.has(libevdev.EV_REL.REL_WHEEL_HI_RES)
 753
 754    def test_wheel(self):
 755        uhdev = self.uhdev
 756
 757        # check if the kernel is high res wheel compatible
 758        high_res_wheel = self.is_wheel_highres(uhdev)
 759
 760        syn_event = self.syn_event
 761        # The Resolution Multiplier is applied to the HID reports, so we
 762        # need to pre-multiply too.
 763        mult = uhdev.wheel_multiplier
 764
 765        r = uhdev.event(0, 0, wheels=1 * mult)
 766        expected = [syn_event]
 767        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1))
 768        if high_res_wheel:
 769            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120))
 770        events = uhdev.next_sync_events()
 771        self.debug_reports(r, uhdev, events)
 772        self.assertInputEvents(expected, events)
 773
 774        r = uhdev.event(0, 0, wheels=-1 * mult)
 775        expected = [syn_event]
 776        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -1))
 777        if high_res_wheel:
 778            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120))
 779        events = uhdev.next_sync_events()
 780        self.debug_reports(r, uhdev, events)
 781        self.assertInputEvents(expected, events)
 782
 783        r = uhdev.event(-1, 2, wheels=3 * mult)
 784        expected = [syn_event]
 785        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
 786        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
 787        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 3))
 788        if high_res_wheel:
 789            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 360))
 790        events = uhdev.next_sync_events()
 791        self.debug_reports(r, uhdev, events)
 792        self.assertInputEvents(expected, events)
 793
 794
 795class TestTwoWheelMouse(TestWheelMouse):
 796    def create_device(self):
 797        return TwoWheelMouse()
 798
 799    def is_hwheel_highres(self, uhdev):
 800        evdev = uhdev.get_evdev()
 801        assert evdev.has(libevdev.EV_REL.REL_HWHEEL)
 802        return evdev.has(libevdev.EV_REL.REL_HWHEEL_HI_RES)
 803
 804    def test_ac_pan(self):
 805        uhdev = self.uhdev
 806
 807        # check if the kernel is high res wheel compatible
 808        high_res_wheel = self.is_wheel_highres(uhdev)
 809        high_res_hwheel = self.is_hwheel_highres(uhdev)
 810        assert high_res_wheel == high_res_hwheel
 811
 812        syn_event = self.syn_event
 813        # The Resolution Multiplier is applied to the HID reports, so we
 814        # need to pre-multiply too.
 815        hmult = uhdev.hwheel_multiplier
 816        vmult = uhdev.wheel_multiplier
 817
 818        r = uhdev.event(0, 0, wheels=(0, 1 * hmult))
 819        expected = [syn_event]
 820        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1))
 821        if high_res_hwheel:
 822            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120))
 823        events = uhdev.next_sync_events()
 824        self.debug_reports(r, uhdev, events)
 825        self.assertInputEvents(expected, events)
 826
 827        r = uhdev.event(0, 0, wheels=(0, -1 * hmult))
 828        expected = [syn_event]
 829        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, -1))
 830        if high_res_hwheel:
 831            expected.append(
 832                libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120)
 833            )
 834        events = uhdev.next_sync_events()
 835        self.debug_reports(r, uhdev, events)
 836        self.assertInputEvents(expected, events)
 837
 838        r = uhdev.event(-1, 2, wheels=(0, 3 * hmult))
 839        expected = [syn_event]
 840        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
 841        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
 842        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 3))
 843        if high_res_hwheel:
 844            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 360))
 845        events = uhdev.next_sync_events()
 846        self.debug_reports(r, uhdev, events)
 847        self.assertInputEvents(expected, events)
 848
 849        r = uhdev.event(-1, 2, wheels=(-3 * vmult, 4 * hmult))
 850        expected = [syn_event]
 851        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
 852        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
 853        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -3))
 854        if high_res_wheel:
 855            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -360))
 856        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 4))
 857        if high_res_wheel:
 858            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 480))
 859        events = uhdev.next_sync_events()
 860        self.debug_reports(r, uhdev, events)
 861        self.assertInputEvents(expected, events)
 862
 863
 864class TestResolutionMultiplierMouse(TestTwoWheelMouse):
 865    def create_device(self):
 866        return ResolutionMultiplierMouse()
 867
 868    def is_wheel_highres(self, uhdev):
 869        high_res = super().is_wheel_highres(uhdev)
 870
 871        if not high_res:
 872            # the kernel doesn't seem to support the high res wheel mice,
 873            # make sure we haven't triggered the feature
 874            assert uhdev.wheel_multiplier == 1
 875
 876        return high_res
 877
 878    def test_resolution_multiplier_wheel(self):
 879        uhdev = self.uhdev
 880
 881        if not self.is_wheel_highres(uhdev):
 882            pytest.skip("Kernel not compatible, we can not trigger the conditions")
 883
 884        assert uhdev.wheel_multiplier > 1
 885        assert 120 % uhdev.wheel_multiplier == 0
 886
 887    def test_wheel_with_multiplier(self):
 888        uhdev = self.uhdev
 889
 890        if not self.is_wheel_highres(uhdev):
 891            pytest.skip("Kernel not compatible, we can not trigger the conditions")
 892
 893        assert uhdev.wheel_multiplier > 1
 894
 895        syn_event = self.syn_event
 896        mult = uhdev.wheel_multiplier
 897
 898        r = uhdev.event(0, 0, wheels=1)
 899        expected = [syn_event]
 900        expected.append(
 901            libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult)
 902        )
 903        events = uhdev.next_sync_events()
 904        self.debug_reports(r, uhdev, events)
 905        self.assertInputEvents(expected, events)
 906
 907        r = uhdev.event(0, 0, wheels=-1)
 908        expected = [syn_event]
 909        expected.append(
 910            libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120 / mult)
 911        )
 912        events = uhdev.next_sync_events()
 913        self.debug_reports(r, uhdev, events)
 914        self.assertInputEvents(expected, events)
 915
 916        expected = [syn_event]
 917        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1))
 918        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2))
 919        expected.append(
 920            libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult)
 921        )
 922
 923        for _ in range(mult - 1):
 924            r = uhdev.event(1, -2, wheels=1)
 925            events = uhdev.next_sync_events()
 926            self.debug_reports(r, uhdev, events)
 927            self.assertInputEvents(expected, events)
 928
 929        r = uhdev.event(1, -2, wheels=1)
 930        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1))
 931        events = uhdev.next_sync_events()
 932        self.debug_reports(r, uhdev, events)
 933        self.assertInputEvents(expected, events)
 934
 935
 936class TestBadResolutionMultiplierMouse(TestTwoWheelMouse):
 937    def create_device(self):
 938        return BadResolutionMultiplierMouse()
 939
 940    def is_wheel_highres(self, uhdev):
 941        high_res = super().is_wheel_highres(uhdev)
 942
 943        assert uhdev.wheel_multiplier == 1
 944
 945        return high_res
 946
 947    def test_resolution_multiplier_wheel(self):
 948        uhdev = self.uhdev
 949
 950        assert uhdev.wheel_multiplier == 1
 951
 952
 953class TestResolutionMultiplierHWheelMouse(TestResolutionMultiplierMouse):
 954    def create_device(self):
 955        return ResolutionMultiplierHWheelMouse()
 956
 957    def is_hwheel_highres(self, uhdev):
 958        high_res = super().is_hwheel_highres(uhdev)
 959
 960        if not high_res:
 961            # the kernel doesn't seem to support the high res wheel mice,
 962            # make sure we haven't triggered the feature
 963            assert uhdev.hwheel_multiplier == 1
 964
 965        return high_res
 966
 967    def test_resolution_multiplier_ac_pan(self):
 968        uhdev = self.uhdev
 969
 970        if not self.is_hwheel_highres(uhdev):
 971            pytest.skip("Kernel not compatible, we can not trigger the conditions")
 972
 973        assert uhdev.hwheel_multiplier > 1
 974        assert 120 % uhdev.hwheel_multiplier == 0
 975
 976    def test_ac_pan_with_multiplier(self):
 977        uhdev = self.uhdev
 978
 979        if not self.is_hwheel_highres(uhdev):
 980            pytest.skip("Kernel not compatible, we can not trigger the conditions")
 981
 982        assert uhdev.hwheel_multiplier > 1
 983
 984        syn_event = self.syn_event
 985        hmult = uhdev.hwheel_multiplier
 986
 987        r = uhdev.event(0, 0, wheels=(0, 1))
 988        expected = [syn_event]
 989        expected.append(
 990            libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult)
 991        )
 992        events = uhdev.next_sync_events()
 993        self.debug_reports(r, uhdev, events)
 994        self.assertInputEvents(expected, events)
 995
 996        r = uhdev.event(0, 0, wheels=(0, -1))
 997        expected = [syn_event]
 998        expected.append(
 999            libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120 / hmult)
1000        )
1001        events = uhdev.next_sync_events()
1002        self.debug_reports(r, uhdev, events)
1003        self.assertInputEvents(expected, events)
1004
1005        expected = [syn_event]
1006        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1))
1007        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2))
1008        expected.append(
1009            libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult)
1010        )
1011
1012        for _ in range(hmult - 1):
1013            r = uhdev.event(1, -2, wheels=(0, 1))
1014            events = uhdev.next_sync_events()
1015            self.debug_reports(r, uhdev, events)
1016            self.assertInputEvents(expected, events)
1017
1018        r = uhdev.event(1, -2, wheels=(0, 1))
1019        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1))
1020        events = uhdev.next_sync_events()
1021        self.debug_reports(r, uhdev, events)
1022        self.assertInputEvents(expected, events)
1023
1024
1025class TestMiMouse(TestWheelMouse):
1026    def create_device(self):
1027        return MIDongleMIWirelessMouse()
1028
1029    def assertInputEvents(self, expected_events, effective_events):
1030        # Buttons and x/y are spread over two HID reports, so we can get two
1031        # event frames for this device.
1032        remaining = self.assertInputEventsIn(expected_events, effective_events)
1033        try:
1034            remaining.remove(libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0))
1035        except ValueError:
1036            # If there's no SYN_REPORT in the list, continue and let the
1037            # assert below print out the real error
1038            pass
1039        assert remaining == []
1040
1041
1042class TestBadReportDescriptorMouse(base.BaseTestCase.TestUhid):
1043    def create_device(self):
1044        return BadReportDescriptorMouse()
1045
1046    def assertName(self, uhdev):
1047        pass
~/ Read-only file preview.