pyglet 2.1.13__py3-none-any.whl → 3.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. pyglet/__init__.py +67 -61
  2. pyglet/__init__.pyi +15 -8
  3. pyglet/app/__init__.py +22 -13
  4. pyglet/app/async_app.py +212 -0
  5. pyglet/app/base.py +2 -1
  6. pyglet/app/{xlib.py → linux.py} +3 -3
  7. pyglet/config/__init__.py +101 -0
  8. pyglet/config/gl/__init__.py +30 -0
  9. pyglet/config/gl/egl.py +120 -0
  10. pyglet/config/gl/macos.py +262 -0
  11. pyglet/config/gl/windows.py +267 -0
  12. pyglet/config/gl/x11.py +142 -0
  13. pyglet/customtypes.py +43 -2
  14. pyglet/display/__init__.py +8 -6
  15. pyglet/display/base.py +3 -63
  16. pyglet/display/cocoa.py +12 -17
  17. pyglet/display/emscripten.py +39 -0
  18. pyglet/display/headless.py +23 -30
  19. pyglet/display/wayland.py +157 -0
  20. pyglet/display/win32.py +5 -21
  21. pyglet/display/xlib.py +19 -27
  22. pyglet/display/xlib_vidmoderestore.py +2 -2
  23. pyglet/enums.py +183 -0
  24. pyglet/event.py +0 -1
  25. pyglet/experimental/geoshader_sprite.py +15 -13
  26. pyglet/experimental/hidraw.py +6 -15
  27. pyglet/experimental/multitexture_sprite.py +31 -19
  28. pyglet/experimental/particles.py +13 -35
  29. pyglet/font/__init__.py +251 -85
  30. pyglet/font/base.py +116 -61
  31. pyglet/font/dwrite/__init__.py +349 -204
  32. pyglet/font/dwrite/dwrite_lib.py +27 -5
  33. pyglet/font/fontconfig.py +14 -6
  34. pyglet/font/freetype.py +138 -87
  35. pyglet/font/freetype_lib.py +19 -0
  36. pyglet/font/group.py +179 -0
  37. pyglet/font/harfbuzz/__init__.py +3 -3
  38. pyglet/font/pyodide_js.py +310 -0
  39. pyglet/font/quartz.py +319 -126
  40. pyglet/font/ttf.py +45 -3
  41. pyglet/font/user.py +14 -19
  42. pyglet/font/win32.py +45 -21
  43. pyglet/graphics/__init__.py +8 -787
  44. pyglet/graphics/allocation.py +115 -1
  45. pyglet/graphics/api/__init__.py +77 -0
  46. pyglet/graphics/api/base.py +299 -0
  47. pyglet/graphics/api/gl/__init__.py +58 -0
  48. pyglet/graphics/api/gl/base.py +24 -0
  49. pyglet/graphics/{vertexbuffer.py → api/gl/buffer.py} +104 -159
  50. pyglet/graphics/api/gl/cocoa/context.py +76 -0
  51. pyglet/graphics/api/gl/context.py +391 -0
  52. pyglet/graphics/api/gl/default_shaders.py +0 -0
  53. pyglet/graphics/api/gl/draw.py +627 -0
  54. pyglet/graphics/api/gl/egl/__init__.py +0 -0
  55. pyglet/graphics/api/gl/egl/context.py +92 -0
  56. pyglet/graphics/api/gl/enums.py +76 -0
  57. pyglet/graphics/api/gl/framebuffer.py +315 -0
  58. pyglet/graphics/api/gl/gl.py +5463 -0
  59. pyglet/graphics/api/gl/gl_info.py +188 -0
  60. pyglet/graphics/api/gl/global_opengl.py +226 -0
  61. pyglet/{gl → graphics/api/gl}/lib.py +34 -18
  62. pyglet/graphics/api/gl/shader.py +1476 -0
  63. pyglet/graphics/api/gl/shapes.py +55 -0
  64. pyglet/graphics/api/gl/sprite.py +102 -0
  65. pyglet/graphics/api/gl/state.py +219 -0
  66. pyglet/graphics/api/gl/text.py +190 -0
  67. pyglet/graphics/api/gl/texture.py +1526 -0
  68. pyglet/graphics/{vertexarray.py → api/gl/vertexarray.py} +11 -13
  69. pyglet/graphics/api/gl/vertexdomain.py +751 -0
  70. pyglet/graphics/api/gl/win32/__init__.py +0 -0
  71. pyglet/graphics/api/gl/win32/context.py +108 -0
  72. pyglet/graphics/api/gl/win32/wgl_info.py +24 -0
  73. pyglet/graphics/api/gl/xlib/__init__.py +0 -0
  74. pyglet/graphics/api/gl/xlib/context.py +174 -0
  75. pyglet/{gl → graphics/api/gl/xlib}/glx_info.py +26 -31
  76. pyglet/graphics/api/gl1/__init__.py +0 -0
  77. pyglet/{gl → graphics/api/gl1}/gl_compat.py +3 -2
  78. pyglet/graphics/api/gl2/__init__.py +0 -0
  79. pyglet/graphics/api/gl2/buffer.py +320 -0
  80. pyglet/graphics/api/gl2/draw.py +600 -0
  81. pyglet/graphics/api/gl2/global_opengl.py +122 -0
  82. pyglet/graphics/api/gl2/shader.py +200 -0
  83. pyglet/graphics/api/gl2/shapes.py +51 -0
  84. pyglet/graphics/api/gl2/sprite.py +79 -0
  85. pyglet/graphics/api/gl2/text.py +175 -0
  86. pyglet/graphics/api/gl2/vertexdomain.py +364 -0
  87. pyglet/graphics/api/webgl/__init__.py +233 -0
  88. pyglet/graphics/api/webgl/buffer.py +302 -0
  89. pyglet/graphics/api/webgl/context.py +234 -0
  90. pyglet/graphics/api/webgl/draw.py +590 -0
  91. pyglet/graphics/api/webgl/enums.py +76 -0
  92. pyglet/graphics/api/webgl/framebuffer.py +360 -0
  93. pyglet/graphics/api/webgl/gl.py +1537 -0
  94. pyglet/graphics/api/webgl/gl_info.py +130 -0
  95. pyglet/graphics/api/webgl/shader.py +1346 -0
  96. pyglet/graphics/api/webgl/shapes.py +92 -0
  97. pyglet/graphics/api/webgl/sprite.py +102 -0
  98. pyglet/graphics/api/webgl/state.py +227 -0
  99. pyglet/graphics/api/webgl/text.py +187 -0
  100. pyglet/graphics/api/webgl/texture.py +1227 -0
  101. pyglet/graphics/api/webgl/vertexarray.py +54 -0
  102. pyglet/graphics/api/webgl/vertexdomain.py +616 -0
  103. pyglet/graphics/api/webgl/webgl_js.pyi +307 -0
  104. pyglet/{image → graphics}/atlas.py +33 -32
  105. pyglet/graphics/base.py +10 -0
  106. pyglet/graphics/buffer.py +245 -0
  107. pyglet/graphics/draw.py +578 -0
  108. pyglet/graphics/framebuffer.py +26 -0
  109. pyglet/graphics/instance.py +178 -69
  110. pyglet/graphics/shader.py +267 -1553
  111. pyglet/graphics/state.py +83 -0
  112. pyglet/graphics/texture.py +703 -0
  113. pyglet/graphics/vertexdomain.py +695 -538
  114. pyglet/gui/ninepatch.py +10 -10
  115. pyglet/gui/widgets.py +120 -10
  116. pyglet/image/__init__.py +20 -1973
  117. pyglet/image/animation.py +12 -12
  118. pyglet/image/base.py +730 -0
  119. pyglet/image/codecs/__init__.py +9 -0
  120. pyglet/image/codecs/bmp.py +53 -30
  121. pyglet/image/codecs/dds.py +53 -31
  122. pyglet/image/codecs/gdiplus.py +38 -14
  123. pyglet/image/codecs/gdkpixbuf2.py +0 -2
  124. pyglet/image/codecs/js_image.py +99 -0
  125. pyglet/image/codecs/ktx2.py +161 -0
  126. pyglet/image/codecs/pil.py +1 -1
  127. pyglet/image/codecs/png.py +1 -1
  128. pyglet/image/codecs/wic.py +11 -2
  129. pyglet/info.py +26 -24
  130. pyglet/input/__init__.py +8 -0
  131. pyglet/input/base.py +163 -105
  132. pyglet/input/controller.py +13 -19
  133. pyglet/input/controller_db.py +39 -24
  134. pyglet/input/emscripten/__init__.py +18 -0
  135. pyglet/input/emscripten/gamepad_js.py +397 -0
  136. pyglet/input/linux/__init__.py +11 -5
  137. pyglet/input/linux/evdev.py +10 -11
  138. pyglet/input/linux/x11_xinput.py +2 -2
  139. pyglet/input/linux/x11_xinput_tablet.py +1 -1
  140. pyglet/input/macos/__init__.py +7 -2
  141. pyglet/input/macos/darwin_gc.py +559 -0
  142. pyglet/input/win32/__init__.py +1 -1
  143. pyglet/input/win32/directinput.py +34 -29
  144. pyglet/input/win32/xinput.py +11 -61
  145. pyglet/lib.py +3 -3
  146. pyglet/libs/__init__.py +1 -1
  147. pyglet/{gl → libs/darwin}/agl.py +1 -1
  148. pyglet/libs/darwin/cocoapy/__init__.py +2 -2
  149. pyglet/libs/darwin/cocoapy/cocoahelpers.py +181 -0
  150. pyglet/libs/darwin/cocoapy/cocoalibs.py +31 -0
  151. pyglet/libs/darwin/cocoapy/cocoatypes.py +27 -0
  152. pyglet/libs/darwin/cocoapy/runtime.py +81 -45
  153. pyglet/libs/darwin/coreaudio.py +4 -4
  154. pyglet/{gl → libs/darwin}/lib_agl.py +9 -8
  155. pyglet/libs/darwin/quartzkey.py +1 -3
  156. pyglet/libs/egl/__init__.py +2 -0
  157. pyglet/libs/egl/egl_lib.py +576 -0
  158. pyglet/libs/egl/eglext.py +51 -5
  159. pyglet/libs/linux/__init__.py +0 -0
  160. pyglet/libs/linux/egl/__init__.py +0 -0
  161. pyglet/libs/linux/egl/eglext.py +22 -0
  162. pyglet/libs/linux/glx/__init__.py +0 -0
  163. pyglet/{gl → libs/linux/glx}/glx.py +13 -14
  164. pyglet/{gl → libs/linux/glx}/glxext_arb.py +408 -192
  165. pyglet/{gl → libs/linux/glx}/glxext_mesa.py +1 -1
  166. pyglet/{gl → libs/linux/glx}/glxext_nv.py +345 -164
  167. pyglet/{gl → libs/linux/glx}/lib_glx.py +3 -2
  168. pyglet/libs/linux/wayland/__init__.py +0 -0
  169. pyglet/libs/linux/wayland/client.py +1068 -0
  170. pyglet/libs/linux/wayland/lib_wayland.py +207 -0
  171. pyglet/libs/linux/wayland/wayland_egl.py +38 -0
  172. pyglet/libs/{wayland → linux/wayland}/xkbcommon.py +26 -0
  173. pyglet/libs/{x11 → linux/x11}/xf86vmode.py +4 -4
  174. pyglet/libs/{x11 → linux/x11}/xinerama.py +2 -2
  175. pyglet/libs/{x11 → linux/x11}/xinput.py +10 -10
  176. pyglet/libs/linux/x11/xrandr.py +0 -0
  177. pyglet/libs/{x11 → linux/x11}/xrender.py +1 -1
  178. pyglet/libs/shared/__init__.py +0 -0
  179. pyglet/libs/shared/spirv/__init__.py +0 -0
  180. pyglet/libs/shared/spirv/lib_shaderc.py +85 -0
  181. pyglet/libs/shared/spirv/lib_spirv_cross.py +126 -0
  182. pyglet/libs/win32/__init__.py +28 -8
  183. pyglet/libs/win32/constants.py +59 -48
  184. pyglet/libs/win32/context_managers.py +20 -3
  185. pyglet/libs/win32/dinput.py +105 -88
  186. pyglet/{gl → libs/win32}/lib_wgl.py +52 -26
  187. pyglet/libs/win32/types.py +58 -23
  188. pyglet/{gl → libs/win32}/wgl.py +32 -25
  189. pyglet/{gl → libs/win32}/wglext_arb.py +364 -2
  190. pyglet/media/__init__.py +9 -10
  191. pyglet/media/codecs/__init__.py +12 -1
  192. pyglet/media/codecs/base.py +99 -96
  193. pyglet/media/codecs/ffmpeg.py +2 -2
  194. pyglet/media/codecs/ffmpeg_lib/libavformat.py +3 -8
  195. pyglet/media/codecs/webaudio_pyodide.py +111 -0
  196. pyglet/media/drivers/__init__.py +9 -4
  197. pyglet/media/drivers/base.py +4 -4
  198. pyglet/media/drivers/openal/__init__.py +1 -1
  199. pyglet/media/drivers/openal/adaptation.py +3 -3
  200. pyglet/media/drivers/pulse/__init__.py +1 -1
  201. pyglet/media/drivers/pulse/adaptation.py +3 -3
  202. pyglet/media/drivers/pyodide_js/__init__.py +8 -0
  203. pyglet/media/drivers/pyodide_js/adaptation.py +288 -0
  204. pyglet/media/drivers/xaudio2/adaptation.py +3 -3
  205. pyglet/media/player.py +276 -193
  206. pyglet/media/player_worker_thread.py +1 -1
  207. pyglet/model/__init__.py +39 -29
  208. pyglet/model/codecs/base.py +4 -4
  209. pyglet/model/codecs/gltf.py +3 -3
  210. pyglet/model/codecs/obj.py +71 -43
  211. pyglet/resource.py +129 -78
  212. pyglet/shapes.py +154 -194
  213. pyglet/sprite.py +47 -164
  214. pyglet/text/__init__.py +44 -54
  215. pyglet/text/caret.py +12 -7
  216. pyglet/text/document.py +19 -17
  217. pyglet/text/formats/html.py +2 -2
  218. pyglet/text/formats/structured.py +10 -40
  219. pyglet/text/layout/__init__.py +20 -13
  220. pyglet/text/layout/base.py +176 -287
  221. pyglet/text/layout/incremental.py +9 -10
  222. pyglet/text/layout/scrolling.py +7 -95
  223. pyglet/window/__init__.py +183 -172
  224. pyglet/window/cocoa/__init__.py +62 -51
  225. pyglet/window/cocoa/pyglet_delegate.py +2 -25
  226. pyglet/window/cocoa/pyglet_view.py +9 -8
  227. pyglet/window/dialog/__init__.py +184 -0
  228. pyglet/window/dialog/base.py +99 -0
  229. pyglet/window/dialog/darwin.py +121 -0
  230. pyglet/window/dialog/linux.py +72 -0
  231. pyglet/window/dialog/windows.py +194 -0
  232. pyglet/window/emscripten/__init__.py +779 -0
  233. pyglet/window/headless/__init__.py +44 -28
  234. pyglet/window/key.py +2 -0
  235. pyglet/window/mouse.py +2 -2
  236. pyglet/window/wayland/__init__.py +377 -0
  237. pyglet/window/win32/__init__.py +101 -46
  238. pyglet/window/xlib/__init__.py +104 -66
  239. {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/METADATA +2 -3
  240. pyglet-3.0.dev1.dist-info/RECORD +322 -0
  241. {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/WHEEL +1 -1
  242. pyglet/gl/__init__.py +0 -208
  243. pyglet/gl/base.py +0 -499
  244. pyglet/gl/cocoa.py +0 -309
  245. pyglet/gl/gl.py +0 -4625
  246. pyglet/gl/gl.pyi +0 -2320
  247. pyglet/gl/gl_compat.pyi +0 -3097
  248. pyglet/gl/gl_info.py +0 -190
  249. pyglet/gl/headless.py +0 -166
  250. pyglet/gl/wgl_info.py +0 -36
  251. pyglet/gl/wglext_nv.py +0 -1096
  252. pyglet/gl/win32.py +0 -268
  253. pyglet/gl/xlib.py +0 -295
  254. pyglet/image/buffer.py +0 -274
  255. pyglet/image/codecs/s3tc.py +0 -354
  256. pyglet/libs/x11/xrandr.py +0 -166
  257. pyglet-2.1.13.dist-info/RECORD +0 -234
  258. /pyglet/{libs/wayland → graphics/api/gl/cocoa}/__init__.py +0 -0
  259. /pyglet/libs/{egl → linux/egl}/egl.py +0 -0
  260. /pyglet/libs/{egl → linux/egl}/lib.py +0 -0
  261. /pyglet/libs/{ioctl.py → linux/ioctl.py} +0 -0
  262. /pyglet/libs/{wayland → linux/wayland}/gbm.py +0 -0
  263. /pyglet/libs/{x11 → linux/x11}/__init__.py +0 -0
  264. /pyglet/libs/{x11 → linux/x11}/cursorfont.py +0 -0
  265. /pyglet/libs/{x11 → linux/x11}/xlib.py +0 -0
  266. /pyglet/libs/{x11 → linux/x11}/xsync.py +0 -0
  267. {pyglet-2.1.13.dist-info/licenses → pyglet-3.0.dev1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,397 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import warnings
5
+ from typing import Literal
6
+
7
+ import js
8
+ from js import console, navigator
9
+ from pyodide.ffi import create_proxy
10
+
11
+ import pyglet
12
+ from pyglet.input import Button, Device
13
+ from pyglet.input.base import AbsoluteAxis, Button, Controller, ControllerManager, Control
14
+ from pyglet.math import Vec2
15
+
16
+ class TriggerButton(Control):
17
+ """A combination of a Button and an Axis.
18
+
19
+ A trigger can behave like an axis between 0 and 1, but many games also want to treat it like a button press for
20
+ fully down or fully released.
21
+
22
+ When it reaches the maximum value, it will dispatch ``on_press``, and ``on_release`` on the minimum value.
23
+ """
24
+ def __init__(self, name: str, minimum: float, maximum: float, raw_name: None | str = None, inverted: bool = False) -> None:
25
+ super().__init__(name, raw_name, inverted)
26
+ self.min = minimum
27
+ self.max = maximum
28
+
29
+ @property
30
+ def value(self) -> float:
31
+ return self._value
32
+
33
+ @value.setter
34
+ def value(self, newvalue: float) -> None:
35
+ if newvalue == self._value:
36
+ return
37
+ self._value = newvalue
38
+ self.dispatch_event('on_change', bool(newvalue))
39
+ if newvalue >= self.max:
40
+ self.dispatch_event('on_press')
41
+ elif newvalue <= self.min:
42
+ self.dispatch_event('on_release')
43
+
44
+ _standard_button_map = {
45
+ 0: "a",
46
+ 1: "b",
47
+ 2: "x",
48
+ 3: "y",
49
+ 4: "leftshoulder",
50
+ 5: "rightshoulder",
51
+ 6: "lefttrigger",
52
+ 7: "righttrigger",
53
+ 8: "back",
54
+ 9: "start",
55
+ 10: "leftstick",
56
+ 11: "rightstick",
57
+ 12: "dpup",
58
+ 13: "dpdown",
59
+ 14: "dpleft",
60
+ 15: "dpright",
61
+ 16: "guide",
62
+ 17: "touchpad", # touchpad button click for ps4.
63
+ }
64
+
65
+ _standard_axis_map = {
66
+ 0: "leftx",
67
+ 1: "lefty",
68
+ 2: "rightx",
69
+ 3: "righty",
70
+ }
71
+
72
+ # It's something, but not sure how to type it.
73
+ JSPromise = None
74
+
75
+ class _GamepadActuator:
76
+ """Typing for Javascript Gamepad Actuator object."""
77
+ effects: list[str]
78
+
79
+ def playEffect(self,
80
+ effect_type: Literal["dual-rumble", "trigger-rumble"],
81
+ duration: int = 0,
82
+ startDelay: int = 0,
83
+ strongMagnitude: float = 0,
84
+ weakMagnitude: float = 0,
85
+ leftTrigger: float = 0,
86
+ rightTrigger: float = 0) -> JSPromise:
87
+ """Play a specific vibration effect."""
88
+
89
+ def pulse(self, value: float, duration: int) -> JSPromise:
90
+ """Pulse at a certain intensity for a specified duration."""
91
+
92
+ def reset(self) -> None:
93
+ """Stop all effects."""
94
+
95
+
96
+ class _GamepadButton:
97
+ """Typing for Javascript GamepadButton object."""
98
+ pressed: bool
99
+ touched: bool # Some browsers support this, otherwise it may always be False
100
+ value: float
101
+
102
+ class _Gamepad:
103
+ """Typing for Javascript Gamepad object."""
104
+ id: str
105
+ index: int
106
+ connected: bool
107
+ timestamp: float
108
+ mapping: str # Typically "standard" or ""
109
+ axes: list[float]
110
+ buttons: list[_GamepadButton]
111
+ vibrationActuator: _GamepadActuator | None
112
+
113
+
114
+ class JavascriptGamepad(Device):
115
+ """Small wrapper over the Gamepad device.
116
+
117
+ Do not rely on the gamepad object, as it gets replaced every frame in an update.
118
+ """
119
+ controls: dict[str, Button | AbsoluteAxis]
120
+
121
+ def __init__(self, gamepad: _Gamepad) -> None:
122
+ super().__init__(None, f"{gamepad.id}")
123
+ self.index = gamepad.index
124
+ self.connected = gamepad.connected
125
+ self.gamepad = gamepad # Will get replaced
126
+
127
+ self.vendor_id = 0
128
+ self.product_id = 0
129
+ self.device_name = gamepad.id
130
+ self.mapping_name = "standard"
131
+
132
+ # Rumble support. Only on Chrome and Edge.
133
+ if hasattr(gamepad, "vibrationActuator") and gamepad.vibrationActuator is not None:
134
+ if "dual-rumble" in gamepad.vibrationActuator.effects: # weak/strong motors.
135
+ self.vibration = True
136
+ console.log(f"Rumble supported for gamepad: {gamepad.id}")
137
+ else:
138
+ self.vibration = False
139
+ console.log("Trigger rumble support found, but not implemented.")
140
+ else:
141
+ self.vibration = False
142
+ console.log("Rumble not supported.")
143
+
144
+ self.vibration = False
145
+
146
+ # Some controllers report the Vendor and Product ID:
147
+ match = re.search(r'^(.*?)(?: \((.*?)(?: Vendor: ([0-9a-fA-F]{4}) Product: ([0-9a-fA-F]{4}))?\))?$', gamepad.id)
148
+ if match:
149
+ self.device_name, mapping_name, vendor_id, product_id = match.groups()
150
+
151
+ # If a vendor ID and product ID are found.
152
+ if vendor_id and product_id:
153
+ # Convert IDs to little-endian
154
+ vendor_le = vendor_id[2:4] + vendor_id[0:2]
155
+ product_le = product_id[2:4] + product_id[0:2]
156
+ self.guid = f"03000000{vendor_le}0000{product_le}000000000000"
157
+ else:
158
+ warnings.warn("Vendor/Product IDs not found in GamePad ID.")
159
+ self.guid = "STANDARD GAMEPAD"
160
+
161
+ self.mapping_name = mapping_name if mapping_name else "standard"
162
+ else:
163
+ warnings.warn(f"Gamepad information could not be parsed from: {gamepad.id}")
164
+
165
+ # See layout here: https://w3c.github.io/gamepad/#remapping
166
+ self.controls = {
167
+ 'a': Button('a'), # the “south” face button # 0
168
+ 'b': Button('b'), # the “east” face button # 1
169
+ 'x': Button('x'), # the “west” face button # 2
170
+ 'y': Button('y'), # the “north” face button # 3
171
+ 'back': Button('back'), # 8
172
+ 'start': Button('start'), # 9
173
+ 'guide': Button('guide'), # 16
174
+ 'leftshoulder': Button('leftshoulder'), # 4
175
+ 'rightshoulder': Button('rightshoulder'), # 5
176
+ 'lefttrigger': TriggerButton('lefttrigger', 0.0, 1.0), # 6
177
+ 'righttrigger': TriggerButton('righttrigger', 0.0, 1.0), # 7
178
+ 'leftstick': Button('leftstick'), # 10
179
+ 'rightstick': Button('rightstick'), # 11
180
+ 'dpup': Button('dpup'), # 12
181
+ 'dpdown': Button('dpdown'), # 13
182
+ 'dpleft': Button('dpleft'), # 14
183
+ 'dpright': Button('dpright'), # 15
184
+
185
+ 'leftx': AbsoluteAxis('leftx', -1, 1), # axis horizontal # 0
186
+ 'lefty': AbsoluteAxis('lefty', -1, 1), # axis vertical # 1
187
+ 'rightx': AbsoluteAxis('rightx', -1, 1), # axis horizontal 2
188
+ 'righty': AbsoluteAxis('righty', -1, 1), # axis vertical 3
189
+
190
+ 'touchpad': Button('touchpad'), # 17 on PS4 controller.
191
+ }
192
+
193
+ def get_controls(self) -> list[Control]:
194
+ return list(self.controls.values())
195
+
196
+ def get_guid(self) -> str:
197
+ return self.guid
198
+
199
+
200
+ class GamepadController(Controller):
201
+ """Javascript Gamepad Controller object that handles buttons and controls."""
202
+ device: JavascriptGamepad
203
+ def __init__(self, device: JavascriptGamepad, mapping: dict) -> None:
204
+ super().__init__(device, mapping)
205
+ self._last_updated = 0.0
206
+
207
+ @property
208
+ def gamepad_device(self) -> _Gamepad:
209
+ return self.device.gamepad
210
+
211
+ def _initialize_controls(self) -> None:
212
+ for button_name in _standard_button_map.values():
213
+ control = self.device.controls[button_name]
214
+ self._button_controls.append(control)
215
+ self._add_button(control, button_name)
216
+
217
+ for axis_name in _standard_axis_map.values():
218
+ control = self.device.controls[axis_name]
219
+ self._axis_controls.append(control)
220
+ self._add_axis(control, axis_name)
221
+
222
+ def update(self, gamepad: _Gamepad) -> None:
223
+ # Disconnect event should catch any disconnects.
224
+ # Save some CPU if event fails by avoiding trying to update the states.
225
+ if not gamepad.connected:
226
+ self.device.connected = False
227
+ return
228
+
229
+ # Avoid going through all buttons unless the timestamp has changed on the gamepad data.
230
+ if gamepad.timestamp > self._last_updated:
231
+ self.device.connected = True
232
+
233
+ for button_id, button in enumerate(gamepad.buttons):
234
+ name = _standard_button_map[button_id]
235
+ self.device.controls[name].value = button.pressed
236
+
237
+ for axes_id, value in enumerate(gamepad.axes):
238
+ name = _standard_axis_map[axes_id]
239
+ self.device.controls[name].value = value
240
+
241
+ self._last_updated = gamepad.timestamp
242
+
243
+ def _add_axis(self, control: Control, name: str) -> None:
244
+ if name == "lefttrigger":
245
+ @control.event
246
+ def on_change(value):
247
+ setattr(self, name, value)
248
+ self.dispatch_event('on_lefttrigger_motion', self, value)
249
+
250
+ @control.event
251
+ def on_press():
252
+ self.dispatch_event('on_button_press', self, name)
253
+
254
+ @control.event
255
+ def on_release():
256
+ self.dispatch_event('on_button_release', self, name)
257
+
258
+ if name == "righttrigger":
259
+ @control.event
260
+ def on_change(value):
261
+ setattr(self, name, value)
262
+ self.dispatch_event('on_righttrigger_motion', self, value)
263
+
264
+ @control.event
265
+ def on_press():
266
+ self.dispatch_event('on_button_press', self, name)
267
+
268
+ @control.event
269
+ def on_release():
270
+ self.dispatch_event('on_button_release', self, name)
271
+
272
+ elif name in ("leftx", "lefty"):
273
+ @control.event
274
+ def on_change(value):
275
+ normalized_value = value
276
+ setattr(self, name, normalized_value)
277
+ self.dispatch_event('on_leftstick_motion', self, Vec2(self.leftx, -self.lefty))
278
+
279
+ elif name in ("rightx", "righty"):
280
+ @control.event
281
+ def on_change(value):
282
+ normalized_value = value
283
+ setattr(self, name, normalized_value)
284
+ self.dispatch_event('on_rightstick_motion', self, Vec2(self.rightx, -self.righty))
285
+
286
+ def _add_button(self, control: Control, name: str) -> None:
287
+
288
+ if name in ("dpleft", "dpright", "dpup", "dpdown"):
289
+ @control.event
290
+ def on_change(value):
291
+ target, bias = {
292
+ 'dpleft': ('dpadx', -1.0), 'dpright': ('dpadx', 1.0),
293
+ 'dpdown': ('dpady', -1.0), 'dpup': ('dpady', 1.0)
294
+ }[name]
295
+ setattr(self, target, bias * value)
296
+ self.dispatch_event('on_dpad_motion', self, Vec2(self.dpadx, self.dpady))
297
+ else:
298
+ @control.event
299
+ def on_change(value):
300
+ setattr(self, name, value)
301
+
302
+ @control.event
303
+ def on_press():
304
+ self.dispatch_event('on_button_press', self, name)
305
+
306
+ @control.event
307
+ def on_release():
308
+ self.dispatch_event('on_button_release', self, name)
309
+
310
+ def rumble_play_weak(self, strength: float=1.0, duration: float=0.5) -> None:
311
+ if self.device.vibration is True:
312
+ strength_clamp = int(max(min(1.0, strength), 0))
313
+ self.gamepad_device.vibrationActuator.playEffect(
314
+ "dual-rumble",
315
+ startDelay=0,
316
+ duration=int(duration*1000),
317
+ weakMagnitude=strength_clamp,
318
+ )
319
+
320
+ def rumble_play_strong(self, strength: float=1.0, duration: float=0.5) -> None:
321
+ if self.device.vibration is True:
322
+ strength_clamp = int(max(min(1.0, strength), 0))
323
+ self.gamepad_device.vibrationActuator.playEffect(
324
+ "dual-rumble",
325
+ startDelay=0,
326
+ duration=int(duration*1000),
327
+ strongMagnitude=strength_clamp,
328
+ )
329
+
330
+ def rumble_stop_weak(self) -> None:
331
+ if self.device.vibration is True:
332
+ self.gamepad_device.vibrationActuator.reset()
333
+
334
+ def rumble_stop_strong(self) -> None:
335
+ if self.device.vibration is True:
336
+ self.gamepad_device.vibrationActuator.reset()
337
+
338
+
339
+ class JavascriptGamepadManager(ControllerManager):
340
+ """Interface between Javascript/Pyodide and Pyglet to manage controllers."""
341
+
342
+ _controllers: dict[int, GamepadController]
343
+
344
+ def __init__(self):
345
+ self._controllers = {}
346
+ if hasattr(navigator, "getGamepads"):
347
+ console.log("Gamepad API supported")
348
+ else:
349
+ console.log("Gamepad API not supported.")
350
+ raise ImportError("Gamepad API not supported in this browser.")
351
+
352
+ # Premake the gamepad objects so they can be kept on the user side.
353
+ self._controllers = {}
354
+ self._connected = set()
355
+
356
+ self._proxy_connect = create_proxy(self.on_gamepad_connected)
357
+ self._proxy_disconnect = create_proxy(self.on_gamepad_disconnected)
358
+
359
+ # Add event listeners for gamepad connection and disconnection
360
+ js.window.addEventListener("gamepadconnected", self._proxy_connect)
361
+ js.window.addEventListener("gamepaddisconnected", self._proxy_disconnect)
362
+
363
+ pyglet.clock.schedule_interval_soft(self._query_pads, 1/60.0)
364
+
365
+ def on_gamepad_connected(self, event) -> None:
366
+ console.log(f"Gamepad {event.gamepad.index} connected: {event.gamepad.id}. | Buttons: {len(event.gamepad.buttons)}, | Axis: {len(event.gamepad.axes)}")
367
+ wrapper = JavascriptGamepad(event.gamepad)
368
+ controller = GamepadController(wrapper, {"name": wrapper.device_name, "guid": wrapper.get_guid() })
369
+ self._controllers[event.gamepad.index] = controller
370
+ self.dispatch_event('on_connect', controller)
371
+
372
+ def on_gamepad_disconnected(self, event) -> None:
373
+ console.log(f"Gamepad disconnected: {event.gamepad.id}")
374
+ if event.gamepad.index in self._controllers:
375
+ del self._controllers[event.gamepad.index]
376
+
377
+ def _query_pads(self, _dt: float) -> None:
378
+ gamepads = navigator.getGamepads()
379
+
380
+ for i in range(gamepads.length):
381
+ gamepad = gamepads[i]
382
+ if gamepad is not None:
383
+ self._controllers[i].update(gamepad)
384
+
385
+ @property
386
+ def maximum_controllers(self) -> int:
387
+ gamepads = navigator.getGamepads()
388
+ return gamepads.length
389
+
390
+ def get_devices(self):
391
+ return []
392
+
393
+ def get_controllers(self):
394
+ return list(self._controllers.values())
395
+
396
+ def get_controllers(display=None):
397
+ return []
@@ -1,10 +1,16 @@
1
- from .evdev import get_devices as evdev_get_devices
1
+ import warnings
2
+
3
+ import pyglet
4
+
5
+ from .evdev import get_devices
2
6
  from .evdev import get_joysticks
3
7
  from .evdev import get_controllers
4
8
  from .evdev import EvdevControllerManager as ControllerManager
5
- from .x11_xinput_tablet import get_tablets
6
- from .x11_xinput import get_devices as x11xinput_get_devices
7
9
 
10
+ if not (pyglet.options.headless or pyglet.options.wayland):
11
+ from .x11_xinput_tablet import get_tablets
8
12
 
9
- def get_devices(display=None):
10
- return evdev_get_devices(display) + x11xinput_get_devices(display)
13
+ else:
14
+ def get_tablets():
15
+ warnings.warn('Tablets are currently only supported under Xlib.')
16
+ return []
@@ -17,8 +17,8 @@ from ctypes import c_byte as _c_byte
17
17
  import pyglet
18
18
 
19
19
  from .evdev_constants import *
20
- from pyglet.app.xlib import XlibSelectDevice
21
- from pyglet.libs.ioctl import _IOR, _IOR_str, _IOR_len, _IOW
20
+ from pyglet.app.linux import LinuxSelectDevice
21
+ from pyglet.libs.linux.ioctl import _IOR, _IOR_str, _IOR_len, _IOW
22
22
  from pyglet.input.base import Device, RelativeAxis, AbsoluteAxis, Button, Joystick, Controller
23
23
  from pyglet.input.base import DeviceOpenException, ControllerManager
24
24
  from pyglet.input.controller import get_mapping, Relation, create_guid
@@ -273,7 +273,7 @@ event_types = {
273
273
  }
274
274
 
275
275
 
276
- class EvdevDevice(XlibSelectDevice, Device):
276
+ class EvdevDevice(LinuxSelectDevice, Device):
277
277
  _fileno: int | None
278
278
  _poll: "select.poll | None"
279
279
 
@@ -393,7 +393,7 @@ class EvdevDevice(XlibSelectDevice, Device):
393
393
  def ff_upload_effect(self, structure):
394
394
  os.write(self._fileno, structure)
395
395
 
396
- # XlibSelectDevice interface
396
+ # LinuxSelectDevice interface
397
397
 
398
398
  def fileno(self):
399
399
  return self._fileno
@@ -498,7 +498,7 @@ class FFController(Controller):
498
498
  self.device.ff_upload_effect(self._stop_strong_event)
499
499
 
500
500
 
501
- class EvdevControllerManager(ControllerManager, XlibSelectDevice):
501
+ class EvdevControllerManager(ControllerManager, LinuxSelectDevice):
502
502
 
503
503
  def __init__(self, display=None):
504
504
  super().__init__()
@@ -631,16 +631,16 @@ def _detect_controller_mapping(device):
631
631
 
632
632
  for i, control in enumerate(button_controls):
633
633
  if name := _aliases.get(control.event_code):
634
- mapping[name] = Relation('button', i)
634
+ mapping[name] = Relation('button', index=i)
635
635
 
636
636
  for i, control in enumerate(axis_controls):
637
637
  if name := _aliases.get(control.event_code):
638
- mapping[name] = Relation('axis', i)
638
+ mapping[name] = Relation('axis', index=i)
639
639
 
640
640
  for i, control in enumerate(hat_controls):
641
641
  if name := _aliases.get(control.event_code):
642
642
  index = 1 + i << 1
643
- mapping[name] = Relation('hat0', index)
643
+ mapping[name] = Relation('hat0', index=index)
644
644
 
645
645
  return mapping
646
646
 
@@ -654,9 +654,8 @@ def _create_controller(device) -> Controller | None:
654
654
 
655
655
  mapping = get_mapping(device.get_guid())
656
656
  if not mapping:
657
- warnings.warn(f"Warning: {device} (GUID: {device.get_guid()}) "
658
- f"has no controller mappings. Update the mappings in the Controller DB.\n"
659
- f"Auto-detecting as defined by the 'Linux gamepad specification'")
657
+ warnings.warn(f"\n{device} (GUID: {device.get_guid()}) has no controller mapping.\n"
658
+ f"Auto-detecting as defined by the 'Linux gamepad specification'.")
660
659
  mapping = _detect_controller_mapping(device)
661
660
 
662
661
  if FF_RUMBLE in device.ff_types:
@@ -2,11 +2,11 @@ import ctypes
2
2
  import pyglet
3
3
  from pyglet.input.base import Device, DeviceOpenException
4
4
  from pyglet.input.base import Button, RelativeAxis, AbsoluteAxis
5
- from pyglet.libs.x11 import xlib
5
+ from pyglet.libs.linux.x11 import xlib
6
6
  from pyglet.util import asstr
7
7
 
8
8
  try:
9
- from pyglet.libs.x11 import xinput as xi
9
+ from pyglet.libs.linux.x11 import xinput as xi
10
10
 
11
11
  _have_xinput = True
12
12
  except:
@@ -5,7 +5,7 @@ from pyglet.input.linux.x11_xinput import get_devices, DeviceResponder
5
5
 
6
6
 
7
7
  try:
8
- from pyglet.libs.x11 import xinput as xi
8
+ from pyglet.libs.linux.x11 import xinput as xi
9
9
  _have_xinput = True
10
10
  except:
11
11
  _have_xinput = False
@@ -1,9 +1,14 @@
1
1
  from .darwin_hid import get_devices
2
2
  from .darwin_hid import get_joysticks
3
3
  from .darwin_hid import get_apple_remote
4
- from .darwin_hid import get_controllers
5
- from .darwin_hid import DarwinControllerManager as ControllerManager
4
+ #from .darwin_hid import DarwinControllerManager as ControllerManager
5
+ from .darwin_gc import AppleControllerManager as ControllerManager
6
6
 
7
+ def get_controllers(display=None):
8
+ from .darwin_gc import get_controllers as get_apple_controllers
9
+ from .darwin_hid import get_controllers as get_darwin_controllers
10
+
11
+ return [*get_apple_controllers(), *get_darwin_controllers()]
7
12
 
8
13
  def get_tablets(display=None):
9
14
  import warnings