mirror of
https://github.com/torvalds/linux.git
synced 2026-05-02 21:42:42 -04:00
selftests/hid: import base_gamepad.py from hid-tools
We need to slightly change base_device.py for supporting HID-BPF, so instead of monkey patching, let's just embed it in the kernel tree. Link: https://lore.kernel.org/r/20240410-bpf_sources-v1-16-a8bf16033ef8@kernel.org Reviewed-by: Peter Hutterer <peter.hutterer@who-t.net> Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
This commit is contained in:
238
tools/testing/selftests/hid/tests/base_gamepad.py
Normal file
238
tools/testing/selftests/hid/tests/base_gamepad.py
Normal file
@@ -0,0 +1,238 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
import libevdev
|
||||
|
||||
from .base_device import BaseDevice
|
||||
from hidtools.util import BusType
|
||||
|
||||
|
||||
class InvalidHIDCommunication(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GamepadData(object):
|
||||
pass
|
||||
|
||||
|
||||
class AxisMapping(object):
|
||||
"""Represents a mapping between a HID type
|
||||
and an evdev event"""
|
||||
|
||||
def __init__(self, hid, evdev=None):
|
||||
self.hid = hid.lower()
|
||||
|
||||
if evdev is None:
|
||||
evdev = f"ABS_{hid.upper()}"
|
||||
|
||||
self.evdev = libevdev.evbit("EV_ABS", evdev)
|
||||
|
||||
|
||||
class BaseGamepad(BaseDevice):
|
||||
buttons_map = {
|
||||
1: "BTN_SOUTH",
|
||||
2: "BTN_EAST",
|
||||
3: "BTN_C",
|
||||
4: "BTN_NORTH",
|
||||
5: "BTN_WEST",
|
||||
6: "BTN_Z",
|
||||
7: "BTN_TL",
|
||||
8: "BTN_TR",
|
||||
9: "BTN_TL2",
|
||||
10: "BTN_TR2",
|
||||
11: "BTN_SELECT",
|
||||
12: "BTN_START",
|
||||
13: "BTN_MODE",
|
||||
14: "BTN_THUMBL",
|
||||
15: "BTN_THUMBR",
|
||||
}
|
||||
|
||||
axes_map = {
|
||||
"left_stick": {
|
||||
"x": AxisMapping("x"),
|
||||
"y": AxisMapping("y"),
|
||||
},
|
||||
"right_stick": {
|
||||
"x": AxisMapping("z"),
|
||||
"y": AxisMapping("Rz"),
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, rdesc, application="Game Pad", name=None, input_info=None):
|
||||
assert rdesc is not None
|
||||
super().__init__(name, application, input_info=input_info, rdesc=rdesc)
|
||||
self.buttons = (1, 2, 3)
|
||||
self._buttons = {}
|
||||
self.left = (127, 127)
|
||||
self.right = (127, 127)
|
||||
self.hat_switch = 15
|
||||
assert self.parsed_rdesc is not None
|
||||
|
||||
self.fields = []
|
||||
for r in self.parsed_rdesc.input_reports.values():
|
||||
if r.application_name == self.application:
|
||||
self.fields.extend([f.usage_name for f in r])
|
||||
|
||||
def store_axes(self, which, gamepad, data):
|
||||
amap = self.axes_map[which]
|
||||
x, y = data
|
||||
setattr(gamepad, amap["x"].hid, x)
|
||||
setattr(gamepad, amap["y"].hid, y)
|
||||
|
||||
def create_report(
|
||||
self,
|
||||
*,
|
||||
left=(None, None),
|
||||
right=(None, None),
|
||||
hat_switch=None,
|
||||
buttons=None,
|
||||
reportID=None,
|
||||
application="Game Pad",
|
||||
):
|
||||
"""
|
||||
Return an input report for this device.
|
||||
|
||||
:param left: a tuple of absolute (x, y) value of the left joypad
|
||||
where ``None`` is "leave unchanged"
|
||||
:param right: a tuple of absolute (x, y) value of the right joypad
|
||||
where ``None`` is "leave unchanged"
|
||||
:param hat_switch: an absolute angular value of the hat switch
|
||||
(expressed in 1/8 of circle, 0 being North, 2 East)
|
||||
where ``None`` is "leave unchanged"
|
||||
:param buttons: a dict of index/bool for the button states,
|
||||
where ``None`` is "leave unchanged"
|
||||
:param reportID: the numeric report ID for this report, if needed
|
||||
:param application: the application used to report the values
|
||||
"""
|
||||
if buttons is not None:
|
||||
for i, b in buttons.items():
|
||||
if i not in self.buttons:
|
||||
raise InvalidHIDCommunication(
|
||||
f"button {i} is not part of this {self.application}"
|
||||
)
|
||||
if b is not None:
|
||||
self._buttons[i] = b
|
||||
|
||||
def replace_none_in_tuple(item, default):
|
||||
if item is None:
|
||||
item = (None, None)
|
||||
|
||||
if None in item:
|
||||
if item[0] is None:
|
||||
item = (default[0], item[1])
|
||||
if item[1] is None:
|
||||
item = (item[0], default[1])
|
||||
|
||||
return item
|
||||
|
||||
right = replace_none_in_tuple(right, self.right)
|
||||
self.right = right
|
||||
left = replace_none_in_tuple(left, self.left)
|
||||
self.left = left
|
||||
|
||||
if hat_switch is None:
|
||||
hat_switch = self.hat_switch
|
||||
else:
|
||||
self.hat_switch = hat_switch
|
||||
|
||||
reportID = reportID or self.default_reportID
|
||||
|
||||
gamepad = GamepadData()
|
||||
for i, b in self._buttons.items():
|
||||
gamepad.__setattr__(f"b{i}", int(b) if b is not None else 0)
|
||||
|
||||
self.store_axes("left_stick", gamepad, left)
|
||||
self.store_axes("right_stick", gamepad, right)
|
||||
gamepad.hatswitch = hat_switch # type: ignore ### gamepad is by default empty
|
||||
return super().create_report(
|
||||
gamepad, reportID=reportID, application=application
|
||||
)
|
||||
|
||||
def event(
|
||||
self, *, left=(None, None), right=(None, None), hat_switch=None, buttons=None
|
||||
):
|
||||
"""
|
||||
Send an input event on the default report ID.
|
||||
|
||||
:param left: a tuple of absolute (x, y) value of the left joypad
|
||||
where ``None`` is "leave unchanged"
|
||||
:param right: a tuple of absolute (x, y) value of the right joypad
|
||||
where ``None`` is "leave unchanged"
|
||||
:param hat_switch: an absolute angular value of the hat switch
|
||||
where ``None`` is "leave unchanged"
|
||||
:param buttons: a dict of index/bool for the button states,
|
||||
where ``None`` is "leave unchanged"
|
||||
"""
|
||||
r = self.create_report(
|
||||
left=left, right=right, hat_switch=hat_switch, buttons=buttons
|
||||
)
|
||||
self.call_input_event(r)
|
||||
return [r]
|
||||
|
||||
|
||||
class JoystickGamepad(BaseGamepad):
|
||||
buttons_map = {
|
||||
1: "BTN_TRIGGER",
|
||||
2: "BTN_THUMB",
|
||||
3: "BTN_THUMB2",
|
||||
4: "BTN_TOP",
|
||||
5: "BTN_TOP2",
|
||||
6: "BTN_PINKIE",
|
||||
7: "BTN_BASE",
|
||||
8: "BTN_BASE2",
|
||||
9: "BTN_BASE3",
|
||||
10: "BTN_BASE4",
|
||||
11: "BTN_BASE5",
|
||||
12: "BTN_BASE6",
|
||||
13: "BTN_DEAD",
|
||||
}
|
||||
|
||||
axes_map = {
|
||||
"left_stick": {
|
||||
"x": AxisMapping("x"),
|
||||
"y": AxisMapping("y"),
|
||||
},
|
||||
"right_stick": {
|
||||
"x": AxisMapping("rudder"),
|
||||
"y": AxisMapping("throttle"),
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, rdesc, application="Joystick", name=None, input_info=None):
|
||||
super().__init__(rdesc, application, name, input_info)
|
||||
|
||||
def create_report(
|
||||
self,
|
||||
*,
|
||||
left=(None, None),
|
||||
right=(None, None),
|
||||
hat_switch=None,
|
||||
buttons=None,
|
||||
reportID=None,
|
||||
application=None,
|
||||
):
|
||||
"""
|
||||
Return an input report for this device.
|
||||
|
||||
:param left: a tuple of absolute (x, y) value of the left joypad
|
||||
where ``None`` is "leave unchanged"
|
||||
:param right: a tuple of absolute (x, y) value of the right joypad
|
||||
where ``None`` is "leave unchanged"
|
||||
:param hat_switch: an absolute angular value of the hat switch
|
||||
where ``None`` is "leave unchanged"
|
||||
:param buttons: a dict of index/bool for the button states,
|
||||
where ``None`` is "leave unchanged"
|
||||
:param reportID: the numeric report ID for this report, if needed
|
||||
:param application: the application for this report, if needed
|
||||
"""
|
||||
if application is None:
|
||||
application = "Joystick"
|
||||
return super().create_report(
|
||||
left=left,
|
||||
right=right,
|
||||
hat_switch=hat_switch,
|
||||
buttons=buttons,
|
||||
reportID=reportID,
|
||||
application=application,
|
||||
)
|
||||
|
||||
def store_right_joystick(self, gamepad, data):
|
||||
gamepad.rudder, gamepad.throttle = data
|
||||
Reference in New Issue
Block a user