1 # SPDX-License-Identifier: GPL-2.0
4 from .base_device import BaseDevice
5 from hidtools.util import BusType
8 class InvalidHIDCommunication(Exception):
12 class GamepadData(object):
16 class AxisMapping(object):
17 """Represents a mapping between a HID type
20 def __init__(self, hid, evdev=None):
21 self.hid = hid.lower()
24 evdev = f"ABS_{hid.upper()}"
26 self.evdev = libevdev.evbit("EV_ABS", evdev)
29 class BaseGamepad(BaseDevice):
50 "x": AxisMapping("x"),
51 "y": AxisMapping("y"),
54 "x": AxisMapping("z"),
55 "y": AxisMapping("Rz"),
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)
64 self.left = (127, 127)
65 self.right = (127, 127)
67 assert self.parsed_rdesc is not None
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])
74 def store_axes(self, which, gamepad, data):
75 amap = self.axes_map[which]
77 setattr(gamepad, amap["x"].hid, x)
78 setattr(gamepad, amap["y"].hid, y)
88 application="Game Pad",
91 Return an input report for this device.
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
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}"
114 def replace_none_in_tuple(item, default):
120 item = (default[0], item[1])
122 item = (item[0], default[1])
126 right = replace_none_in_tuple(right, self.right)
128 left = replace_none_in_tuple(left, self.left)
131 if hat_switch is None:
132 hat_switch = self.hat_switch
134 self.hat_switch = hat_switch
136 reportID = reportID or self.default_reportID
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)
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
150 self, *, left=(None, None), right=(None, None), hat_switch=None, buttons=None
153 Send an input event on the default report ID.
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"
164 r = self.create_report(
165 left=left, right=right, hat_switch=hat_switch, buttons=buttons
167 self.call_input_event(r)
171 class JoystickGamepad(BaseGamepad):
190 "x": AxisMapping("x"),
191 "y": AxisMapping("y"),
194 "x": AxisMapping("rudder"),
195 "y": AxisMapping("throttle"),
199 def __init__(self, rdesc, application="Joystick", name=None, input_info=None):
200 super().__init__(rdesc, application, name, input_info)
213 Return an input report for this device.
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
226 if application is None:
227 application = "Joystick"
228 return super().create_report(
231 hat_switch=hat_switch,
234 application=application,
237 def store_right_joystick(self, gamepad, data):
238 gamepad.rudder, gamepad.throttle = data