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 239