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