239 lines
7.1 KiB
Python
239 lines
7.1 KiB
Python
# 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
|