#!/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 = [
0 x05,
0 x01,
# .Usage Page (Generic Desktop) 0
0 x09,
0 x02,
# .Usage (Mouse) 2
0 xa1,
0 x01,
# .Collection (Application) 4
0 x09,
0 x02,
# ..Usage (Mouse) 6
0 xa1,
0 x02,
# ..Collection (Logical) 8
0 x09,
0 x01,
# ...Usage (Pointer) 10
0 xa1,
0 x00,
# ...Collection (Physical) 12
0 x05,
0 x09,
# ....Usage Page (Button) 14
0 x19,
0 x01,
# ....Usage Minimum (1) 16
0 x29,
0 x03,
# ....Usage Maximum (3) 18
0 x15,
0 x00,
# ....Logical Minimum (0) 20
0 x25,
0 x01,
# ....Logical Maximum (1) 22
0 x75,
0 x01,
# ....Report Size (1) 24
0 x95,
0 x03,
# ....Report Count (3) 26
0 x81,
0 x02,
# ....Input (Data,Var,Abs) 28
0 x75,
0 x05,
# ....Report Size (5) 30
0 x95,
0 x01,
# ....Report Count (1) 32
0 x81,
0 x03,
# ....Input (Cnst,Var,Abs) 34
0 x05,
0 x01,
# ....Usage Page (Generic Desktop) 36
0 x09,
0 x30,
# ....Usage (X) 38
0 x09,
0 x31,
# ....Usage (Y) 40
0 x15,
0 x81,
# ....Logical Minimum (-127) 42
0 x25,
0 x7f,
# ....Logical Maximum (127) 44
0 x75,
0 x08,
# ....Report Size (8) 46
0 x95,
0 x02,
# ....Report Count (2) 48
0 x81,
0 x06,
# ....Input (Data,Var,Rel) 50
0 xc0,
# ...End Collection 52
0 xc0,
# ..End Collection 53
0 xc0,
# .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 = [
0 x05,
0 x01,
# Usage Page (Generic Desktop) 0
0 x09,
0 x02,
# Usage (Mouse) 2
0 xa1,
0 x01,
# Collection (Application) 4
0 x05,
0 x09,
# .Usage Page (Button) 6
0 x19,
0 x01,
# .Usage Minimum (1) 8
0 x29,
0 x03,
# .Usage Maximum (3) 10
0 x15,
0 x00,
# .Logical Minimum (0) 12
0 x25,
0 x01,
# .Logical Maximum (1) 14
0 x95,
0 x03,
# .Report Count (3) 16
0 x75,
0 x01,
# .Report Size (1) 18
0 x81,
0 x02,
# .Input (Data,Var,Abs) 20
0 x95,
0 x01,
# .Report Count (1) 22
0 x75,
0 x05,
# .Report Size (5) 24
0 x81,
0 x03,
# .Input (Cnst,Var,Abs) 26
0 x05,
0 x01,
# .Usage Page (Generic Desktop) 28
0 x09,
0 x01,
# .Usage (Pointer) 30
0 xa1,
0 x00,
# .Collection (Physical) 32
0 x09,
0 x30,
# ..Usage (X) 34
0 x09,
0 x31,
# ..Usage (Y) 36
0 x15,
0 x81,
# ..Logical Minimum (-127) 38
0 x25,
0 x7f,
# ..Logical Maximum (127) 40
0 x75,
0 x08,
# ..Report Size (8) 42
0 x95,
0 x02,
# ..Report Count (2) 44
0 x81,
0 x06,
# ..Input (Data,Var,Rel) 46
0 xc0,
# .End Collection 48
0 x09,
0 x38,
# .Usage (Wheel) 49
0 x15,
0 x81,
# .Logical Minimum (-127) 51
0 x25,
0 x7f,
# .Logical Maximum (127) 53
0 x75,
0 x08,
# .Report Size (8) 55
0 x95,
0 x01,
# .Report Count (1) 57
0 x81,
0 x06,
# .Input (Data,Var,Rel) 59
0 xc0,
# 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 = [
0 x05,
0 x01,
# Usage Page (Generic Desktop) 0
0 x09,
0 x02,
# Usage (Mouse) 2
0 xa1,
0 x01,
# Collection (Application) 4
0 x09,
0 x01,
# .Usage (Pointer) 6
0 xa1,
0 x00,
# .Collection (Physical) 8
0 x05,
0 x09,
# ..Usage Page (Button) 10
0 x19,
0 x01,
# ..Usage Minimum (1) 12
0 x29,
0 x10,
# ..Usage Maximum (16) 14
0 x15,
0 x00,
# ..Logical Minimum (0) 16
0 x25,
0 x01,
# ..Logical Maximum (1) 18
0 x95,
0 x10,
# ..Report Count (16) 20
0 x75,
0 x01,
# ..Report Size (1) 22
0 x81,
0 x02,
# ..Input (Data,Var,Abs) 24
0 x05,
0 x01,
# ..Usage Page (Generic Desktop) 26
0 x16,
0 x01,
0 x80,
# ..Logical Minimum (-32767) 28
0 x26,
0 xff,
0 x7f,
# ..Logical Maximum (32767) 31
0 x75,
0 x10,
# ..Report Size (16) 34
0 x95,
0 x02,
# ..Report Count (2) 36
0 x09,
0 x30,
# ..Usage (X) 38
0 x09,
0 x31,
# ..Usage (Y) 40
0 x81,
0 x06,
# ..Input (Data,Var,Rel) 42
0 x15,
0 x81,
# ..Logical Minimum (-127) 44
0 x25,
0 x7f,
# ..Logical Maximum (127) 46
0 x75,
0 x08,
# ..Report Size (8) 48
0 x95,
0 x01,
# ..Report Count (1) 50
0 x09,
0 x38,
# ..Usage (Wheel) 52
0 x81,
0 x06,
# ..Input (Data,Var,Rel) 54
0 x05,
0 x0c,
# ..Usage Page (Consumer Devices) 56
0 x0a,
0 x38,
0 x02,
# ..Usage (AC Pan) 58
0 x95,
0 x01,
# ..Report Count (1) 61
0 x81,
0 x06,
# ..Input (Data,Var,Rel) 63
0 xc0,
# .End Collection 65
0 xc0,
# 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 = [
0 x05,
0 x01,
# Usage Page (Generic Desktop)
0 x09,
0 x02,
# Usage (Mouse)
0 xa1,
0 x01,
# Collection (Application)
0 x85,
0 x01,
# .Report ID (1)
0 x09,
0 x01,
# .Usage (Pointer)
0 xa1,
0 x00,
# .Collection (Physical)
0 x95,
0 x05,
# ..Report Count (5)
0 x75,
0 x01,
# ..Report Size (1)
0 x05,
0 x09,
# ..Usage Page (Button)
0 x19,
0 x01,
# ..Usage Minimum (1)
0 x29,
0 x05,
# ..Usage Maximum (5)
0 x15,
0 x00,
# ..Logical Minimum (0)
0 x25,
0 x01,
# ..Logical Maximum (1)
0 x81,
0 x02,
# ..Input (Data,Var,Abs)
0 x95,
0 x01,
# ..Report Count (1)
0 x75,
0 x03,
# ..Report Size (3)
0 x81,
0 x01,
# ..Input (Cnst,Arr,Abs)
0 x75,
0 x08,
# ..Report Size (8)
0 x95,
0 x01,
# ..Report Count (1)
0 x05,
0 x01,
# ..Usage Page (Generic Desktop)
0 x09,
0 x38,
# ..Usage (Wheel)
0 x15,
0 x81,
# ..Logical Minimum (-127)
0 x25,
0 x7f,
# ..Logical Maximum (127)
0 x81,
0 x06,
# ..Input (Data,Var,Rel)
0 x05,
0 x0c,
# ..Usage Page (Consumer Devices)
0 x0a,
0 x38,
0 x02,
# ..Usage (AC Pan)
0 x95,
0 x01,
# ..Report Count (1)
0 x81,
0 x06,
# ..Input (Data,Var,Rel)
0 xc0,
# .End Collection
0 x85,
0 x02,
# .Report ID (2)
0 x09,
0 x01,
# .Usage (Consumer Control)
0 xa1,
0 x00,
# .Collection (Physical)
0 x75,
0 x0c,
# ..Report Size (12)
0 x95,
0 x02,
# ..Report Count (2)
0 x05,
0 x01,
# ..Usage Page (Generic Desktop)
0 x09,
0 x30,
# ..Usage (X)
0 x09,
0 x31,
# ..Usage (Y)
0 x16,
0 x01,
0 xf8,
# ..Logical Minimum (-2047)
0 x26,
0 xff,
0 x07,
# ..Logical Maximum (2047)
0 x81,
0 x06,
# ..Input (Data,Var,Rel)
0 xc0,
# .End Collection
0 xc0,
# End Collection
0 x05,
0 x0c,
# Usage Page (Consumer Devices)
0 x09,
0 x01,
# Usage (Consumer Control)
0 xa1,
0 x01,
# Collection (Application)
0 x85,
0 x03,
# .Report ID (3)
0 x15,
0 x00,
# .Logical Minimum (0)
0 x25,
0 x01,
# .Logical Maximum (1)
0 x75,
0 x01,
# .Report Size (1)
0 x95,
0 x01,
# .Report Count (1)
0 x09,
0 xcd,
# .Usage (Play/Pause)
0 x81,
0 x06,
# .Input (Data,Var,Rel)
0 x0a,
0 x83,
0 x01,
# .Usage (AL Consumer Control Config)
0 x81,
0 x06,
# .Input (Data,Var,Rel)
0 x09,
0 xb5,
# .Usage (Scan Next Track)
0 x81,
0 x06,
# .Input (Data,Var,Rel)
0 x09,
0 xb6,
# .Usage (Scan Previous Track)
0 x81,
0 x06,
# .Input (Data,Var,Rel)
0 x09,
0 xea,
# .Usage (Volume Down)
0 x81,
0 x06,
# .Input (Data,Var,Rel)
0 x09,
0 xe9,
# .Usage (Volume Up)
0 x81,
0 x06,
# .Input (Data,Var,Rel)
0 x0a,
0 x25,
0 x02,
# .Usage (AC Forward)
0 x81,
0 x06,
# .Input (Data,Var,Rel)
0 x0a,
0 x24,
0 x02,
# .Usage (AC Back)
0 x81,
0 x06,
# .Input (Data,Var,Rel)
0 xc0,
# End Collection
]
# fmt: on
device_input_info = (BusType.USB,
0 x2717,
0 x003B)
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 = [
0 x05,
0 x01,
# Usage Page (Generic Desktop) 83
0 x09,
0 x02,
# Usage (Mouse) 85
0 xa1,
0 x01,
# Collection (Application) 87
0 x05,
0 x01,
# .Usage Page (Generic Desktop) 89
0 x09,
0 x02,
# .Usage (Mouse) 91
0 xa1,
0 x02,
# .Collection (Logical) 93
0 x85,
0 x11,
# ..Report ID (17) 95
0 x09,
0 x01,
# ..Usage (Pointer) 97
0 xa1,
0 x00,
# ..Collection (Physical) 99
0 x05,
0 x09,
# ...Usage Page (Button) 101
0 x19,
0 x01,
# ...Usage Minimum (1) 103
0 x29,
0 x03,
# ...Usage Maximum (3) 105
0 x95,
0 x03,
# ...Report Count (3) 107
0 x75,
0 x01,
# ...Report Size (1) 109
0 x25,
0 x01,
# ...Logical Maximum (1) 111
0 x81,
0 x02,
# ...Input (Data,Var,Abs) 113
0 x95,
0 x01,
# ...Report Count (1) 115
0 x81,
0 x01,
# ...Input (Cnst,Arr,Abs) 117
0 x09,
0 x05,
# ...Usage (Vendor Usage 0x05) 119
0 x81,
0 x02,
# ...Input (Data,Var,Abs) 121
0 x95,
0 x03,
# ...Report Count (3) 123
0 x81,
0 x01,
# ...Input (Cnst,Arr,Abs) 125
0 x05,
0 x01,
# ...Usage Page (Generic Desktop) 127
0 x09,
0 x30,
# ...Usage (X) 129
0 x09,
0 x31,
# ...Usage (Y) 131
0 x95,
0 x02,
# ...Report Count (2) 133
0 x75,
0 x08,
# ...Report Size (8) 135
0 x15,
0 x81,
# ...Logical Minimum (-127) 137
0 x25,
0 x7f,
# ...Logical Maximum (127) 139
0 x81,
0 x06,
# ...Input (Data,Var,Rel) 141
0 xa1,
0 x02,
# ...Collection (Logical) 143
0 x85,
0 x12,
# ....Report ID (18) 145
0 x09,
0 x48,
# ....Usage (Resolution Multiplier) 147
0 x95,
0 x01,
# ....Report Count (1) 149
0 x75,
0 x02,
# ....Report Size (2) 151
0 x15,
0 x00,
# ....Logical Minimum (0) 153
0 x25,
0 x01,
# ....Logical Maximum (1) 155
0 x35,
0 x01,
# ....Physical Minimum (1) 157
0 x45,
0 x04,
# ....Physical Maximum (4) 159
0 xb1,
0 x02,
# ....Feature (Data,Var,Abs) 161
0 x35,
0 x00,
# ....Physical Minimum (0) 163
0 x45,
0 x00,
# ....Physical Maximum (0) 165
0 x75,
0 x06,
# ....Report Size (6) 167
0 xb1,
0 x01,
# ....Feature (Cnst,Arr,Abs) 169
0 x85,
0 x11,
# ....Report ID (17) 171
0 x09,
0 x38,
# ....Usage (Wheel) 173
0 x15,
0 x81,
# ....Logical Minimum (-127) 175
0 x25,
0 x7f,
# ....Logical Maximum (127) 177
0 x75,
0 x08,
# ....Report Size (8) 179
0 x81,
0 x06,
# ....Input (Data,Var,Rel) 181
0 xc0,
# ...End Collection 183
0 x05,
0 x0c,
# ...Usage Page (Consumer Devices) 184
0 x75,
0 x08,
# ...Report Size (8) 186
0 x0a,
0 x38,
0 x02,
# ...Usage (AC Pan) 188
0 x81,
0 x06,
# ...Input (Data,Var,Rel) 191
0 xc0,
# ..End Collection 193
0 xc0,
# .End Collection 194
0 xc0,
# 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 =
0 x11
# 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 = [
0 x12,
0 x1]
def set_report(self, req, rnum, rtype, data):
if rtype != self.UHID_FEATURE_REPORT:
raise InvalidHIDCommunication(f
"Unexpected report type: {rtype}" )
if rnum !=
0 x12:
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 = [
0 x96,
0 x01,
0 x00,
# Report Count (1) 0
0 x06,
0 x01,
0 x00,
# Usage Page (Generic Desktop) 3
# 0x03, 0x00, 0x00, 0x00, 0x00, # Ignored by the kernel somehow
0 x2a,
0 x90,
0 xa0,
# Usage Maximum (41104) 6
0 x27,
0 x00,
0 x00,
0 x00,
0 x00,
# Logical Maximum (0) 9
0 xb3,
0 x81,
0 x3e,
0 x25,
0 x03,
# Feature (Cnst,Arr,Abs,Vol) 14
0 x1b,
0 xdd,
0 xe8,
0 x40,
0 x50,
# Usage Minimum (1346431197) 19
0 x3b,
0 x5d,
0 x8c,
0 x3d,
0 xda,
# Designator Index 24
]
# fmt: on
def __init__(
self, rdesc=report_descriptor, name=
None , input_info=(
3 ,
0 x045E,
0 x07DA)
):
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 !=
0 x0:
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 = [
0 x05,
0 x01,
# Usage Page (Generic Desktop) 0
0 x09,
0 x02,
# Usage (Mouse) 2
0 xa1,
0 x01,
# Collection (Application) 4
0 x05,
0 x01,
# .Usage Page (Generic Desktop) 6
0 x09,
0 x02,
# .Usage (Mouse) 8
0 xa1,
0 x02,
# .Collection (Logical) 10
0 x85,
0 x1a,
# ..Report ID (26) 12
0 x09,
0 x01,
# ..Usage (Pointer) 14
0 xa1,
0 x00,
# ..Collection (Physical) 16
0 x05,
0 x09,
# ...Usage Page (Button) 18
0 x19,
0 x01,
# ...Usage Minimum (1) 20
0 x29,
0 x05,
# ...Usage Maximum (5) 22
0 x95,
0 x05,
# ...Report Count (5) 24
0 x75,
0 x01,
# ...Report Size (1) 26
0 x15,
0 x00,
# ...Logical Minimum (0) 28
0 x25,
0 x01,
# ...Logical Maximum (1) 30
0 x81,
0 x02,
# ...Input (Data,Var,Abs) 32
0 x75,
0 x03,
# ...Report Size (3) 34
0 x95,
0 x01,
# ...Report Count (1) 36
0 x81,
0 x01,
# ...Input (Cnst,Arr,Abs) 38
0 x05,
0 x01,
# ...Usage Page (Generic Desktop) 40
0 x09,
0 x30,
# ...Usage (X) 42
0 x09,
0 x31,
# ...Usage (Y) 44
0 x95,
0 x02,
# ...Report Count (2) 46
0 x75,
0 x10,
# ...Report Size (16) 48
0 x16,
0 x01,
0 x80,
# ...Logical Minimum (-32767) 50
0 x26,
0 xff,
0 x7f,
# ...Logical Maximum (32767) 53
0 x81,
0 x06,
# ...Input (Data,Var,Rel) 56
0 xa1,
0 x02,
# ...Collection (Logical) 58
0 x85,
0 x12,
# ....Report ID (18) 60
0 x09,
0 x48,
# ....Usage (Resolution Multiplier) 62
0 x95,
0 x01,
# ....Report Count (1) 64
0 x75,
0 x02,
# ....Report Size (2) 66
0 x15,
0 x00,
# ....Logical Minimum (0) 68
0 x25,
0 x01,
# ....Logical Maximum (1) 70
0 x35,
0 x01,
# ....Physical Minimum (1) 72
0 x45,
0 x0c,
# ....Physical Maximum (12) 74
0 xb1,
0 x02,
# ....Feature (Data,Var,Abs) 76
0 x85,
0 x1a,
# ....Report ID (26) 78
0 x09,
0 x38,
# ....Usage (Wheel) 80
0 x35,
0 x00,
# ....Physical Minimum (0) 82
0 x45,
0 x00,
# ....Physical Maximum (0) 84
0 x95,
0 x01,
# ....Report Count (1) 86
0 x75,
0 x10,
# ....Report Size (16) 88
0 x16,
0 x01,
0 x80,
# ....Logical Minimum (-32767) 90
0 x26,
0 xff,
0 x7f,
# ....Logical Maximum (32767) 93
0 x81,
0 x06,
# ....Input (Data,Var,Rel) 96
0 xc0,
# ...End Collection 98
0 xa1,
0 x02,
# ...Collection (Logical) 99
0 x85,
0 x12,
# ....Report ID (18) 101
0 x09,
0 x48,
# ....Usage (Resolution Multiplier) 103
0 x75,
0 x02,
# ....Report Size (2) 105
0 x15,
0 x00,
# ....Logical Minimum (0) 107
0 x25,
0 x01,
# ....Logical Maximum (1) 109
0 x35,
0 x01,
# ....Physical Minimum (1) 111
0 x45,
0 x0c,
# ....Physical Maximum (12) 113
0 xb1,
0 x02,
# ....Feature (Data,Var,Abs) 115
0 x35,
0 x00,
# ....Physical Minimum (0) 117
0 x45,
0 x00,
# ....Physical Maximum (0) 119
0 x75,
0 x04,
# ....Report Size (4) 121
0 xb1,
0 x01,
# ....Feature (Cnst,Arr,Abs) 123
0 x85,
0 x1a,
# ....Report ID (26) 125
0 x05,
0 x0c,
# ....Usage Page (Consumer Devices) 127
0 x95,
0 x01,
# ....Report Count (1) 129
0 x75,
0 x10,
# ....Report Size (16) 131
0 x16,
0 x01,
0 x80,
# ....Logical Minimum (-32767) 133
0 x26,
0 xff,
0 x7f,
# ....Logical Maximum (32767) 136
0 x0a,
0 x38,
0 x02,
# ....Usage (AC Pan) 139
0 x81,
0 x06,
# ....Input (Data,Var,Rel) 142
0 xc0,
# ...End Collection 144
0 xc0,
# ..End Collection 145
0 xc0,
# .End Collection 146
0 xc0,
# 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 =
0 x1A
# 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 = [
0 x12,
0 x5]
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
Messung V0.5 in Prozent C=95 H=87 G=90
¤ Dauer der Verarbeitung: 0.23 Sekunden
(vorverarbeitet am 2026-06-07)
¤
*© Formatika GbR, Deutschland