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