Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/hid: import base_gamepad.py from hid-tools

We need to slightly change base_device.py for supporting HID-BPF,
so instead of monkey patching, let's just embed it in the kernel tree.

Link: https://lore.kernel.org/r/20240410-bpf_sources-v1-16-a8bf16033ef8@kernel.org
Reviewed-by: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>

+242 -1
+238
tools/testing/selftests/hid/tests/base_gamepad.py
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + import libevdev 3 + 4 + from .base_device import BaseDevice 5 + from hidtools.util import BusType 6 + 7 + 8 + class InvalidHIDCommunication(Exception): 9 + pass 10 + 11 + 12 + class GamepadData(object): 13 + pass 14 + 15 + 16 + class 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 + 29 + class 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 + 171 + class 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
+4 -1
tools/testing/selftests/hid/tests/test_gamepad.py
··· 10 10 import libevdev 11 11 import pytest 12 12 13 - from hidtools.device.base_gamepad import AsusGamepad, SaitekGamepad 13 + from .base_gamepad import ( 14 + AsusGamepad, 15 + SaitekGamepad, 16 + ) 14 17 15 18 import logging 16 19