#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2017 Red Hat, Inc.
#
from . import base
import hidtools.hid
from hidtools.util import BusType
import libevdev
import logging
import pytest
logger = logging.getLogger("hidtools.test.mouse")
# workaround https://gitlab.freedesktop.org/libevdev/python-libevdev/issues/6
try:
libevdev.EV_REL.REL_WHEEL_HI_RES
except AttributeError:
libevdev.EV_REL.REL_WHEEL_HI_RES = libevdev.EV_REL.REL_0B
libevdev.EV_REL.REL_HWHEEL_HI_RES = libevdev.EV_REL.REL_0C
class InvalidHIDCommunication(Exception):
pass
class MouseData(object):
pass
class BaseMouse(base.UHIDTestDevice):
def __init__(self, rdesc, name=None, input_info=None):
assert rdesc is not None
super().__init__(name, "Mouse", input_info=input_info, rdesc=rdesc)
self.left = False
self.right = False
self.middle = False
def create_report(self, x, y, buttons=None, wheels=None, reportID=None):
"""
Return an input report for this device.
:param x: relative x
:param y: relative y
:param buttons: a (l, r, m) tuple of bools for the button states,
where ``None`` is "leave unchanged"
:param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for
the two wheels
:param reportID: the numeric report ID for this report, if needed
"""
if buttons is not None:
left, right, middle = buttons
if left is not None:
self.left = left
if right is not None:
self.right = right
if middle is not None:
self.middle = middle
left = self.left
right = self.right
middle = self.middle
# Note: the BaseMouse doesn't actually have a wheel but the
# create_report magic only fills in those fields exist, so let's
# make this generic here.
wheel, acpan = 0, 0
if wheels is not None:
if isinstance(wheels, tuple):
wheel = wheels[0]
acpan = wheels[1]
else:
wheel = wheels
reportID = reportID or self.default_reportID
mouse = MouseData()
mouse.b1 = int(left)
mouse.b2 = int(right)
mouse.b3 = int(middle)
mouse.x = x
mouse.y = y
mouse.wheel = wheel
mouse.acpan = acpan
return super().create_report(mouse, reportID=reportID)
def event(self, x, y, buttons=None, wheels=None):
"""
Send an input event on the default report ID.
:param x: relative x
:param y: relative y
:param buttons: a (l, r, m) tuple of bools for the button states,
where ``None`` is "leave unchanged"
:param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for
the two wheels
"""
r = self.create_report(x, y, buttons, wheels)
self.call_input_event(r)
return [r]
class ButtonMouse(BaseMouse):
# fmt: off
report_descriptor = [
0x05, 0x01, # .Usage Page (Generic Desktop) 0
0x09, 0x02, # .Usage (Mouse) 2
0xa1, 0x01, # .Collection (Application) 4
0x09, 0x02, # ..Usage (Mouse) 6
0xa1, 0x02, # ..Collection (Logical) 8
0x09, 0x01, # ...Usage (Pointer) 10
0xa1, 0x00, # ...Collection (Physical) 12
0x05, 0x09, # ....Usage Page (Button) 14
0x19, 0x01, # ....Usage Minimum (1) 16
0x29, 0x03, # ....Usage Maximum (3) 18
0x15, 0x00, # ....Logical Minimum (0) 20
0x25, 0x01, # ....Logical Maximum (1) 22
0x75, 0x01, # ....Report Size (1) 24
0x95, 0x03, # ....Report Count (3) 26
0x81, 0x02, # ....Input (Data,Var,Abs) 28
0x75, 0x05, # ....Report Size (5) 30
0x95, 0x01, # ....Report Count (1) 32
0x81, 0x03, # ....Input (Cnst,Var,Abs) 34
0x05, 0x01, # ....Usage Page (Generic Desktop) 36
0x09, 0x30, # ....Usage (X) 38
0x09, 0x31, # ....Usage (Y) 40
0x15, 0x81, # ....Logical Minimum (-127) 42
0x25, 0x7f, # ....Logical Maximum (127) 44
0x75, 0x08, # ....Report Size (8) 46
0x95, 0x02, # ....Report Count (2) 48
0x81, 0x06, # ....Input (Data,Var,Rel) 50
0xc0, # ...End Collection 52
0xc0, # ..End Collection 53
0xc0, # .End Collection 54
]
# fmt: on
def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
super().__init__(rdesc, name, input_info)
def fake_report(self, x, y, buttons):
if buttons is not None:
left, right, middle = buttons
if left is None:
left = self.left
if right is None:
right = self.right
if middle is None:
middle = self.middle
else:
left = self.left
right = self.right
middle = self.middle
button_mask = sum(1 << i for i, b in enumerate([left, right, middle]) if b)
x = max(-127, min(127, x))
y = max(-127, min(127, y))
x = hidtools.util.to_twos_comp(x, 8)
y = hidtools.util.to_twos_comp(y, 8)
return [button_mask, x, y]
class WheelMouse(ButtonMouse):
# fmt: off
report_descriptor = [
0x05, 0x01, # Usage Page (Generic Desktop) 0
0x09, 0x02, # Usage (Mouse) 2
0xa1, 0x01, # Collection (Application) 4
0x05