Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/hid/tests/base_gamepad.py
26308 views
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
239
240