File Index
Files and directories
Sign in
FILE

base_gamepad.py

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/hid/tests/base_gamepad.py PYTHON
  1# SPDX-License-Identifier: GPL-2.0
  2import libevdev
  3
  4from .base_device import BaseDevice
  5from hidtools.util import BusType
  6
  7
  8class InvalidHIDCommunication(Exception):
  9    pass
 10
 11
 12class GamepadData(object):
 13    pass
 14
 15
 16class AxisMapping(object):
 17    """Represents a mapping between a HID type
 18    and an evdev event"""
 19
 20    def __init__(self, hid, evdev=None):
 21        self.hid = hid.lower()
 22
 23        if evdev is None:
 24            evdev = f"ABS_{hid.upper()}"
 25
 26        self.evdev = libevdev.evbit("EV_ABS", evdev)
 27
 28
 29class BaseGamepad(BaseDevice):
 30    buttons_map = {
 31        1: "BTN_SOUTH",
 32        2: "BTN_EAST",
 33        3: "BTN_C",
 34        4: "BTN_NORTH",
 35        5: "BTN_WEST",
 36        6: "BTN_Z",
 37        7: "BTN_TL",
 38        8: "BTN_TR",
 39        9: "BTN_TL2",
 40        10: "BTN_TR2",
 41        11: "BTN_SELECT",
 42        12: "BTN_START",
 43        13: "BTN_MODE",
 44        14: "BTN_THUMBL",
 45        15: "BTN_THUMBR",
 46    }
 47
 48    axes_map = {
 49        "left_stick": {
 50            "x": AxisMapping("x"),
 51            "y": AxisMapping("y"),
 52        },
 53        "right_stick": {
 54            "x": AxisMapping("z"),
 55            "y": AxisMapping("Rz"),
 56        },
 57    }
 58
 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)
 63        self._buttons = {}
 64        self.left = (127, 127)
 65        self.right = (127, 127)
 66        self.hat_switch = 15
 67        assert self.parsed_rdesc is not None
 68
 69        self.fields = []
 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])
 73
 74    def store_axes(self, which, gamepad, data):
 75        amap = self.axes_map[which]
 76        x, y = data
 77        setattr(gamepad, amap["x"].hid, x)
 78        setattr(gamepad, amap["y"].hid, y)
 79
 80    def create_report(
 81        self,
 82        *,
 83        left=(None, None),
 84        right=(None, None),
 85        hat_switch=None,
 86        buttons=None,
 87        reportID=None,
 88        application="Game Pad",
 89    ):
 90        """
 91        Return an input report for this device.
 92
 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
104        """
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}"
110                    )
111                if b is not None:
112                    self._buttons[i] = b
113
114        def replace_none_in_tuple(item, default):
115            if item is None:
116                item = (None, None)
117
118            if None in item:
119                if item[0] is None:
120                    item = (default[0], item[1])
121                if item[1] is None:
122                    item = (item[0], default[1])
123
124            return item
125
126        right = replace_none_in_tuple(right, self.right)
127        self.right = right
128        left = replace_none_in_tuple(left, self.left)
129        self.left = left
130
131        if hat_switch is None:
132            hat_switch = self.hat_switch
133        else:
134            self.hat_switch = hat_switch
135
136        reportID = reportID or self.default_reportID
137
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)
141
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
147        )
148
149    def event(
150        self, *, left=(None, None), right=(None, None), hat_switch=None, buttons=None
151    ):
152        """
153        Send an input event on the default report ID.
154
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"
163        """
164        r = self.create_report(
165            left=left, right=right, hat_switch=hat_switch, buttons=buttons
166        )
167        self.call_input_event(r)
168        return [r]
169
170
171class JoystickGamepad(BaseGamepad):
172    buttons_map = {
173        1: "BTN_TRIGGER",
174        2: "BTN_THUMB",
175        3: "BTN_THUMB2",
176        4: "BTN_TOP",
177        5: "BTN_TOP2",
178        6: "BTN_PINKIE",
179        7: "BTN_BASE",
180        8: "BTN_BASE2",
181        9: "BTN_BASE3",
182        10: "BTN_BASE4",
183        11: "BTN_BASE5",
184        12: "BTN_BASE6",
185        13: "BTN_DEAD",
186    }
187
188    axes_map = {
189        "left_stick": {
190            "x": AxisMapping("x"),
191            "y": AxisMapping("y"),
192        },
193        "right_stick": {
194            "x": AxisMapping("rudder"),
195            "y": AxisMapping("throttle"),
196        },
197    }
198
199    def __init__(self, rdesc, application="Joystick", name=None, input_info=None):
200        super().__init__(rdesc, application, name, input_info)
201
202    def create_report(
203        self,
204        *,
205        left=(None, None),
206        right=(None, None),
207        hat_switch=None,
208        buttons=None,
209        reportID=None,
210        application=None,
211    ):
212        """
213        Return an input report for this device.
214
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
225        """
226        if application is None:
227            application = "Joystick"
228        return super().create_report(
229            left=left,
230            right=right,
231            hat_switch=hat_switch,
232            buttons=buttons,
233            reportID=reportID,
234            application=application,
235        )
236
237    def store_right_joystick(self, gamepad, data):
238        gamepad.rudder, gamepad.throttle = data
~/ Read-only file preview.