| #!/bin/env python3 | |
| # SPDX-License-Identifier: GPL-2.0 | |
| # -*- coding: utf-8 -*- | |
| # | |
| # Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com> | |
| # Copyright (c) 2017 Red Hat, Inc. | |
| # | |
| from . import base | |
| import hidtools.hid | |
| from hidtools.util import BusType | |
| import libevdev | |
| import logging | |
| import pytest | |
| logger = logging.getLogger("hidtools.test.mouse") | |
| # workaround https://gitlab.freedesktop.org/libevdev/python-libevdev/issues/6 | |
| try: | |
| libevdev.EV_REL.REL_WHEEL_HI_RES | |
| except AttributeError: | |
| libevdev.EV_REL.REL_WHEEL_HI_RES = libevdev.EV_REL.REL_0B | |
| libevdev.EV_REL.REL_HWHEEL_HI_RES = libevdev.EV_REL.REL_0C | |
| class InvalidHIDCommunication(Exception): | |
| pass | |
| class MouseData(object): | |
| pass | |
| class BaseMouse(base.UHIDTestDevice): | |
| def __init__(self, rdesc, name=None, input_info=None): | |
| assert rdesc is not None | |
| super().__init__(name, "Mouse", input_info=input_info, rdesc=rdesc) | |
| self.left = False | |
| self.right = False | |
| self.middle = False | |
| def create_report(self, x, y, buttons=None, wheels=None, reportID=None): | |
| """ | |
| Return an input report for this device. | |
| :param x: relative x | |
| :param y: relative y | |
| :param buttons: a (l, r, m) tuple of bools for the button states, | |
| where ``None`` is "leave unchanged" | |
| :param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for | |
| the two wheels | |
| :param reportID: the numeric report ID for this report, if needed | |
| """ | |
| if buttons is not None: | |
| left, right, middle = buttons | |
| if left is not None: | |
| self.left = left | |
| if right is not None: | |
| self.right = right | |
| if middle is not None: | |
| self.middle = middle | |
| left = self.left | |
| right = self.right | |
| middle = self.middle | |
| # Note: the BaseMouse doesn't actually have a wheel but the | |
| # create_report magic only fills in those fields exist, so let's | |
| # make this generic here. | |
| wheel, acpan = 0, 0 | |
| if wheels is not None: | |
| if isinstance(wheels, tuple): | |
| wheel = wheels[0] | |
| acpan = wheels[1] | |
| else: | |
| wheel = wheels | |
| reportID = reportID or self.default_reportID | |
| mouse = MouseData() | |
| mouse.b1 = int(left) | |
| mouse.b2 = int(right) | |
| mouse.b3 = int(middle) | |
| mouse.x = x | |
| mouse.y = y | |
| mouse.wheel = wheel | |
| mouse.acpan = acpan | |
| return super().create_report(mouse, reportID=reportID) | |
| def event(self, x, y, buttons=None, wheels=None): | |
| """ | |
| Send an input event on the default report ID. | |
| :param x: relative x | |
| :param y: relative y | |
| :param buttons: a (l, r, m) tuple of bools for the button states, | |
| where ``None`` is "leave unchanged" | |
| :param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for | |
| the two wheels | |
| """ | |
| r = self.create_report(x, y, buttons, wheels) | |
| self.call_input_event(r) | |
| return [r] | |
| class ButtonMouse(BaseMouse): | |
| # fmt: off | |
| report_descriptor = [ | |
| 0x05, 0x01, # .Usage Page (Generic Desktop) 0 | |
| 0x09, 0x02, # .Usage (Mouse) 2 | |
| 0xa1, 0x01, # .Collection (Application) 4 | |
| 0x09, 0x02, # ..Usage (Mouse) 6 | |
| 0xa1, 0x02, # ..Collection (Logical) 8 | |
| 0x09, 0x01, # ...Usage (Pointer) 10 | |
| 0xa1, 0x00, # ...Collection (Physical) 12 | |
| 0x05, 0x09, # ....Usage Page (Button) 14 | |
| 0x19, 0x01, # ....Usage Minimum (1) 16 | |
| 0x29, 0x03, # ....Usage Maximum (3) 18 | |
| 0x15, 0x00, # ....Logical Minimum (0) 20 | |
| 0x25, 0x01, # ....Logical Maximum (1) 22 | |
| 0x75, 0x01, # ....Report Size (1) 24 | |
| 0x95, 0x03, # ....Report Count (3) 26 | |
| 0x81, 0x02, # ....Input (Data,Var,Abs) 28 | |
| 0x75, 0x05, # ....Report Size (5) 30 | |
| 0x95, 0x01, # ....Report Count (1) 32 | |
| 0x81, 0x03, # ....Input (Cnst,Var,Abs) 34 | |
| 0x05, 0x01, # ....Usage Page (Generic Desktop) 36 | |
| 0x09, 0x30, # ....Usage (X) 38 | |
| 0x09, 0x31, # ....Usage (Y) 40 | |
| 0x15, 0x81, # ....Logical Minimum (-127) 42 | |
| 0x25, 0x7f, # ....Logical Maximum (127) 44 | |
| 0x75, 0x08, # ....Report Size (8) 46 | |
| 0x95, 0x02, # ....Report Count (2) 48 | |
| 0x81, 0x06, # ....Input (Data,Var,Rel) 50 | |
| 0xc0, # ...End Collection 52 | |
| 0xc0, # ..End Collection 53 | |
| 0xc0, # .End Collection 54 | |
| ] | |
| # fmt: on | |
| def __init__(self, rdesc=report_descriptor, name=None, input_info=None): | |
| super().__init__(rdesc, name, input_info) | |
| def fake_report(self, x, y, buttons): | |
| if buttons is not None: | |
| left, right, middle = buttons | |
| if left is None: | |
| left = self.left | |
| if right is None: | |
| right = self.right | |
| if middle is None: | |
| middle = self.middle | |
| else: | |
| left = self.left | |
| right = self.right | |
| middle = self.middle | |
| button_mask = sum(1 << i for i, b in enumerate([left, right, middle]) if b) | |
| x = max(-127, min(127, x)) | |
| y = max(-127, min(127, y)) | |
| x = hidtools.util.to_twos_comp(x, 8) | |
| y = hidtools.util.to_twos_comp(y, 8) | |
| return [button_mask, x, y] | |
| class WheelMouse(ButtonMouse): | |
| # fmt: off | |
| report_descriptor = [ | |
| 0x05, 0x01, # Usage Page (Generic Desktop) 0 | |
| 0x09, 0x02, # Usage (Mouse) 2 | |
| 0xa1, 0x01, # Collection (Application) 4 | |
| 0x05, 0x09, # .Usage Page (Button) 6 | |
| 0x19, 0x01, # .Usage Minimum (1) 8 | |
| 0x29, 0x03, # .Usage Maximum (3) 10 | |
| 0x15, 0x00, # .Logical Minimum (0) 12 | |
| 0x25, 0x01, # .Logical Maximum (1) 14 | |
| 0x95, 0x03, # .Report Count (3) 16 | |
| 0x75, 0x01, # .Report Size (1) 18 | |
| 0x81, 0x02, # .Input (Data,Var,Abs) 20 | |
| 0x95, 0x01, # .Report Count (1) 22 | |
| 0x75, 0x05, # .Report Size (5) 24 | |
| 0x81, 0x03, # .Input (Cnst,Var,Abs) 26 | |
| 0x05, 0x01, # .Usage Page (Generic Desktop) 28 | |
| 0x09, 0x01, # .Usage (Pointer) 30 | |
| 0xa1, 0x00, # .Collection (Physical) 32 | |
| 0x09, 0x30, # ..Usage (X) 34 | |
| 0x09, 0x31, # ..Usage (Y) 36 | |
| 0x15, 0x81, # ..Logical Minimum (-127) 38 | |
| 0x25, 0x7f, # ..Logical Maximum (127) 40 | |
| 0x75, 0x08, # ..Report Size (8) 42 | |
| 0x95, 0x02, # ..Report Count (2) 44 | |
| 0x81, 0x06, # ..Input (Data,Var,Rel) 46 | |
| 0xc0, # .End Collection 48 | |
| 0x09, 0x38, # .Usage (Wheel) 49 | |
| 0x15, 0x81, # .Logical Minimum (-127) 51 | |
| 0x25, 0x7f, # .Logical Maximum (127) 53 | |
| 0x75, 0x08, # .Report Size (8) 55 | |
| 0x95, 0x01, # .Report Count (1) 57 | |
| 0x81, 0x06, # .Input (Data,Var,Rel) 59 | |
| 0xc0, # End Collection 61 | |
| ] | |
| # fmt: on | |
| def __init__(self, rdesc=report_descriptor, name=None, input_info=None): | |
| super().__init__(rdesc, name, input_info) | |
| self.wheel_multiplier = 1 | |
| class TwoWheelMouse(WheelMouse): | |
| # fmt: off | |
| report_descriptor = [ | |
| 0x05, 0x01, # Usage Page (Generic Desktop) 0 | |
| 0x09, 0x02, # Usage (Mouse) 2 | |
| 0xa1, 0x01, # Collection (Application) 4 | |
| 0x09, 0x01, # .Usage (Pointer) 6 | |
| 0xa1, 0x00, # .Collection (Physical) 8 | |
| 0x05, 0x09, # ..Usage Page (Button) 10 | |
| 0x19, 0x01, # ..Usage Minimum (1) 12 | |
| 0x29, 0x10, # ..Usage Maximum (16) 14 | |
| 0x15, 0x00, # ..Logical Minimum (0) 16 | |
| 0x25, 0x01, # ..Logical Maximum (1) 18 | |
| 0x95, 0x10, # ..Report Count (16) 20 | |
| 0x75, 0x01, # ..Report Size (1) 22 | |
| 0x81, 0x02, # ..Input (Data,Var,Abs) 24 | |
| 0x05, 0x01, # ..Usage Page (Generic Desktop) 26 | |
| 0x16, 0x01, 0x80, # ..Logical Minimum (-32767) 28 | |
| 0x26, 0xff, 0x7f, # ..Logical Maximum (32767) 31 | |
| 0x75, 0x10, # ..Report Size (16) 34 | |
| 0x95, 0x02, # ..Report Count (2) 36 | |
| 0x09, 0x30, # ..Usage (X) 38 | |
| 0x09, 0x31, # ..Usage (Y) 40 | |
| 0x81, 0x06, # ..Input (Data,Var,Rel) 42 | |
| 0x15, 0x81, # ..Logical Minimum (-127) 44 | |
| 0x25, 0x7f, # ..Logical Maximum (127) 46 | |
| 0x75, 0x08, # ..Report Size (8) 48 | |
| 0x95, 0x01, # ..Report Count (1) 50 | |
| 0x09, 0x38, # ..Usage (Wheel) 52 | |
| 0x81, 0x06, # ..Input (Data,Var,Rel) 54 | |
| 0x05, 0x0c, # ..Usage Page (Consumer Devices) 56 | |
| 0x0a, 0x38, 0x02, # ..Usage (AC Pan) 58 | |
| 0x95, 0x01, # ..Report Count (1) 61 | |
| 0x81, 0x06, # ..Input (Data,Var,Rel) 63 | |
| 0xc0, # .End Collection 65 | |
| 0xc0, # End Collection 66 | |
| ] | |
| # fmt: on | |
| def __init__(self, rdesc=report_descriptor, name=None, input_info=None): | |
| super().__init__(rdesc, name, input_info) | |
| self.hwheel_multiplier = 1 | |
| class MIDongleMIWirelessMouse(TwoWheelMouse): | |
| # fmt: off | |
| report_descriptor = [ | |
| 0x05, 0x01, # Usage Page (Generic Desktop) | |
| 0x09, 0x02, # Usage (Mouse) | |
| 0xa1, 0x01, # Collection (Application) | |
| 0x85, 0x01, # .Report ID (1) | |
| 0x09, 0x01, # .Usage (Pointer) | |
| 0xa1, 0x00, # .Collection (Physical) | |
| 0x95, 0x05, # ..Report Count (5) | |
| 0x75, 0x01, # ..Report Size (1) | |
| 0x05, 0x09, # ..Usage Page (Button) | |
| 0x19, 0x01, # ..Usage Minimum (1) | |
| 0x29, 0x05, # ..Usage Maximum (5) | |
| 0x15, 0x00, # ..Logical Minimum (0) | |
| 0x25, 0x01, # ..Logical Maximum (1) | |
| 0x81, 0x02, # ..Input (Data,Var,Abs) | |
| 0x95, 0x01, # ..Report Count (1) | |
| 0x75, 0x03, # ..Report Size (3) | |
| 0x81, 0x01, # ..Input (Cnst,Arr,Abs) | |
| 0x75, 0x08, # ..Report Size (8) | |
| 0x95, 0x01, # ..Report Count (1) | |
| 0x05, 0x01, # ..Usage Page (Generic Desktop) | |
| 0x09, 0x38, # ..Usage (Wheel) | |
| 0x15, 0x81, # ..Logical Minimum (-127) | |
| 0x25, 0x7f, # ..Logical Maximum (127) | |
| 0x81, 0x06, # ..Input (Data,Var,Rel) | |
| 0x05, 0x0c, # ..Usage Page (Consumer Devices) | |
| 0x0a, 0x38, 0x02, # ..Usage (AC Pan) | |
| 0x95, 0x01, # ..Report Count (1) | |
| 0x81, 0x06, # ..Input (Data,Var,Rel) | |
| 0xc0, # .End Collection | |
| 0x85, 0x02, # .Report ID (2) | |
| 0x09, 0x01, # .Usage (Consumer Control) | |
| 0xa1, 0x00, # .Collection (Physical) | |
| 0x75, 0x0c, # ..Report Size (12) | |
| 0x95, 0x02, # ..Report Count (2) | |
| 0x05, 0x01, # ..Usage Page (Generic Desktop) | |
| 0x09, 0x30, # ..Usage (X) | |
| 0x09, 0x31, # ..Usage (Y) | |
| 0x16, 0x01, 0xf8, # ..Logical Minimum (-2047) | |
| 0x26, 0xff, 0x07, # ..Logical Maximum (2047) | |
| 0x81, 0x06, # ..Input (Data,Var,Rel) | |
| 0xc0, # .End Collection | |
| 0xc0, # End Collection | |
| 0x05, 0x0c, # Usage Page (Consumer Devices) | |
| 0x09, 0x01, # Usage (Consumer Control) | |
| 0xa1, 0x01, # Collection (Application) | |
| 0x85, 0x03, # .Report ID (3) | |
| 0x15, 0x00, # .Logical Minimum (0) | |
| 0x25, 0x01, # .Logical Maximum (1) | |
| 0x75, 0x01, # .Report Size (1) | |
| 0x95, 0x01, # .Report Count (1) | |
| 0x09, 0xcd, # .Usage (Play/Pause) | |
| 0x81, 0x06, # .Input (Data,Var,Rel) | |
| 0x0a, 0x83, 0x01, # .Usage (AL Consumer Control Config) | |
| 0x81, 0x06, # .Input (Data,Var,Rel) | |
| 0x09, 0xb5, # .Usage (Scan Next Track) | |
| 0x81, 0x06, # .Input (Data,Var,Rel) | |
| 0x09, 0xb6, # .Usage (Scan Previous Track) | |
| 0x81, 0x06, # .Input (Data,Var,Rel) | |
| 0x09, 0xea, # .Usage (Volume Down) | |
| 0x81, 0x06, # .Input (Data,Var,Rel) | |
| 0x09, 0xe9, # .Usage (Volume Up) | |
| 0x81, 0x06, # .Input (Data,Var,Rel) | |
| 0x0a, 0x25, 0x02, # .Usage (AC Forward) | |
| 0x81, 0x06, # .Input (Data,Var,Rel) | |
| 0x0a, 0x24, 0x02, # .Usage (AC Back) | |
| 0x81, 0x06, # .Input (Data,Var,Rel) | |
| 0xc0, # End Collection | |
| ] | |
| # fmt: on | |
| device_input_info = (BusType.USB, 0x2717, 0x003B) | |
| device_name = "uhid test MI Dongle MI Wireless Mouse" | |
| def __init__( | |
| self, rdesc=report_descriptor, name=device_name, input_info=device_input_info | |
| ): | |
| super().__init__(rdesc, name, input_info) | |
| def event(self, x, y, buttons=None, wheels=None): | |
| # this mouse spreads the relative pointer and the mouse buttons | |
| # onto 2 distinct reports | |
| rs = [] | |
| r = self.create_report(x, y, buttons, wheels, reportID=1) | |
| self.call_input_event(r) | |
| rs.append(r) | |
| r = self.create_report(x, y, buttons, reportID=2) | |
| self.call_input_event(r) | |
| rs.append(r) | |
| return rs | |
| class ResolutionMultiplierMouse(TwoWheelMouse): | |
| # fmt: off | |
| report_descriptor = [ | |
| 0x05, 0x01, # Usage Page (Generic Desktop) 83 | |
| 0x09, 0x02, # Usage (Mouse) 85 | |
| 0xa1, 0x01, # Collection (Application) 87 | |
| 0x05, 0x01, # .Usage Page (Generic Desktop) 89 | |
| 0x09, 0x02, # .Usage (Mouse) 91 | |
| 0xa1, 0x02, # .Collection (Logical) 93 | |
| 0x85, 0x11, # ..Report ID (17) 95 | |
| 0x09, 0x01, # ..Usage (Pointer) 97 | |
| 0xa1, 0x00, # ..Collection (Physical) 99 | |
| 0x05, 0x09, # ...Usage Page (Button) 101 | |
| 0x19, 0x01, # ...Usage Minimum (1) 103 | |
| 0x29, 0x03, # ...Usage Maximum (3) 105 | |
| 0x95, 0x03, # ...Report Count (3) 107 | |
| 0x75, 0x01, # ...Report Size (1) 109 | |
| 0x25, 0x01, # ...Logical Maximum (1) 111 | |
| 0x81, 0x02, # ...Input (Data,Var,Abs) 113 | |
| 0x95, 0x01, # ...Report Count (1) 115 | |
| 0x81, 0x01, # ...Input (Cnst,Arr,Abs) 117 | |
| 0x09, 0x05, # ...Usage (Vendor Usage 0x05) 119 | |
| 0x81, 0x02, # ...Input (Data,Var,Abs) 121 | |
| 0x95, 0x03, # ...Report Count (3) 123 | |
| 0x81, 0x01, # ...Input (Cnst,Arr,Abs) 125 | |
| 0x05, 0x01, # ...Usage Page (Generic Desktop) 127 | |
| 0x09, 0x30, # ...Usage (X) 129 | |
| 0x09, 0x31, # ...Usage (Y) 131 | |
| 0x95, 0x02, # ...Report Count (2) 133 | |
| 0x75, 0x08, # ...Report Size (8) 135 | |
| 0x15, 0x81, # ...Logical Minimum (-127) 137 | |
| 0x25, 0x7f, # ...Logical Maximum (127) 139 | |
| 0x81, 0x06, # ...Input (Data,Var,Rel) 141 | |
| 0xa1, 0x02, # ...Collection (Logical) 143 | |
| 0x85, 0x12, # ....Report ID (18) 145 | |
| 0x09, 0x48, # ....Usage (Resolution Multiplier) 147 | |
| 0x95, 0x01, # ....Report Count (1) 149 | |
| 0x75, 0x02, # ....Report Size (2) 151 | |
| 0x15, 0x00, # ....Logical Minimum (0) 153 | |
| 0x25, 0x01, # ....Logical Maximum (1) 155 | |
| 0x35, 0x01, # ....Physical Minimum (1) 157 | |
| 0x45, 0x04, # ....Physical Maximum (4) 159 | |
| 0xb1, 0x02, # ....Feature (Data,Var,Abs) 161 | |
| 0x35, 0x00, # ....Physical Minimum (0) 163 | |
| 0x45, 0x00, # ....Physical Maximum (0) 165 | |
| 0x75, 0x06, # ....Report Size (6) 167 | |
| 0xb1, 0x01, # ....Feature (Cnst,Arr,Abs) 169 | |
| 0x85, 0x11, # ....Report ID (17) 171 | |
| 0x09, 0x38, # ....Usage (Wheel) 173 | |
| 0x15, 0x81, # ....Logical Minimum (-127) 175 | |
| 0x25, 0x7f, # ....Logical Maximum (127) 177 | |
| 0x75, 0x08, # ....Report Size (8) 179 | |
| 0x81, 0x06, # ....Input (Data,Var,Rel) 181 | |
| 0xc0, # ...End Collection 183 | |
| 0x05, 0x0c, # ...Usage Page (Consumer Devices) 184 | |
| 0x75, 0x08, # ...Report Size (8) 186 | |
| 0x0a, 0x38, 0x02, # ...Usage (AC Pan) 188 | |
| 0x81, 0x06, # ...Input (Data,Var,Rel) 191 | |
| 0xc0, # ..End Collection 193 | |
| 0xc0, # .End Collection 194 | |
| 0xc0, # End Collection 195 | |
| ] | |
| # fmt: on | |
| def __init__(self, rdesc=report_descriptor, name=None, input_info=None): | |
| super().__init__(rdesc, name, input_info) | |
| self.default_reportID = 0x11 | |
| # Feature Report 12, multiplier Feature value must be set to 0b01, | |
| # i.e. 1. We should extract that from the descriptor instead | |
| # of hardcoding it here, but meanwhile this will do. | |
| self.set_feature_report = [0x12, 0x1] | |
| def set_report(self, req, rnum, rtype, data): | |
| if rtype != self.UHID_FEATURE_REPORT: | |
| raise InvalidHIDCommunication(f"Unexpected report type: {rtype}") | |
| if rnum != 0x12: | |
| raise InvalidHIDCommunication(f"Unexpected report number: {rnum}") | |
| if data != self.set_feature_report: | |
| raise InvalidHIDCommunication( | |
| f"Unexpected data: {data}, expected {self.set_feature_report}" | |
| ) | |
| self.wheel_multiplier = 4 | |
| return 0 | |
| class BadResolutionMultiplierMouse(ResolutionMultiplierMouse): | |
| def set_report(self, req, rnum, rtype, data): | |
| super().set_report(req, rnum, rtype, data) | |
| self.wheel_multiplier = 1 | |
| self.hwheel_multiplier = 1 | |
| return 32 # EPIPE | |
| class BadReportDescriptorMouse(BaseMouse): | |
| """ | |
| This "device" was one autogenerated by syzbot. There are a lot of issues in | |
| it, and the most problematic is that it declares features that have no | |
| size. | |
| This leads to report->size being set to 0 and can mess up with usbhid | |
| internals. Fortunately, uhid merely passes the incoming buffer, without | |
| touching it so a buffer of size 0 will be translated to [] without | |
| triggering a kernel oops. | |
| Because the report descriptor is wrong, no input are created, and we need | |
| to tweak a little bit the parameters to make it look correct. | |
| """ | |
| # fmt: off | |
| report_descriptor = [ | |
| 0x96, 0x01, 0x00, # Report Count (1) 0 | |
| 0x06, 0x01, 0x00, # Usage Page (Generic Desktop) 3 | |
| # 0x03, 0x00, 0x00, 0x00, 0x00, # Ignored by the kernel somehow | |
| 0x2a, 0x90, 0xa0, # Usage Maximum (41104) 6 | |
| 0x27, 0x00, 0x00, 0x00, 0x00, # Logical Maximum (0) 9 | |
| 0xb3, 0x81, 0x3e, 0x25, 0x03, # Feature (Cnst,Arr,Abs,Vol) 14 | |
| 0x1b, 0xdd, 0xe8, 0x40, 0x50, # Usage Minimum (1346431197) 19 | |
| 0x3b, 0x5d, 0x8c, 0x3d, 0xda, # Designator Index 24 | |
| ] | |
| # fmt: on | |
| def __init__( | |
| self, rdesc=report_descriptor, name=None, input_info=(3, 0x045E, 0x07DA) | |
| ): | |
| super().__init__(rdesc, name, input_info) | |
| self.high_resolution_report_called = False | |
| def get_evdev(self, application=None): | |
| assert self._input_nodes is None | |
| return ( | |
| "Ok" # should be a list or None, but both would fail, so abusing the system | |
| ) | |
| def next_sync_events(self, application=None): | |
| # there are no evdev nodes, so no events | |
| return [] | |
| def is_ready(self): | |
| # we wait for the SET_REPORT command to come | |
| return self.high_resolution_report_called | |
| def set_report(self, req, rnum, rtype, data): | |
| if rtype != self.UHID_FEATURE_REPORT: | |
| raise InvalidHIDCommunication(f"Unexpected report type: {rtype}") | |
| if rnum != 0x0: | |
| raise InvalidHIDCommunication(f"Unexpected report number: {rnum}") | |
| if len(data) != 1: | |
| raise InvalidHIDCommunication(f"Unexpected data: {data}, expected '[0]'") | |
| self.high_resolution_report_called = True | |
| return 0 | |
| class ResolutionMultiplierHWheelMouse(TwoWheelMouse): | |
| # fmt: off | |
| report_descriptor = [ | |
| 0x05, 0x01, # Usage Page (Generic Desktop) 0 | |
| 0x09, 0x02, # Usage (Mouse) 2 | |
| 0xa1, 0x01, # Collection (Application) 4 | |
| 0x05, 0x01, # .Usage Page (Generic Desktop) 6 | |
| 0x09, 0x02, # .Usage (Mouse) 8 | |
| 0xa1, 0x02, # .Collection (Logical) 10 | |
| 0x85, 0x1a, # ..Report ID (26) 12 | |
| 0x09, 0x01, # ..Usage (Pointer) 14 | |
| 0xa1, 0x00, # ..Collection (Physical) 16 | |
| 0x05, 0x09, # ...Usage Page (Button) 18 | |
| 0x19, 0x01, # ...Usage Minimum (1) 20 | |
| 0x29, 0x05, # ...Usage Maximum (5) 22 | |
| 0x95, 0x05, # ...Report Count (5) 24 | |
| 0x75, 0x01, # ...Report Size (1) 26 | |
| 0x15, 0x00, # ...Logical Minimum (0) 28 | |
| 0x25, 0x01, # ...Logical Maximum (1) 30 | |
| 0x81, 0x02, # ...Input (Data,Var,Abs) 32 | |
| 0x75, 0x03, # ...Report Size (3) 34 | |
| 0x95, 0x01, # ...Report Count (1) 36 | |
| 0x81, 0x01, # ...Input (Cnst,Arr,Abs) 38 | |
| 0x05, 0x01, # ...Usage Page (Generic Desktop) 40 | |
| 0x09, 0x30, # ...Usage (X) 42 | |
| 0x09, 0x31, # ...Usage (Y) 44 | |
| 0x95, 0x02, # ...Report Count (2) 46 | |
| 0x75, 0x10, # ...Report Size (16) 48 | |
| 0x16, 0x01, 0x80, # ...Logical Minimum (-32767) 50 | |
| 0x26, 0xff, 0x7f, # ...Logical Maximum (32767) 53 | |
| 0x81, 0x06, # ...Input (Data,Var,Rel) 56 | |
| 0xa1, 0x02, # ...Collection (Logical) 58 | |
| 0x85, 0x12, # ....Report ID (18) 60 | |
| 0x09, 0x48, # ....Usage (Resolution Multiplier) 62 | |
| 0x95, 0x01, # ....Report Count (1) 64 | |
| 0x75, 0x02, # ....Report Size (2) 66 | |
| 0x15, 0x00, # ....Logical Minimum (0) 68 | |
| 0x25, 0x01, # ....Logical Maximum (1) 70 | |
| 0x35, 0x01, # ....Physical Minimum (1) 72 | |
| 0x45, 0x0c, # ....Physical Maximum (12) 74 | |
| 0xb1, 0x02, # ....Feature (Data,Var,Abs) 76 | |
| 0x85, 0x1a, # ....Report ID (26) 78 | |
| 0x09, 0x38, # ....Usage (Wheel) 80 | |
| 0x35, 0x00, # ....Physical Minimum (0) 82 | |
| 0x45, 0x00, # ....Physical Maximum (0) 84 | |
| 0x95, 0x01, # ....Report Count (1) 86 | |
| 0x75, 0x10, # ....Report Size (16) 88 | |
| 0x16, 0x01, 0x80, # ....Logical Minimum (-32767) 90 | |
| 0x26, 0xff, 0x7f, # ....Logical Maximum (32767) 93 | |
| 0x81, 0x06, # ....Input (Data,Var,Rel) 96 | |
| 0xc0, # ...End Collection 98 | |
| 0xa1, 0x02, # ...Collection (Logical) 99 | |
| 0x85, 0x12, # ....Report ID (18) 101 | |
| 0x09, 0x48, # ....Usage (Resolution Multiplier) 103 | |
| 0x75, 0x02, # ....Report Size (2) 105 | |
| 0x15, 0x00, # ....Logical Minimum (0) 107 | |
| 0x25, 0x01, # ....Logical Maximum (1) 109 | |
| 0x35, 0x01, # ....Physical Minimum (1) 111 | |
| 0x45, 0x0c, # ....Physical Maximum (12) 113 | |
| 0xb1, 0x02, # ....Feature (Data,Var,Abs) 115 | |
| 0x35, 0x00, # ....Physical Minimum (0) 117 | |
| 0x45, 0x00, # ....Physical Maximum (0) 119 | |
| 0x75, 0x04, # ....Report Size (4) 121 | |
| 0xb1, 0x01, # ....Feature (Cnst,Arr,Abs) 123 | |
| 0x85, 0x1a, # ....Report ID (26) 125 | |
| 0x05, 0x0c, # ....Usage Page (Consumer Devices) 127 | |
| 0x95, 0x01, # ....Report Count (1) 129 | |
| 0x75, 0x10, # ....Report Size (16) 131 | |
| 0x16, 0x01, 0x80, # ....Logical Minimum (-32767) 133 | |
| 0x26, 0xff, 0x7f, # ....Logical Maximum (32767) 136 | |
| 0x0a, 0x38, 0x02, # ....Usage (AC Pan) 139 | |
| 0x81, 0x06, # ....Input (Data,Var,Rel) 142 | |
| 0xc0, # ...End Collection 144 | |
| 0xc0, # ..End Collection 145 | |
| 0xc0, # .End Collection 146 | |
| 0xc0, # End Collection 147 | |
| ] | |
| # fmt: on | |
| def __init__(self, rdesc=report_descriptor, name=None, input_info=None): | |
| super().__init__(rdesc, name, input_info) | |
| self.default_reportID = 0x1A | |
| # Feature Report 12, multiplier Feature value must be set to 0b0101, | |
| # i.e. 5. We should extract that from the descriptor instead | |
| # of hardcoding it here, but meanwhile this will do. | |
| self.set_feature_report = [0x12, 0x5] | |
| def set_report(self, req, rnum, rtype, data): | |
| super().set_report(req, rnum, rtype, data) | |
| self.wheel_multiplier = 12 | |
| self.hwheel_multiplier = 12 | |
| return 0 | |
| class BaseTest: | |
| class TestMouse(base.BaseTestCase.TestUhid): | |
| def test_buttons(self): | |
| """check for button reliability.""" | |
| uhdev = self.uhdev | |
| evdev = uhdev.get_evdev() | |
| syn_event = self.syn_event | |
| r = uhdev.event(0, 0, (None, True, None)) | |
| expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEventsIn((syn_event, expected_event), events) | |
| assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1 | |
| r = uhdev.event(0, 0, (None, False, None)) | |
| expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEventsIn((syn_event, expected_event), events) | |
| assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0 | |
| r = uhdev.event(0, 0, (None, None, True)) | |
| expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 1) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEventsIn((syn_event, expected_event), events) | |
| assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 1 | |
| r = uhdev.event(0, 0, (None, None, False)) | |
| expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 0) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEventsIn((syn_event, expected_event), events) | |
| assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 0 | |
| r = uhdev.event(0, 0, (True, None, None)) | |
| expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEventsIn((syn_event, expected_event), events) | |
| assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1 | |
| r = uhdev.event(0, 0, (False, None, None)) | |
| expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEventsIn((syn_event, expected_event), events) | |
| assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0 | |
| r = uhdev.event(0, 0, (True, True, None)) | |
| expected_event0 = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) | |
| expected_event1 = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEventsIn( | |
| (syn_event, expected_event0, expected_event1), events | |
| ) | |
| assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1 | |
| assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1 | |
| r = uhdev.event(0, 0, (False, None, None)) | |
| expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEventsIn((syn_event, expected_event), events) | |
| assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1 | |
| assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0 | |
| r = uhdev.event(0, 0, (None, False, None)) | |
| expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEventsIn((syn_event, expected_event), events) | |
| assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0 | |
| assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0 | |
| def test_relative(self): | |
| """Check for relative events.""" | |
| uhdev = self.uhdev | |
| syn_event = self.syn_event | |
| r = uhdev.event(0, -1) | |
| expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_Y, -1) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents((syn_event, expected_event), events) | |
| r = uhdev.event(1, 0) | |
| expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_X, 1) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents((syn_event, expected_event), events) | |
| r = uhdev.event(-1, 2) | |
| expected_event0 = libevdev.InputEvent(libevdev.EV_REL.REL_X, -1) | |
| expected_event1 = libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents( | |
| (syn_event, expected_event0, expected_event1), events | |
| ) | |
| class TestSimpleMouse(BaseTest.TestMouse): | |
| def create_device(self): | |
| return ButtonMouse() | |
| def test_rdesc(self): | |
| """Check that the testsuite actually manages to format the | |
| reports according to the report descriptors. | |
| No kernel device is used here""" | |
| uhdev = self.uhdev | |
| event = (0, 0, (None, None, None)) | |
| assert uhdev.fake_report(*event) == uhdev.create_report(*event) | |
| event = (0, 0, (None, True, None)) | |
| assert uhdev.fake_report(*event) == uhdev.create_report(*event) | |
| event = (0, 0, (True, True, None)) | |
| assert uhdev.fake_report(*event) == uhdev.create_report(*event) | |
| event = (0, 0, (False, False, False)) | |
| assert uhdev.fake_report(*event) == uhdev.create_report(*event) | |
| event = (1, 0, (True, False, True)) | |
| assert uhdev.fake_report(*event) == uhdev.create_report(*event) | |
| event = (-1, 0, (True, False, True)) | |
| assert uhdev.fake_report(*event) == uhdev.create_report(*event) | |
| event = (-5, 5, (True, False, True)) | |
| assert uhdev.fake_report(*event) == uhdev.create_report(*event) | |
| event = (-127, 127, (True, False, True)) | |
| assert uhdev.fake_report(*event) == uhdev.create_report(*event) | |
| event = (0, -128, (True, False, True)) | |
| with pytest.raises(hidtools.hid.RangeError): | |
| uhdev.create_report(*event) | |
| class TestWheelMouse(BaseTest.TestMouse): | |
| def create_device(self): | |
| return WheelMouse() | |
| def is_wheel_highres(self, uhdev): | |
| evdev = uhdev.get_evdev() | |
| assert evdev.has(libevdev.EV_REL.REL_WHEEL) | |
| return evdev.has(libevdev.EV_REL.REL_WHEEL_HI_RES) | |
| def test_wheel(self): | |
| uhdev = self.uhdev | |
| # check if the kernel is high res wheel compatible | |
| high_res_wheel = self.is_wheel_highres(uhdev) | |
| syn_event = self.syn_event | |
| # The Resolution Multiplier is applied to the HID reports, so we | |
| # need to pre-multiply too. | |
| mult = uhdev.wheel_multiplier | |
| r = uhdev.event(0, 0, wheels=1 * mult) | |
| expected = [syn_event] | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1)) | |
| if high_res_wheel: | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120)) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| r = uhdev.event(0, 0, wheels=-1 * mult) | |
| expected = [syn_event] | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -1)) | |
| if high_res_wheel: | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120)) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| r = uhdev.event(-1, 2, wheels=3 * mult) | |
| expected = [syn_event] | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1)) | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2)) | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 3)) | |
| if high_res_wheel: | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 360)) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| class TestTwoWheelMouse(TestWheelMouse): | |
| def create_device(self): | |
| return TwoWheelMouse() | |
| def is_hwheel_highres(self, uhdev): | |
| evdev = uhdev.get_evdev() | |
| assert evdev.has(libevdev.EV_REL.REL_HWHEEL) | |
| return evdev.has(libevdev.EV_REL.REL_HWHEEL_HI_RES) | |
| def test_ac_pan(self): | |
| uhdev = self.uhdev | |
| # check if the kernel is high res wheel compatible | |
| high_res_wheel = self.is_wheel_highres(uhdev) | |
| high_res_hwheel = self.is_hwheel_highres(uhdev) | |
| assert high_res_wheel == high_res_hwheel | |
| syn_event = self.syn_event | |
| # The Resolution Multiplier is applied to the HID reports, so we | |
| # need to pre-multiply too. | |
| hmult = uhdev.hwheel_multiplier | |
| vmult = uhdev.wheel_multiplier | |
| r = uhdev.event(0, 0, wheels=(0, 1 * hmult)) | |
| expected = [syn_event] | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1)) | |
| if high_res_hwheel: | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120)) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| r = uhdev.event(0, 0, wheels=(0, -1 * hmult)) | |
| expected = [syn_event] | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, -1)) | |
| if high_res_hwheel: | |
| expected.append( | |
| libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120) | |
| ) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| r = uhdev.event(-1, 2, wheels=(0, 3 * hmult)) | |
| expected = [syn_event] | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1)) | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2)) | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 3)) | |
| if high_res_hwheel: | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 360)) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| r = uhdev.event(-1, 2, wheels=(-3 * vmult, 4 * hmult)) | |
| expected = [syn_event] | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1)) | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2)) | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -3)) | |
| if high_res_wheel: | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -360)) | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 4)) | |
| if high_res_wheel: | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 480)) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| class TestResolutionMultiplierMouse(TestTwoWheelMouse): | |
| def create_device(self): | |
| return ResolutionMultiplierMouse() | |
| def is_wheel_highres(self, uhdev): | |
| high_res = super().is_wheel_highres(uhdev) | |
| if not high_res: | |
| # the kernel doesn't seem to support the high res wheel mice, | |
| # make sure we haven't triggered the feature | |
| assert uhdev.wheel_multiplier == 1 | |
| return high_res | |
| def test_resolution_multiplier_wheel(self): | |
| uhdev = self.uhdev | |
| if not self.is_wheel_highres(uhdev): | |
| pytest.skip("Kernel not compatible, we can not trigger the conditions") | |
| assert uhdev.wheel_multiplier > 1 | |
| assert 120 % uhdev.wheel_multiplier == 0 | |
| def test_wheel_with_multiplier(self): | |
| uhdev = self.uhdev | |
| if not self.is_wheel_highres(uhdev): | |
| pytest.skip("Kernel not compatible, we can not trigger the conditions") | |
| assert uhdev.wheel_multiplier > 1 | |
| syn_event = self.syn_event | |
| mult = uhdev.wheel_multiplier | |
| r = uhdev.event(0, 0, wheels=1) | |
| expected = [syn_event] | |
| expected.append( | |
| libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult) | |
| ) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| r = uhdev.event(0, 0, wheels=-1) | |
| expected = [syn_event] | |
| expected.append( | |
| libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120 / mult) | |
| ) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| expected = [syn_event] | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1)) | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2)) | |
| expected.append( | |
| libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult) | |
| ) | |
| for _ in range(mult - 1): | |
| r = uhdev.event(1, -2, wheels=1) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| r = uhdev.event(1, -2, wheels=1) | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1)) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| class TestBadResolutionMultiplierMouse(TestTwoWheelMouse): | |
| def create_device(self): | |
| return BadResolutionMultiplierMouse() | |
| def is_wheel_highres(self, uhdev): | |
| high_res = super().is_wheel_highres(uhdev) | |
| assert uhdev.wheel_multiplier == 1 | |
| return high_res | |
| def test_resolution_multiplier_wheel(self): | |
| uhdev = self.uhdev | |
| assert uhdev.wheel_multiplier == 1 | |
| class TestResolutionMultiplierHWheelMouse(TestResolutionMultiplierMouse): | |
| def create_device(self): | |
| return ResolutionMultiplierHWheelMouse() | |
| def is_hwheel_highres(self, uhdev): | |
| high_res = super().is_hwheel_highres(uhdev) | |
| if not high_res: | |
| # the kernel doesn't seem to support the high res wheel mice, | |
| # make sure we haven't triggered the feature | |
| assert uhdev.hwheel_multiplier == 1 | |
| return high_res | |
| def test_resolution_multiplier_ac_pan(self): | |
| uhdev = self.uhdev | |
| if not self.is_hwheel_highres(uhdev): | |
| pytest.skip("Kernel not compatible, we can not trigger the conditions") | |
| assert uhdev.hwheel_multiplier > 1 | |
| assert 120 % uhdev.hwheel_multiplier == 0 | |
| def test_ac_pan_with_multiplier(self): | |
| uhdev = self.uhdev | |
| if not self.is_hwheel_highres(uhdev): | |
| pytest.skip("Kernel not compatible, we can not trigger the conditions") | |
| assert uhdev.hwheel_multiplier > 1 | |
| syn_event = self.syn_event | |
| hmult = uhdev.hwheel_multiplier | |
| r = uhdev.event(0, 0, wheels=(0, 1)) | |
| expected = [syn_event] | |
| expected.append( | |
| libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult) | |
| ) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| r = uhdev.event(0, 0, wheels=(0, -1)) | |
| expected = [syn_event] | |
| expected.append( | |
| libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120 / hmult) | |
| ) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| expected = [syn_event] | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1)) | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2)) | |
| expected.append( | |
| libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult) | |
| ) | |
| for _ in range(hmult - 1): | |
| r = uhdev.event(1, -2, wheels=(0, 1)) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| r = uhdev.event(1, -2, wheels=(0, 1)) | |
| expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1)) | |
| events = uhdev.next_sync_events() | |
| self.debug_reports(r, uhdev, events) | |
| self.assertInputEvents(expected, events) | |
| class TestMiMouse(TestWheelMouse): | |
| def create_device(self): | |
| return MIDongleMIWirelessMouse() | |
| def assertInputEvents(self, expected_events, effective_events): | |
| # Buttons and x/y are spread over two HID reports, so we can get two | |
| # event frames for this device. | |
| remaining = self.assertInputEventsIn(expected_events, effective_events) | |
| try: | |
| remaining.remove(libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0)) | |
| except ValueError: | |
| # If there's no SYN_REPORT in the list, continue and let the | |
| # assert below print out the real error | |
| pass | |
| assert remaining == [] | |
| class TestBadReportDescriptorMouse(base.BaseTestCase.TestUhid): | |
| def create_device(self): | |
| return BadReportDescriptorMouse() | |
| def assertName(self, uhdev): | |
| pass |