base_gamepad.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. # SPDX-License-Identifier: GPL-2.0
  2. import libevdev
  3. from .base_device import BaseDevice
  4. from hidtools.util import BusType
  5. class InvalidHIDCommunication(Exception):
  6. pass
  7. class GamepadData(object):
  8. pass
  9. class AxisMapping(object):
  10. """Represents a mapping between a HID type
  11. and an evdev event"""
  12. def __init__(self, hid, evdev=None):
  13. self.hid = hid.lower()
  14. if evdev is None:
  15. evdev = f"ABS_{hid.upper()}"
  16. self.evdev = libevdev.evbit("EV_ABS", evdev)
  17. class BaseGamepad(BaseDevice):
  18. buttons_map = {
  19. 1: "BTN_SOUTH",
  20. 2: "BTN_EAST",
  21. 3: "BTN_C",
  22. 4: "BTN_NORTH",
  23. 5: "BTN_WEST",
  24. 6: "BTN_Z",
  25. 7: "BTN_TL",
  26. 8: "BTN_TR",
  27. 9: "BTN_TL2",
  28. 10: "BTN_TR2",
  29. 11: "BTN_SELECT",
  30. 12: "BTN_START",
  31. 13: "BTN_MODE",
  32. 14: "BTN_THUMBL",
  33. 15: "BTN_THUMBR",
  34. }
  35. axes_map = {
  36. "left_stick": {
  37. "x": AxisMapping("x"),
  38. "y": AxisMapping("y"),
  39. },
  40. "right_stick": {
  41. "x": AxisMapping("z"),
  42. "y": AxisMapping("Rz"),
  43. },
  44. }
  45. def __init__(self, rdesc, application="Game Pad", name=None, input_info=None):
  46. assert rdesc is not None
  47. super().__init__(name, application, input_info=input_info, rdesc=rdesc)
  48. self.buttons = (1, 2, 3)
  49. self._buttons = {}
  50. self.left = (127, 127)
  51. self.right = (127, 127)
  52. self.hat_switch = 15
  53. assert self.parsed_rdesc is not None
  54. self.fields = []
  55. for r in self.parsed_rdesc.input_reports.values():
  56. if r.application_name == self.application:
  57. self.fields.extend([f.usage_name for f in r])
  58. def store_axes(self, which, gamepad, data):
  59. amap = self.axes_map[which]
  60. x, y = data
  61. setattr(gamepad, amap["x"].hid, x)
  62. setattr(gamepad, amap["y"].hid, y)
  63. def create_report(
  64. self,
  65. *,
  66. left=(None, None),
  67. right=(None, None),
  68. hat_switch=None,
  69. buttons=None,
  70. reportID=None,
  71. application="Game Pad",
  72. ):
  73. """
  74. Return an input report for this device.
  75. :param left: a tuple of absolute (x, y) value of the left joypad
  76. where ``None`` is "leave unchanged"
  77. :param right: a tuple of absolute (x, y) value of the right joypad
  78. where ``None`` is "leave unchanged"
  79. :param hat_switch: an absolute angular value of the hat switch
  80. (expressed in 1/8 of circle, 0 being North, 2 East)
  81. where ``None`` is "leave unchanged"
  82. :param buttons: a dict of index/bool for the button states,
  83. where ``None`` is "leave unchanged"
  84. :param reportID: the numeric report ID for this report, if needed
  85. :param application: the application used to report the values
  86. """
  87. if buttons is not None:
  88. for i, b in buttons.items():
  89. if i not in self.buttons:
  90. raise InvalidHIDCommunication(
  91. f"button {i} is not part of this {self.application}"
  92. )
  93. if b is not None:
  94. self._buttons[i] = b
  95. def replace_none_in_tuple(item, default):
  96. if item is None:
  97. item = (None, None)
  98. if None in item:
  99. if item[0] is None:
  100. item = (default[0], item[1])
  101. if item[1] is None:
  102. item = (item[0], default[1])
  103. return item
  104. right = replace_none_in_tuple(right, self.right)
  105. self.right = right
  106. left = replace_none_in_tuple(left, self.left)
  107. self.left = left
  108. if hat_switch is None:
  109. hat_switch = self.hat_switch
  110. else:
  111. self.hat_switch = hat_switch
  112. reportID = reportID or self.default_reportID
  113. gamepad = GamepadData()
  114. for i, b in self._buttons.items():
  115. gamepad.__setattr__(f"b{i}", int(b) if b is not None else 0)
  116. self.store_axes("left_stick", gamepad, left)
  117. self.store_axes("right_stick", gamepad, right)
  118. gamepad.hatswitch = hat_switch # type: ignore ### gamepad is by default empty
  119. return super().create_report(
  120. gamepad, reportID=reportID, application=application
  121. )
  122. def event(
  123. self, *, left=(None, None), right=(None, None), hat_switch=None, buttons=None
  124. ):
  125. """
  126. Send an input event on the default report ID.
  127. :param left: a tuple of absolute (x, y) value of the left joypad
  128. where ``None`` is "leave unchanged"
  129. :param right: a tuple of absolute (x, y) value of the right joypad
  130. where ``None`` is "leave unchanged"
  131. :param hat_switch: an absolute angular value of the hat switch
  132. where ``None`` is "leave unchanged"
  133. :param buttons: a dict of index/bool for the button states,
  134. where ``None`` is "leave unchanged"
  135. """
  136. r = self.create_report(
  137. left=left, right=right, hat_switch=hat_switch, buttons=buttons
  138. )
  139. self.call_input_event(r)
  140. return [r]
  141. class JoystickGamepad(BaseGamepad):
  142. buttons_map = {
  143. 1: "BTN_TRIGGER",
  144. 2: "BTN_THUMB",
  145. 3: "BTN_THUMB2",
  146. 4: "BTN_TOP",
  147. 5: "BTN_TOP2",
  148. 6: "BTN_PINKIE",
  149. 7: "BTN_BASE",
  150. 8: "BTN_BASE2",
  151. 9: "BTN_BASE3",
  152. 10: "BTN_BASE4",
  153. 11: "BTN_BASE5",
  154. 12: "BTN_BASE6",
  155. 13: "BTN_DEAD",
  156. }
  157. axes_map = {
  158. "left_stick": {
  159. "x": AxisMapping("x"),
  160. "y": AxisMapping("y"),
  161. },
  162. "right_stick": {
  163. "x": AxisMapping("rudder"),
  164. "y": AxisMapping("throttle"),
  165. },
  166. }
  167. def __init__(self, rdesc, application="Joystick", name=None, input_info=None):
  168. super().__init__(rdesc, application, name, input_info)
  169. def create_report(
  170. self,
  171. *,
  172. left=(None, None),
  173. right=(None, None),
  174. hat_switch=None,
  175. buttons=None,
  176. reportID=None,
  177. application=None,
  178. ):
  179. """
  180. Return an input report for this device.
  181. :param left: a tuple of absolute (x, y) value of the left joypad
  182. where ``None`` is "leave unchanged"
  183. :param right: a tuple of absolute (x, y) value of the right joypad
  184. where ``None`` is "leave unchanged"
  185. :param hat_switch: an absolute angular value of the hat switch
  186. where ``None`` is "leave unchanged"
  187. :param buttons: a dict of index/bool for the button states,
  188. where ``None`` is "leave unchanged"
  189. :param reportID: the numeric report ID for this report, if needed
  190. :param application: the application for this report, if needed
  191. """
  192. if application is None:
  193. application = "Joystick"
  194. return super().create_report(
  195. left=left,
  196. right=right,
  197. hat_switch=hat_switch,
  198. buttons=buttons,
  199. reportID=reportID,
  200. application=application,
  201. )
  202. def store_right_joystick(self, gamepad, data):
  203. gamepad.rudder, gamepad.throttle = data