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