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 1048