Skip to content

Commit c6b03c7

Browse files
author
Benjamin Tissoires
committed
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/[email protected] Reviewed-by: Peter Hutterer <[email protected]> Signed-off-by: Benjamin Tissoires <[email protected]>
1 parent 51de9ee commit c6b03c7

File tree

2 files changed

+242
-1
lines changed

2 files changed

+242
-1
lines changed
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
# SPDX-License-Identifier: GPL-2.0
2+
import libevdev
3+
4+
from .base_device import BaseDevice
5+
from hidtools.util import BusType
6+
7+
8+
class InvalidHIDCommunication(Exception):
9+
pass
10+
11+
12+
class GamepadData(object):
13+
pass
14+
15+
16+
class AxisMapping(object):
17+
"""Represents a mapping between a HID type
18+
and an evdev event"""
19+
20+
def __init__(self, hid, evdev=None):
21+
self.hid = hid.lower()
22+
23+
if evdev is None:
24+
evdev = f"ABS_{hid.upper()}"
25+
26+
self.evdev = libevdev.evbit("EV_ABS", evdev)
27+
28+
29+
class BaseGamepad(BaseDevice):
30+
buttons_map = {
31+
1: "BTN_SOUTH",
32+
2: "BTN_EAST",
33+
3: "BTN_C",
34+
4: "BTN_NORTH",
35+
5: "BTN_WEST",
36+
6: "BTN_Z",
37+
7: "BTN_TL",
38+
8: "BTN_TR",
39+
9: "BTN_TL2",
40+
10: "BTN_TR2",
41+
11: "BTN_SELECT",
42+
12: "BTN_START",
43+
13: "BTN_MODE",
44+
14: "BTN_THUMBL",
45+
15: "BTN_THUMBR",
46+
}
47+
48+
axes_map = {
49+
"left_stick": {
50+
"x": AxisMapping("x"),
51+
"y": AxisMapping("y"),
52+
},
53+
"right_stick": {
54+
"x": AxisMapping("z"),
55+
"y": AxisMapping("Rz"),
56+
},
57+
}
58+
59+
def __init__(self, rdesc, application="Game Pad", name=None, input_info=None):
60+
assert rdesc is not None
61+
super().__init__(name, application, input_info=input_info, rdesc=rdesc)
62+
self.buttons = (1, 2, 3)
63+
self._buttons = {}
64+
self.left = (127, 127)
65+
self.right = (127, 127)
66+
self.hat_switch = 15
67+
assert self.parsed_rdesc is not None
68+
69+
self.fields = []
70+
for r in self.parsed_rdesc.input_reports.values():
71+
if r.application_name == self.application:
72+
self.fields.extend([f.usage_name for f in r])
73+
74+
def store_axes(self, which, gamepad, data):
75+
amap = self.axes_map[which]
76+
x, y = data
77+
setattr(gamepad, amap["x"].hid, x)
78+
setattr(gamepad, amap["y"].hid, y)
79+
80+
def create_report(
81+
self,
82+
*,
83+
left=(None, None),
84+
right=(None, None),
85+
hat_switch=None,
86+
buttons=None,
87+
reportID=None,
88+
application="Game Pad",
89+
):
90+
"""
91+
Return an input report for this device.
92+
93+
:param left: a tuple of absolute (x, y) value of the left joypad
94+
where ``None`` is "leave unchanged"
95+
:param right: a tuple of absolute (x, y) value of the right joypad
96+
where ``None`` is "leave unchanged"
97+
:param hat_switch: an absolute angular value of the hat switch
98+
(expressed in 1/8 of circle, 0 being North, 2 East)
99+
where ``None`` is "leave unchanged"
100+
:param buttons: a dict of index/bool for the button states,
101+
where ``None`` is "leave unchanged"
102+
:param reportID: the numeric report ID for this report, if needed
103+
:param application: the application used to report the values
104+
"""
105+
if buttons is not None:
106+
for i, b in buttons.items():
107+
if i not in self.buttons:
108+
raise InvalidHIDCommunication(
109+
f"button {i} is not part of this {self.application}"
110+
)
111+
if b is not None:
112+
self._buttons[i] = b
113+
114+
def replace_none_in_tuple(item, default):
115+
if item is None:
116+
item = (None, None)
117+
118+
if None in item:
119+
if item[0] is None:
120+
item = (default[0], item[1])
121+
if item[1] is None:
122+
item = (item[0], default[1])
123+
124+
return item
125+
126+
right = replace_none_in_tuple(right, self.right)
127+
self.right = right
128+
left = replace_none_in_tuple(left, self.left)
129+
self.left = left
130+
131+
if hat_switch is None:
132+
hat_switch = self.hat_switch
133+
else:
134+
self.hat_switch = hat_switch
135+
136+
reportID = reportID or self.default_reportID
137+
138+
gamepad = GamepadData()
139+
for i, b in self._buttons.items():
140+
gamepad.__setattr__(f"b{i}", int(b) if b is not None else 0)
141+
142+
self.store_axes("left_stick", gamepad, left)
143+
self.store_axes("right_stick", gamepad, right)
144+
gamepad.hatswitch = hat_switch # type: ignore ### gamepad is by default empty
145+
return super().create_report(
146+
gamepad, reportID=reportID, application=application
147+
)
148+
149+
def event(
150+
self, *, left=(None, None), right=(None, None), hat_switch=None, buttons=None
151+
):
152+
"""
153+
Send an input event on the default report ID.
154+
155+
:param left: a tuple of absolute (x, y) value of the left joypad
156+
where ``None`` is "leave unchanged"
157+
:param right: a tuple of absolute (x, y) value of the right joypad
158+
where ``None`` is "leave unchanged"
159+
:param hat_switch: an absolute angular value of the hat switch
160+
where ``None`` is "leave unchanged"
161+
:param buttons: a dict of index/bool for the button states,
162+
where ``None`` is "leave unchanged"
163+
"""
164+
r = self.create_report(
165+
left=left, right=right, hat_switch=hat_switch, buttons=buttons
166+
)
167+
self.call_input_event(r)
168+
return [r]
169+
170+
171+
class JoystickGamepad(BaseGamepad):
172+
buttons_map = {
173+
1: "BTN_TRIGGER",
174+
2: "BTN_THUMB",
175+
3: "BTN_THUMB2",
176+
4: "BTN_TOP",
177+
5: "BTN_TOP2",
178+
6: "BTN_PINKIE",
179+
7: "BTN_BASE",
180+
8: "BTN_BASE2",
181+
9: "BTN_BASE3",
182+
10: "BTN_BASE4",
183+
11: "BTN_BASE5",
184+
12: "BTN_BASE6",
185+
13: "BTN_DEAD",
186+
}
187+
188+
axes_map = {
189+
"left_stick": {
190+
"x": AxisMapping("x"),
191+
"y": AxisMapping("y"),
192+
},
193+
"right_stick": {
194+
"x": AxisMapping("rudder"),
195+
"y": AxisMapping("throttle"),
196+
},
197+
}
198+
199+
def __init__(self, rdesc, application="Joystick", name=None, input_info=None):
200+
super().__init__(rdesc, application, name, input_info)
201+
202+
def create_report(
203+
self,
204+
*,
205+
left=(None, None),
206+
right=(None, None),
207+
hat_switch=None,
208+
buttons=None,
209+
reportID=None,
210+
application=None,
211+
):
212+
"""
213+
Return an input report for this device.
214+
215+
:param left: a tuple of absolute (x, y) value of the left joypad
216+
where ``None`` is "leave unchanged"
217+
:param right: a tuple of absolute (x, y) value of the right joypad
218+
where ``None`` is "leave unchanged"
219+
:param hat_switch: an absolute angular value of the hat switch
220+
where ``None`` is "leave unchanged"
221+
:param buttons: a dict of index/bool for the button states,
222+
where ``None`` is "leave unchanged"
223+
:param reportID: the numeric report ID for this report, if needed
224+
:param application: the application for this report, if needed
225+
"""
226+
if application is None:
227+
application = "Joystick"
228+
return super().create_report(
229+
left=left,
230+
right=right,
231+
hat_switch=hat_switch,
232+
buttons=buttons,
233+
reportID=reportID,
234+
application=application,
235+
)
236+
237+
def store_right_joystick(self, gamepad, data):
238+
gamepad.rudder, gamepad.throttle = data

tools/testing/selftests/hid/tests/test_gamepad.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
import libevdev
1111
import pytest
1212

13-
from hidtools.device.base_gamepad import AsusGamepad, SaitekGamepad
13+
from .base_gamepad import (
14+
AsusGamepad,
15+
SaitekGamepad,
16+
)
1417

1518
import logging
1619

0 commit comments

Comments
 (0)