| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- # 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
|