Path: blob/master/tools/testing/selftests/hid/tests/base_gamepad.py
26308 views
# SPDX-License-Identifier: GPL-2.01import libevdev23from .base_device import BaseDevice4from hidtools.util import BusType567class InvalidHIDCommunication(Exception):8pass91011class GamepadData(object):12pass131415class AxisMapping(object):16"""Represents a mapping between a HID type17and an evdev event"""1819def __init__(self, hid, evdev=None):20self.hid = hid.lower()2122if evdev is None:23evdev = f"ABS_{hid.upper()}"2425self.evdev = libevdev.evbit("EV_ABS", evdev)262728class BaseGamepad(BaseDevice):29buttons_map = {301: "BTN_SOUTH",312: "BTN_EAST",323: "BTN_C",334: "BTN_NORTH",345: "BTN_WEST",356: "BTN_Z",367: "BTN_TL",378: "BTN_TR",389: "BTN_TL2",3910: "BTN_TR2",4011: "BTN_SELECT",4112: "BTN_START",4213: "BTN_MODE",4314: "BTN_THUMBL",4415: "BTN_THUMBR",45}4647axes_map = {48"left_stick": {49"x": AxisMapping("x"),50"y": AxisMapping("y"),51},52"right_stick": {53"x": AxisMapping("z"),54"y": AxisMapping("Rz"),55},56}5758def __init__(self, rdesc, application="Game Pad", name=None, input_info=None):59assert rdesc is not None60super().__init__(name, application, input_info=input_info, rdesc=rdesc)61self.buttons = (1, 2, 3)62self._buttons = {}63self.left = (127, 127)64self.right = (127, 127)65self.hat_switch = 1566assert self.parsed_rdesc is not None6768self.fields = []69for r in self.parsed_rdesc.input_reports.values():70if r.application_name == self.application:71self.fields.extend([f.usage_name for f in r])7273def store_axes(self, which, gamepad, data):74amap = self.axes_map[which]75x, y = data76setattr(gamepad, amap["x"].hid, x)77setattr(gamepad, amap["y"].hid, y)7879def create_report(80self,81*,82left=(None, None),83right=(None, None),84hat_switch=None,85buttons=None,86reportID=None,87application="Game Pad",88):89"""90Return an input report for this device.9192:param left: a tuple of absolute (x, y) value of the left joypad93where ``None`` is "leave unchanged"94:param right: a tuple of absolute (x, y) value of the right joypad95where ``None`` is "leave unchanged"96:param hat_switch: an absolute angular value of the hat switch97(expressed in 1/8 of circle, 0 being North, 2 East)98where ``None`` is "leave unchanged"99:param buttons: a dict of index/bool for the button states,100where ``None`` is "leave unchanged"101:param reportID: the numeric report ID for this report, if needed102:param application: the application used to report the values103"""104if buttons is not None:105for i, b in buttons.items():106if i not in self.buttons:107raise InvalidHIDCommunication(108f"button {i} is not part of this {self.application}"109)110if b is not None:111self._buttons[i] = b112113def replace_none_in_tuple(item, default):114if item is None:115item = (None, None)116117if None in item:118if item[0] is None:119item = (default[0], item[1])120if item[1] is None:121item = (item[0], default[1])122123return item124125right = replace_none_in_tuple(right, self.right)126self.right = right127left = replace_none_in_tuple(left, self.left)128self.left = left129130if hat_switch is None:131hat_switch = self.hat_switch132else:133self.hat_switch = hat_switch134135reportID = reportID or self.default_reportID136137gamepad = GamepadData()138for i, b in self._buttons.items():139gamepad.__setattr__(f"b{i}", int(b) if b is not None else 0)140141self.store_axes("left_stick", gamepad, left)142self.store_axes("right_stick", gamepad, right)143gamepad.hatswitch = hat_switch # type: ignore ### gamepad is by default empty144return super().create_report(145gamepad, reportID=reportID, application=application146)147148def event(149self, *, left=(None, None), right=(None, None), hat_switch=None, buttons=None150):151"""152Send an input event on the default report ID.153154:param left: a tuple of absolute (x, y) value of the left joypad155where ``None`` is "leave unchanged"156:param right: a tuple of absolute (x, y) value of the right joypad157where ``None`` is "leave unchanged"158:param hat_switch: an absolute angular value of the hat switch159where ``None`` is "leave unchanged"160:param buttons: a dict of index/bool for the button states,161where ``None`` is "leave unchanged"162"""163r = self.create_report(164left=left, right=right, hat_switch=hat_switch, buttons=buttons165)166self.call_input_event(r)167return [r]168169170class JoystickGamepad(BaseGamepad):171buttons_map = {1721: "BTN_TRIGGER",1732: "BTN_THUMB",1743: "BTN_THUMB2",1754: "BTN_TOP",1765: "BTN_TOP2",1776: "BTN_PINKIE",1787: "BTN_BASE",1798: "BTN_BASE2",1809: "BTN_BASE3",18110: "BTN_BASE4",18211: "BTN_BASE5",18312: "BTN_BASE6",18413: "BTN_DEAD",185}186187axes_map = {188"left_stick": {189"x": AxisMapping("x"),190"y": AxisMapping("y"),191},192"right_stick": {193"x": AxisMapping("rudder"),194"y": AxisMapping("throttle"),195},196}197198def __init__(self, rdesc, application="Joystick", name=None, input_info=None):199super().__init__(rdesc, application, name, input_info)200201def create_report(202self,203*,204left=(None, None),205right=(None, None),206hat_switch=None,207buttons=None,208reportID=None,209application=None,210):211"""212Return an input report for this device.213214:param left: a tuple of absolute (x, y) value of the left joypad215where ``None`` is "leave unchanged"216:param right: a tuple of absolute (x, y) value of the right joypad217where ``None`` is "leave unchanged"218:param hat_switch: an absolute angular value of the hat switch219where ``None`` is "leave unchanged"220:param buttons: a dict of index/bool for the button states,221where ``None`` is "leave unchanged"222:param reportID: the numeric report ID for this report, if needed223:param application: the application for this report, if needed224"""225if application is None:226application = "Joystick"227return super().create_report(228left=left,229right=right,230hat_switch=hat_switch,231buttons=buttons,232reportID=reportID,233application=application,234)235236def store_right_joystick(self, gamepad, data):237gamepad.rudder, gamepad.throttle = data238239240