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.
- pyglet/__init__.py +67 -61
- pyglet/__init__.pyi +15 -8
- pyglet/app/__init__.py +22 -13
- pyglet/app/async_app.py +212 -0
- pyglet/app/base.py +2 -1
- pyglet/app/{xlib.py → linux.py} +3 -3
- pyglet/config/__init__.py +101 -0
- pyglet/config/gl/__init__.py +30 -0
- pyglet/config/gl/egl.py +120 -0
- pyglet/config/gl/macos.py +262 -0
- pyglet/config/gl/windows.py +267 -0
- pyglet/config/gl/x11.py +142 -0
- pyglet/customtypes.py +43 -2
- pyglet/display/__init__.py +8 -6
- pyglet/display/base.py +3 -63
- pyglet/display/cocoa.py +12 -17
- pyglet/display/emscripten.py +39 -0
- pyglet/display/headless.py +23 -30
- pyglet/display/wayland.py +157 -0
- pyglet/display/win32.py +5 -21
- pyglet/display/xlib.py +19 -27
- pyglet/display/xlib_vidmoderestore.py +2 -2
- pyglet/enums.py +183 -0
- pyglet/event.py +0 -1
- pyglet/experimental/geoshader_sprite.py +15 -13
- pyglet/experimental/hidraw.py +6 -15
- pyglet/experimental/multitexture_sprite.py +31 -19
- pyglet/experimental/particles.py +13 -35
- pyglet/font/__init__.py +251 -85
- pyglet/font/base.py +116 -61
- pyglet/font/dwrite/__init__.py +349 -204
- pyglet/font/dwrite/dwrite_lib.py +27 -5
- pyglet/font/fontconfig.py +14 -6
- pyglet/font/freetype.py +138 -87
- pyglet/font/freetype_lib.py +19 -0
- pyglet/font/group.py +179 -0
- pyglet/font/harfbuzz/__init__.py +3 -3
- pyglet/font/pyodide_js.py +310 -0
- pyglet/font/quartz.py +319 -126
- pyglet/font/ttf.py +45 -3
- pyglet/font/user.py +14 -19
- pyglet/font/win32.py +45 -21
- pyglet/graphics/__init__.py +8 -787
- pyglet/graphics/allocation.py +115 -1
- pyglet/graphics/api/__init__.py +77 -0
- pyglet/graphics/api/base.py +299 -0
- pyglet/graphics/api/gl/__init__.py +58 -0
- pyglet/graphics/api/gl/base.py +24 -0
- pyglet/graphics/{vertexbuffer.py → api/gl/buffer.py} +104 -159
- pyglet/graphics/api/gl/cocoa/context.py +76 -0
- pyglet/graphics/api/gl/context.py +391 -0
- pyglet/graphics/api/gl/default_shaders.py +0 -0
- pyglet/graphics/api/gl/draw.py +627 -0
- pyglet/graphics/api/gl/egl/__init__.py +0 -0
- pyglet/graphics/api/gl/egl/context.py +92 -0
- pyglet/graphics/api/gl/enums.py +76 -0
- pyglet/graphics/api/gl/framebuffer.py +315 -0
- pyglet/graphics/api/gl/gl.py +5463 -0
- pyglet/graphics/api/gl/gl_info.py +188 -0
- pyglet/graphics/api/gl/global_opengl.py +226 -0
- pyglet/{gl → graphics/api/gl}/lib.py +34 -18
- pyglet/graphics/api/gl/shader.py +1476 -0
- pyglet/graphics/api/gl/shapes.py +55 -0
- pyglet/graphics/api/gl/sprite.py +102 -0
- pyglet/graphics/api/gl/state.py +219 -0
- pyglet/graphics/api/gl/text.py +190 -0
- pyglet/graphics/api/gl/texture.py +1526 -0
- pyglet/graphics/{vertexarray.py → api/gl/vertexarray.py} +11 -13
- pyglet/graphics/api/gl/vertexdomain.py +751 -0
- pyglet/graphics/api/gl/win32/__init__.py +0 -0
- pyglet/graphics/api/gl/win32/context.py +108 -0
- pyglet/graphics/api/gl/win32/wgl_info.py +24 -0
- pyglet/graphics/api/gl/xlib/__init__.py +0 -0
- pyglet/graphics/api/gl/xlib/context.py +174 -0
- pyglet/{gl → graphics/api/gl/xlib}/glx_info.py +26 -31
- pyglet/graphics/api/gl1/__init__.py +0 -0
- pyglet/{gl → graphics/api/gl1}/gl_compat.py +3 -2
- pyglet/graphics/api/gl2/__init__.py +0 -0
- pyglet/graphics/api/gl2/buffer.py +320 -0
- pyglet/graphics/api/gl2/draw.py +600 -0
- pyglet/graphics/api/gl2/global_opengl.py +122 -0
- pyglet/graphics/api/gl2/shader.py +200 -0
- pyglet/graphics/api/gl2/shapes.py +51 -0
- pyglet/graphics/api/gl2/sprite.py +79 -0
- pyglet/graphics/api/gl2/text.py +175 -0
- pyglet/graphics/api/gl2/vertexdomain.py +364 -0
- pyglet/graphics/api/webgl/__init__.py +233 -0
- pyglet/graphics/api/webgl/buffer.py +302 -0
- pyglet/graphics/api/webgl/context.py +234 -0
- pyglet/graphics/api/webgl/draw.py +590 -0
- pyglet/graphics/api/webgl/enums.py +76 -0
- pyglet/graphics/api/webgl/framebuffer.py +360 -0
- pyglet/graphics/api/webgl/gl.py +1537 -0
- pyglet/graphics/api/webgl/gl_info.py +130 -0
- pyglet/graphics/api/webgl/shader.py +1346 -0
- pyglet/graphics/api/webgl/shapes.py +92 -0
- pyglet/graphics/api/webgl/sprite.py +102 -0
- pyglet/graphics/api/webgl/state.py +227 -0
- pyglet/graphics/api/webgl/text.py +187 -0
- pyglet/graphics/api/webgl/texture.py +1227 -0
- pyglet/graphics/api/webgl/vertexarray.py +54 -0
- pyglet/graphics/api/webgl/vertexdomain.py +616 -0
- pyglet/graphics/api/webgl/webgl_js.pyi +307 -0
- pyglet/{image → graphics}/atlas.py +33 -32
- pyglet/graphics/base.py +10 -0
- pyglet/graphics/buffer.py +245 -0
- pyglet/graphics/draw.py +578 -0
- pyglet/graphics/framebuffer.py +26 -0
- pyglet/graphics/instance.py +178 -69
- pyglet/graphics/shader.py +267 -1553
- pyglet/graphics/state.py +83 -0
- pyglet/graphics/texture.py +703 -0
- pyglet/graphics/vertexdomain.py +695 -538
- pyglet/gui/ninepatch.py +10 -10
- pyglet/gui/widgets.py +120 -10
- pyglet/image/__init__.py +20 -1973
- pyglet/image/animation.py +12 -12
- pyglet/image/base.py +730 -0
- pyglet/image/codecs/__init__.py +9 -0
- pyglet/image/codecs/bmp.py +53 -30
- pyglet/image/codecs/dds.py +53 -31
- pyglet/image/codecs/gdiplus.py +38 -14
- pyglet/image/codecs/gdkpixbuf2.py +0 -2
- pyglet/image/codecs/js_image.py +99 -0
- pyglet/image/codecs/ktx2.py +161 -0
- pyglet/image/codecs/pil.py +1 -1
- pyglet/image/codecs/png.py +1 -1
- pyglet/image/codecs/wic.py +11 -2
- pyglet/info.py +26 -24
- pyglet/input/__init__.py +8 -0
- pyglet/input/base.py +163 -105
- pyglet/input/controller.py +13 -19
- pyglet/input/controller_db.py +39 -24
- pyglet/input/emscripten/__init__.py +18 -0
- pyglet/input/emscripten/gamepad_js.py +397 -0
- pyglet/input/linux/__init__.py +11 -5
- pyglet/input/linux/evdev.py +10 -11
- pyglet/input/linux/x11_xinput.py +2 -2
- pyglet/input/linux/x11_xinput_tablet.py +1 -1
- pyglet/input/macos/__init__.py +7 -2
- pyglet/input/macos/darwin_gc.py +559 -0
- pyglet/input/win32/__init__.py +1 -1
- pyglet/input/win32/directinput.py +34 -29
- pyglet/input/win32/xinput.py +11 -61
- pyglet/lib.py +3 -3
- pyglet/libs/__init__.py +1 -1
- pyglet/{gl → libs/darwin}/agl.py +1 -1
- pyglet/libs/darwin/cocoapy/__init__.py +2 -2
- pyglet/libs/darwin/cocoapy/cocoahelpers.py +181 -0
- pyglet/libs/darwin/cocoapy/cocoalibs.py +31 -0
- pyglet/libs/darwin/cocoapy/cocoatypes.py +27 -0
- pyglet/libs/darwin/cocoapy/runtime.py +81 -45
- pyglet/libs/darwin/coreaudio.py +4 -4
- pyglet/{gl → libs/darwin}/lib_agl.py +9 -8
- pyglet/libs/darwin/quartzkey.py +1 -3
- pyglet/libs/egl/__init__.py +2 -0
- pyglet/libs/egl/egl_lib.py +576 -0
- pyglet/libs/egl/eglext.py +51 -5
- pyglet/libs/linux/__init__.py +0 -0
- pyglet/libs/linux/egl/__init__.py +0 -0
- pyglet/libs/linux/egl/eglext.py +22 -0
- pyglet/libs/linux/glx/__init__.py +0 -0
- pyglet/{gl → libs/linux/glx}/glx.py +13 -14
- pyglet/{gl → libs/linux/glx}/glxext_arb.py +408 -192
- pyglet/{gl → libs/linux/glx}/glxext_mesa.py +1 -1
- pyglet/{gl → libs/linux/glx}/glxext_nv.py +345 -164
- pyglet/{gl → libs/linux/glx}/lib_glx.py +3 -2
- pyglet/libs/linux/wayland/__init__.py +0 -0
- pyglet/libs/linux/wayland/client.py +1068 -0
- pyglet/libs/linux/wayland/lib_wayland.py +207 -0
- pyglet/libs/linux/wayland/wayland_egl.py +38 -0
- pyglet/libs/{wayland → linux/wayland}/xkbcommon.py +26 -0
- pyglet/libs/{x11 → linux/x11}/xf86vmode.py +4 -4
- pyglet/libs/{x11 → linux/x11}/xinerama.py +2 -2
- pyglet/libs/{x11 → linux/x11}/xinput.py +10 -10
- pyglet/libs/linux/x11/xrandr.py +0 -0
- pyglet/libs/{x11 → linux/x11}/xrender.py +1 -1
- pyglet/libs/shared/__init__.py +0 -0
- pyglet/libs/shared/spirv/__init__.py +0 -0
- pyglet/libs/shared/spirv/lib_shaderc.py +85 -0
- pyglet/libs/shared/spirv/lib_spirv_cross.py +126 -0
- pyglet/libs/win32/__init__.py +28 -8
- pyglet/libs/win32/constants.py +59 -48
- pyglet/libs/win32/context_managers.py +20 -3
- pyglet/libs/win32/dinput.py +105 -88
- pyglet/{gl → libs/win32}/lib_wgl.py +52 -26
- pyglet/libs/win32/types.py +58 -23
- pyglet/{gl → libs/win32}/wgl.py +32 -25
- pyglet/{gl → libs/win32}/wglext_arb.py +364 -2
- pyglet/media/__init__.py +9 -10
- pyglet/media/codecs/__init__.py +12 -1
- pyglet/media/codecs/base.py +99 -96
- pyglet/media/codecs/ffmpeg.py +2 -2
- pyglet/media/codecs/ffmpeg_lib/libavformat.py +3 -8
- pyglet/media/codecs/webaudio_pyodide.py +111 -0
- pyglet/media/drivers/__init__.py +9 -4
- pyglet/media/drivers/base.py +4 -4
- pyglet/media/drivers/openal/__init__.py +1 -1
- pyglet/media/drivers/openal/adaptation.py +3 -3
- pyglet/media/drivers/pulse/__init__.py +1 -1
- pyglet/media/drivers/pulse/adaptation.py +3 -3
- pyglet/media/drivers/pyodide_js/__init__.py +8 -0
- pyglet/media/drivers/pyodide_js/adaptation.py +288 -0
- pyglet/media/drivers/xaudio2/adaptation.py +3 -3
- pyglet/media/player.py +276 -193
- pyglet/media/player_worker_thread.py +1 -1
- pyglet/model/__init__.py +39 -29
- pyglet/model/codecs/base.py +4 -4
- pyglet/model/codecs/gltf.py +3 -3
- pyglet/model/codecs/obj.py +71 -43
- pyglet/resource.py +129 -78
- pyglet/shapes.py +154 -194
- pyglet/sprite.py +47 -164
- pyglet/text/__init__.py +44 -54
- pyglet/text/caret.py +12 -7
- pyglet/text/document.py +19 -17
- pyglet/text/formats/html.py +2 -2
- pyglet/text/formats/structured.py +10 -40
- pyglet/text/layout/__init__.py +20 -13
- pyglet/text/layout/base.py +176 -287
- pyglet/text/layout/incremental.py +9 -10
- pyglet/text/layout/scrolling.py +7 -95
- pyglet/window/__init__.py +183 -172
- pyglet/window/cocoa/__init__.py +62 -51
- pyglet/window/cocoa/pyglet_delegate.py +2 -25
- pyglet/window/cocoa/pyglet_view.py +9 -8
- pyglet/window/dialog/__init__.py +184 -0
- pyglet/window/dialog/base.py +99 -0
- pyglet/window/dialog/darwin.py +121 -0
- pyglet/window/dialog/linux.py +72 -0
- pyglet/window/dialog/windows.py +194 -0
- pyglet/window/emscripten/__init__.py +779 -0
- pyglet/window/headless/__init__.py +44 -28
- pyglet/window/key.py +2 -0
- pyglet/window/mouse.py +2 -2
- pyglet/window/wayland/__init__.py +377 -0
- pyglet/window/win32/__init__.py +101 -46
- pyglet/window/xlib/__init__.py +104 -66
- {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/METADATA +2 -3
- pyglet-3.0.dev1.dist-info/RECORD +322 -0
- {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/WHEEL +1 -1
- pyglet/gl/__init__.py +0 -208
- pyglet/gl/base.py +0 -499
- pyglet/gl/cocoa.py +0 -309
- pyglet/gl/gl.py +0 -4625
- pyglet/gl/gl.pyi +0 -2320
- pyglet/gl/gl_compat.pyi +0 -3097
- pyglet/gl/gl_info.py +0 -190
- pyglet/gl/headless.py +0 -166
- pyglet/gl/wgl_info.py +0 -36
- pyglet/gl/wglext_nv.py +0 -1096
- pyglet/gl/win32.py +0 -268
- pyglet/gl/xlib.py +0 -295
- pyglet/image/buffer.py +0 -274
- pyglet/image/codecs/s3tc.py +0 -354
- pyglet/libs/x11/xrandr.py +0 -166
- pyglet-2.1.13.dist-info/RECORD +0 -234
- /pyglet/{libs/wayland → graphics/api/gl/cocoa}/__init__.py +0 -0
- /pyglet/libs/{egl → linux/egl}/egl.py +0 -0
- /pyglet/libs/{egl → linux/egl}/lib.py +0 -0
- /pyglet/libs/{ioctl.py → linux/ioctl.py} +0 -0
- /pyglet/libs/{wayland → linux/wayland}/gbm.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/__init__.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/cursorfont.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/xlib.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/xsync.py +0 -0
- {pyglet-2.1.13.dist-info/licenses → pyglet-3.0.dev1.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import warnings
|
|
5
|
+
import weakref
|
|
6
|
+
from ctypes import cdll, util, c_void_p, byref
|
|
7
|
+
from enum import Enum, auto
|
|
8
|
+
from typing import Protocol
|
|
9
|
+
|
|
10
|
+
import pyglet
|
|
11
|
+
from pyglet.event import EventDispatcher
|
|
12
|
+
from pyglet.libs.darwin import ObjCSubclass, ObjCInstance, send_super, \
|
|
13
|
+
AutoReleasePool, ns_to_py, nsdict_to_py, PyObjectEncoding, nsnum_to_py
|
|
14
|
+
from pyglet.libs.darwin.cocoapy.runtime import get_callback_block
|
|
15
|
+
from pyglet.math import Vec2
|
|
16
|
+
|
|
17
|
+
from pyglet.window.cocoa.pyglet_delegate import NSNotification
|
|
18
|
+
|
|
19
|
+
from pyglet.input.base import Device, Control, Controller, Button, AbsoluteAxis, ControllerManager, Sign
|
|
20
|
+
|
|
21
|
+
from pyglet.libs.darwin import ObjCClass, get_selector
|
|
22
|
+
from pyglet.window.cocoa.pyglet_view import NSNotificationCenter
|
|
23
|
+
|
|
24
|
+
lib = util.find_library('GameController')
|
|
25
|
+
|
|
26
|
+
# Hack for compatibility with macOS > 11.0
|
|
27
|
+
if lib is None:
|
|
28
|
+
lib = '/System/Library/Frameworks/GameController.framework/GameController'
|
|
29
|
+
|
|
30
|
+
gc = cdll.LoadLibrary(lib)
|
|
31
|
+
|
|
32
|
+
NSObject = ObjCClass('NSObject')
|
|
33
|
+
GCController = ObjCClass("GCController")
|
|
34
|
+
NSArray = ObjCClass('NSArray')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _GCController(Protocol):
|
|
38
|
+
"""Just a type hint to better understand the controller API's"""
|
|
39
|
+
|
|
40
|
+
def playerIndex(self) -> int: ...
|
|
41
|
+
|
|
42
|
+
def vendorName(self) -> str: ...
|
|
43
|
+
|
|
44
|
+
def battery(self): ...
|
|
45
|
+
|
|
46
|
+
def haptics(self): ...
|
|
47
|
+
|
|
48
|
+
def light(self): ...
|
|
49
|
+
|
|
50
|
+
def physicalInputProfile(self): ...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
GCControllerDidConnectNotification = c_void_p.in_dll(gc, 'GCControllerDidConnectNotification')
|
|
54
|
+
GCControllerDidDisconnectNotification = c_void_p.in_dll(gc, 'GCControllerDidDisconnectNotification')
|
|
55
|
+
GCInputDualShockTouchpadButton = c_void_p.in_dll(gc, 'GCInputDualShockTouchpadButton')
|
|
56
|
+
|
|
57
|
+
GCHapticsLocalityDefault = c_void_p.in_dll(gc, 'GCHapticsLocalityDefault')
|
|
58
|
+
GCHapticsLocalityAll = c_void_p.in_dll(gc, 'GCHapticsLocalityAll')
|
|
59
|
+
GCHapticsLocalityHandles = c_void_p.in_dll(gc, 'GCHapticsLocalityHandles')
|
|
60
|
+
GCHapticsLocalityLeftHandle = c_void_p.in_dll(gc, 'GCHapticsLocalityLeftHandle')
|
|
61
|
+
GCHapticsLocalityRightHandle = c_void_p.in_dll(gc, 'GCHapticsLocalityRightHandle')
|
|
62
|
+
GCHapticsLocalityTriggers = c_void_p.in_dll(gc, 'GCHapticsLocalityTriggers')
|
|
63
|
+
GCHapticsLocalityLeftTrigger = c_void_p.in_dll(gc, 'GCHapticsLocalityLeftTrigger')
|
|
64
|
+
GCHapticsLocalityRightTrigger = c_void_p.in_dll(gc, 'GCHapticsLocalityRightTrigger')
|
|
65
|
+
|
|
66
|
+
CHHapticEventParameterIDHapticSharpness = c_void_p.in_dll(gc, 'CHHapticEventParameterIDHapticSharpness')
|
|
67
|
+
CHHapticEventParameterIDHapticIntensity = c_void_p.in_dll(gc, 'CHHapticEventParameterIDHapticIntensity')
|
|
68
|
+
CHHapticEventParameterIDDecayTime = c_void_p.in_dll(gc, 'CHHapticEventParameterIDDecayTime')
|
|
69
|
+
CHHapticEventParameterIDReleaseTime = c_void_p.in_dll(gc, 'CHHapticEventParameterIDReleaseTime')
|
|
70
|
+
CHHapticEventParameterIDSustained = c_void_p.in_dll(gc, 'CHHapticEventParameterIDSustained')
|
|
71
|
+
|
|
72
|
+
CHHapticEventTypeAudioContinuous = c_void_p.in_dll(gc, 'CHHapticEventTypeAudioContinuous')
|
|
73
|
+
CHHapticEventTypeAudioCustom = c_void_p.in_dll(gc, 'CHHapticEventTypeAudioCustom')
|
|
74
|
+
CHHapticEventTypeHapticTransient = c_void_p.in_dll(gc, 'CHHapticEventTypeHapticTransient')
|
|
75
|
+
CHHapticEventTypeHapticContinuous = c_void_p.in_dll(gc, 'CHHapticEventTypeHapticContinuous')
|
|
76
|
+
|
|
77
|
+
GCHapticDurationInfinite = c_void_p.in_dll(gc, 'GCHapticDurationInfinite')
|
|
78
|
+
|
|
79
|
+
GCSystemGestureStateDisabled = 2
|
|
80
|
+
|
|
81
|
+
CHHapticDynamicParameterIDHapticIntensityControl = c_void_p.in_dll(gc,
|
|
82
|
+
'CHHapticDynamicParameterIDHapticIntensityControl')
|
|
83
|
+
|
|
84
|
+
CHHapticEventParameter = ObjCClass('CHHapticEventParameter')
|
|
85
|
+
CHHapticEvent = ObjCClass('CHHapticEvent')
|
|
86
|
+
CHHapticPattern = ObjCClass('CHHapticPattern')
|
|
87
|
+
CHHapticDynamicParameter = ObjCClass('CHHapticDynamicParameter')
|
|
88
|
+
|
|
89
|
+
GCDeviceBatteryStateUnknown = -1
|
|
90
|
+
GCDeviceBatteryStateDischarging = 0
|
|
91
|
+
GCDeviceBatteryStateCharging = 1
|
|
92
|
+
GCDeviceBatteryStateFull = 2
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class BatteryState(Enum):
|
|
96
|
+
UNKNOWN = auto()
|
|
97
|
+
DISCHARGING = auto()
|
|
98
|
+
CHARGING = auto()
|
|
99
|
+
FULL = auto()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
_state_mapping = {
|
|
103
|
+
GCDeviceBatteryStateUnknown: BatteryState.UNKNOWN,
|
|
104
|
+
GCDeviceBatteryStateDischarging: BatteryState.DISCHARGING,
|
|
105
|
+
GCDeviceBatteryStateCharging: BatteryState.CHARGING,
|
|
106
|
+
GCDeviceBatteryStateFull: BatteryState.FULL,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_button_mapping = {
|
|
110
|
+
"Direction Pad Up": "dpup",
|
|
111
|
+
"Direction Pad Down": "dpdown",
|
|
112
|
+
"Direction Pad Left": "dpleft",
|
|
113
|
+
"Direction Pad Right": "dpright",
|
|
114
|
+
"Button Menu": "start",
|
|
115
|
+
"Button Options": "back",
|
|
116
|
+
"Button Home": "guide",
|
|
117
|
+
"Left Thumbstick Button": "leftthumb",
|
|
118
|
+
"Right Thumbstick Button": "rightthumb",
|
|
119
|
+
"Left Shoulder": "leftshoulder",
|
|
120
|
+
"Right Shoulder": "rightshoulder",
|
|
121
|
+
"Left Trigger": "lefttrigger",
|
|
122
|
+
"Right Trigger": "righttrigger",
|
|
123
|
+
"Button A": "a",
|
|
124
|
+
"Button B": "b",
|
|
125
|
+
"Button X": "x",
|
|
126
|
+
"Button Y": "y",
|
|
127
|
+
# "Touchpad Button": "touchpad",
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
_axis_mapping = {
|
|
131
|
+
"Right Thumbstick Y Axis": "righty",
|
|
132
|
+
"Right Thumbstick X Axis": "rightx",
|
|
133
|
+
|
|
134
|
+
"Left Thumbstick X Axis": "leftx",
|
|
135
|
+
"Left Thumbstick Y Axis": "lefty",
|
|
136
|
+
|
|
137
|
+
"Left Trigger": "lefttrigger",
|
|
138
|
+
"Right Trigger": "righttrigger",
|
|
139
|
+
|
|
140
|
+
# "Direction Pad X Axis": "leftx",
|
|
141
|
+
# "Direction Pad Y Axis": "lefty",
|
|
142
|
+
|
|
143
|
+
# "Touchpad 1 Y Axis": "unsupport",
|
|
144
|
+
# "Touchpad 1 X Axis": "unsupport",
|
|
145
|
+
# "Touchpad 2 Y Axis": "unsupport",
|
|
146
|
+
# "Touchpad 2 X Axis": "unsupport",
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
_dpads_mapping = {
|
|
150
|
+
"Direction Pad": AbsoluteAxis.HAT,
|
|
151
|
+
# "Touchpad 2": "unknown",
|
|
152
|
+
# "Touchpad 1": "unknown",
|
|
153
|
+
# "Right Thumbstick": "unknown",
|
|
154
|
+
# "Left Thumbstick": "unknown",
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class HapticEngine:
|
|
159
|
+
def __init__(self, name: str, engine):
|
|
160
|
+
self.name = name
|
|
161
|
+
self.engine = engine
|
|
162
|
+
|
|
163
|
+
# !!! Must retain or it will release on ObjC side.
|
|
164
|
+
self.engine.retain()
|
|
165
|
+
|
|
166
|
+
self._player = None
|
|
167
|
+
self.is_playing = False
|
|
168
|
+
|
|
169
|
+
self.error = c_void_p()
|
|
170
|
+
|
|
171
|
+
self._create_player()
|
|
172
|
+
|
|
173
|
+
def _create_player(self):
|
|
174
|
+
with AutoReleasePool():
|
|
175
|
+
event_param = CHHapticEventParameter.alloc().initWithParameterID_value_(
|
|
176
|
+
CHHapticEventParameterIDHapticIntensity, 1.0,
|
|
177
|
+
)
|
|
178
|
+
params_arr = NSArray.arrayWithObject_(event_param)
|
|
179
|
+
|
|
180
|
+
self.event = CHHapticEvent.alloc().initWithEventType_parameters_relativeTime_duration_(
|
|
181
|
+
CHHapticEventTypeHapticContinuous,
|
|
182
|
+
params_arr,
|
|
183
|
+
0.0,
|
|
184
|
+
10000.0
|
|
185
|
+
)
|
|
186
|
+
event_arr = NSArray.arrayWithObject_(self.event)
|
|
187
|
+
empty_arr = NSArray.alloc().init()
|
|
188
|
+
|
|
189
|
+
pattern = CHHapticPattern.alloc().initWithEvents_parameters_error_(event_arr, empty_arr,
|
|
190
|
+
byref(self.error))
|
|
191
|
+
|
|
192
|
+
if not pattern:
|
|
193
|
+
self.as_nserror()
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
self._player = self.engine.createPlayerWithPattern_error_(pattern, byref(self.error))
|
|
197
|
+
|
|
198
|
+
if not self._player:
|
|
199
|
+
self.as_nserror()
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
self._player.retain()
|
|
203
|
+
|
|
204
|
+
def as_nserror(self):
|
|
205
|
+
ns_error = ObjCInstance(self.error.value)
|
|
206
|
+
# print("macos GC error:", ns_error.localizedDescription())
|
|
207
|
+
|
|
208
|
+
def trigger_event(self, intensity, sharpness, duration):
|
|
209
|
+
self.is_playing = True
|
|
210
|
+
|
|
211
|
+
param = CHHapticDynamicParameter.alloc().initWithParameterID_value_relativeTime_(
|
|
212
|
+
CHHapticDynamicParameterIDHapticIntensityControl, intensity, 0,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
dynam_param_arr = NSArray.arrayWithObject_(param)
|
|
216
|
+
|
|
217
|
+
self._player.sendParameters_atTime_error_(dynam_param_arr, 0, byref(self.error))
|
|
218
|
+
if self.error:
|
|
219
|
+
# print("macos GC: Failed to update parameter")
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
self._player.startAtTime_error_(0, None)
|
|
223
|
+
|
|
224
|
+
pyglet.clock.unschedule(self._schedule_stop)
|
|
225
|
+
pyglet.clock.schedule_once(self._schedule_stop, duration)
|
|
226
|
+
|
|
227
|
+
def _schedule_stop(self, dt):
|
|
228
|
+
self.stop_event()
|
|
229
|
+
|
|
230
|
+
def stop_event(self):
|
|
231
|
+
if self.is_playing and self._player:
|
|
232
|
+
self._player.stopAtTime_error_(0, None)
|
|
233
|
+
self.is_playing = False
|
|
234
|
+
|
|
235
|
+
def delete(self):
|
|
236
|
+
if self._player:
|
|
237
|
+
pyglet.clock.unschedule(self._schedule_stop)
|
|
238
|
+
self.stop_event()
|
|
239
|
+
self._player.cancelAndReturnError_(None)
|
|
240
|
+
self._player.release()
|
|
241
|
+
self._player = None
|
|
242
|
+
|
|
243
|
+
if self.engine:
|
|
244
|
+
self.engine.stopWithCompletionHandler_(None)
|
|
245
|
+
self.engine.release()
|
|
246
|
+
self.engine = None
|
|
247
|
+
|
|
248
|
+
def __del__(self):
|
|
249
|
+
self.delete()
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class AppleGamepad(Device):
|
|
253
|
+
|
|
254
|
+
def __init__(self, manager, controller: _GCController) -> None:
|
|
255
|
+
|
|
256
|
+
with AutoReleasePool():
|
|
257
|
+
self.manager = weakref.proxy(manager)
|
|
258
|
+
self.device_name = ns_to_py(controller.vendorName())
|
|
259
|
+
self.product_category = ns_to_py(controller.productCategory())
|
|
260
|
+
|
|
261
|
+
super().__init__(None, self.device_name)
|
|
262
|
+
self.controller = controller
|
|
263
|
+
|
|
264
|
+
self.index = controller.playerIndex()
|
|
265
|
+
|
|
266
|
+
self.battery = controller.battery() # GCDeviceBattery
|
|
267
|
+
self.haptics = controller.haptics() # GCDeviceHaptics
|
|
268
|
+
self.light = controller.light() # GCDeviceLight
|
|
269
|
+
|
|
270
|
+
self.weak_motor_engine = None
|
|
271
|
+
self.strong_motor_engine = None
|
|
272
|
+
|
|
273
|
+
if self.haptics:
|
|
274
|
+
# self.weak_motor_engine = self._get_motor_engine(GCHapticsLocalityDefault)
|
|
275
|
+
self.weak_motor_engine = self._get_motor_engine(GCHapticsLocalityLeftHandle)
|
|
276
|
+
self.strong_motor_engine = self._get_motor_engine(GCHapticsLocalityRightHandle)
|
|
277
|
+
|
|
278
|
+
# Can not get a strong or weak motor, just use default.
|
|
279
|
+
if self.weak_motor_engine is None and self.strong_motor_engine is None:
|
|
280
|
+
engine = self._get_motor_engine(GCHapticsLocalityDefault)
|
|
281
|
+
self.weak_motor_engine = engine
|
|
282
|
+
self.strong_motor_engine = engine
|
|
283
|
+
# No weak motor, just use the strong one?
|
|
284
|
+
elif self.weak_motor_engine is None and self.strong_motor_engine is not None:
|
|
285
|
+
self.weak_motor_engine = self.strong_motor_engine
|
|
286
|
+
# No strong motor, use the weak one?
|
|
287
|
+
elif self.weak_motor_engine is not None and self.strong_motor_engine is None:
|
|
288
|
+
self.strong_motor_engine = self.weak_motor_engine
|
|
289
|
+
|
|
290
|
+
self.profile = controller.physicalInputProfile()
|
|
291
|
+
|
|
292
|
+
self.buttons = ns_to_py(self.profile.buttons())
|
|
293
|
+
self.axes = ns_to_py(self.profile.axes())
|
|
294
|
+
self.dpads = ns_to_py(self.profile.dpads())
|
|
295
|
+
|
|
296
|
+
self._button_ptrs = {}
|
|
297
|
+
if self.buttons:
|
|
298
|
+
self._button_cb_block = get_callback_block(self._button_changed_callback, encoding=['v', '@', 'f'])
|
|
299
|
+
for button_name, button_objc in self.buttons.items():
|
|
300
|
+
assert button_objc.ptr.value not in self._button_ptrs
|
|
301
|
+
pyglet_name = _button_mapping.get(button_name)
|
|
302
|
+
if not pyglet_name:
|
|
303
|
+
# print(f"Found: {button_name} button, but does not map to anything. Ignoring.")
|
|
304
|
+
continue
|
|
305
|
+
# Try to disable any "gestured" buttons so they can be used like actual buttons.
|
|
306
|
+
if button_objc.isBoundToSystemGesture():
|
|
307
|
+
button_objc.setPreferredSystemGestureState_(GCSystemGestureStateDisabled)
|
|
308
|
+
self._button_ptrs[button_objc.ptr.value] = pyglet_name
|
|
309
|
+
|
|
310
|
+
button_objc.setValueChangedHandler_(self._button_cb_block)
|
|
311
|
+
|
|
312
|
+
self._axes_ptrs = {}
|
|
313
|
+
if self.axes:
|
|
314
|
+
self._axes_cb_block = get_callback_block(self._axis_changed_callback, encoding=['v', '@', 'f'])
|
|
315
|
+
for axis_name, axis_obj in self.axes.items():
|
|
316
|
+
assert axis_obj.ptr.value not in self._axes_ptrs
|
|
317
|
+
pyglet_name = _axis_mapping.get(axis_name)
|
|
318
|
+
if not pyglet_name:
|
|
319
|
+
# print(f"Found: {axis_name} axis, but does not map to anything. Ignoring.")
|
|
320
|
+
continue
|
|
321
|
+
self._axes_ptrs[axis_obj.ptr.value] = pyglet_name
|
|
322
|
+
axis_obj.setValueChangedHandler_(self._axes_cb_block)
|
|
323
|
+
|
|
324
|
+
self._dpad_ptrs = {}
|
|
325
|
+
if self.dpads:
|
|
326
|
+
self._dpad_cb_block = get_callback_block(self._dpads_changed_callback, encoding=['v', '@', 'f', 'f'])
|
|
327
|
+
for dpad_name, dpad_obj in self.dpads.items():
|
|
328
|
+
assert dpad_obj.ptr.value not in self._dpad_ptrs
|
|
329
|
+
pyglet_name = _dpads_mapping.get(dpad_name)
|
|
330
|
+
|
|
331
|
+
if not pyglet_name:
|
|
332
|
+
continue
|
|
333
|
+
self._dpad_ptrs[dpad_obj.ptr.value] = pyglet_name
|
|
334
|
+
dpad_obj.setValueChangedHandler_(self._dpad_cb_block)
|
|
335
|
+
|
|
336
|
+
# print("DEVICE NAME", repr(self.device_name), "index", self.index, self.product_category)
|
|
337
|
+
|
|
338
|
+
# We need to store the Block's so callbacks don't get GC'd, as it contains CFUNCTYPES.
|
|
339
|
+
self.cb_blocks = {}
|
|
340
|
+
|
|
341
|
+
self.controls = {
|
|
342
|
+
'a': Button('a'),
|
|
343
|
+
'b': Button('b'),
|
|
344
|
+
'x': Button('x'),
|
|
345
|
+
'y': Button('y'),
|
|
346
|
+
'back': Button('back'),
|
|
347
|
+
'start': Button('start'),
|
|
348
|
+
'guide': Button('guide'),
|
|
349
|
+
'leftshoulder': Button('leftshoulder'),
|
|
350
|
+
'rightshoulder': Button('rightshoulder'),
|
|
351
|
+
'leftthumb': Button('leftthumb'),
|
|
352
|
+
'rightthumb': Button('rightthumb'),
|
|
353
|
+
'dpup': Button('dpup'),
|
|
354
|
+
'dpdown': Button('dpdown'),
|
|
355
|
+
'dpleft': Button('dpleft'),
|
|
356
|
+
'dpright': Button('dpright'),
|
|
357
|
+
|
|
358
|
+
'leftx': AbsoluteAxis('leftx', -1, 1),
|
|
359
|
+
'lefty': AbsoluteAxis('lefty', -1, 1, inverted=True),
|
|
360
|
+
'rightx': AbsoluteAxis('rightx', -1, 1),
|
|
361
|
+
'righty': AbsoluteAxis('righty', -1, 1, inverted=True),
|
|
362
|
+
'lefttrigger': AbsoluteAxis('lefttrigger', 0, 1),
|
|
363
|
+
'righttrigger': AbsoluteAxis('righttrigger', 0, 1),
|
|
364
|
+
|
|
365
|
+
"hat": AbsoluteAxis('hat', 0, 1),
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
def _axis_changed_callback(self, axes_obj, value):
|
|
369
|
+
name = self._axes_ptrs[axes_obj]
|
|
370
|
+
self.controls[name].value = value
|
|
371
|
+
|
|
372
|
+
def _button_changed_callback(self, button_obj, value):
|
|
373
|
+
name = self._button_ptrs[button_obj]
|
|
374
|
+
self.controls[name].value = value
|
|
375
|
+
|
|
376
|
+
def _dpads_changed_callback(self, dpads_obj, value, value2):
|
|
377
|
+
name = self._dpad_ptrs[dpads_obj]
|
|
378
|
+
self.controls[name].value = value
|
|
379
|
+
|
|
380
|
+
def get_battery_level(self) -> float:
|
|
381
|
+
"""Returns the current battery level.
|
|
382
|
+
|
|
383
|
+
0.0 being discharged, or 1.0 as 100%. Defaults to 0.0 if not found.
|
|
384
|
+
"""
|
|
385
|
+
if self.battery:
|
|
386
|
+
return self.battery.batteryLevel()
|
|
387
|
+
|
|
388
|
+
return 0.0
|
|
389
|
+
|
|
390
|
+
def get_battery_state(self) -> BatteryState:
|
|
391
|
+
if self.battery:
|
|
392
|
+
battery_state = self.battery.batteryState()
|
|
393
|
+
return _state_mapping.get(battery_state, BatteryState.UNKNOWN)
|
|
394
|
+
|
|
395
|
+
return BatteryState.UNKNOWN
|
|
396
|
+
|
|
397
|
+
def _get_motor_engine(self, locality):
|
|
398
|
+
if self.haptics is None:
|
|
399
|
+
return None
|
|
400
|
+
|
|
401
|
+
locality_str = ns_to_py(ObjCInstance(locality))
|
|
402
|
+
|
|
403
|
+
if locality_str not in ns_to_py(self.haptics.supportedLocalities()):
|
|
404
|
+
warnings.warn(f"Controller supports haptics, but not locality: {locality}.")
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
engine = self.haptics.createEngineWithLocality_(locality)
|
|
408
|
+
if not engine:
|
|
409
|
+
warnings.warn("Could not create motor engine.")
|
|
410
|
+
return None
|
|
411
|
+
|
|
412
|
+
err = c_void_p()
|
|
413
|
+
|
|
414
|
+
error = engine.startAndReturnError_(err)
|
|
415
|
+
if not error:
|
|
416
|
+
# print("Error starting engine")
|
|
417
|
+
return None
|
|
418
|
+
|
|
419
|
+
self.haptic_engine = HapticEngine(locality, engine)
|
|
420
|
+
return self.haptic_engine
|
|
421
|
+
|
|
422
|
+
def get_controls(self) -> list[Control]:
|
|
423
|
+
return list(self.controls.values())
|
|
424
|
+
|
|
425
|
+
def get_guid(self) -> str:
|
|
426
|
+
return "MFI_APPLE_CONTROLLER"
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class AppleController(Controller):
|
|
430
|
+
"""Javascript Gamepad Controller object that handles buttons and controls."""
|
|
431
|
+
device: AppleGamepad
|
|
432
|
+
|
|
433
|
+
def __init__(self, device: AppleGamepad, mapping: dict) -> None:
|
|
434
|
+
super().__init__(device, mapping)
|
|
435
|
+
self._last_updated = 0.0
|
|
436
|
+
|
|
437
|
+
def _initialize_controls(self) -> None:
|
|
438
|
+
for button_name in _button_mapping.values():
|
|
439
|
+
# Ignore any controls not mapped by pyglet...
|
|
440
|
+
if button_name not in self.device.controls:
|
|
441
|
+
continue
|
|
442
|
+
control = self.device.controls[button_name]
|
|
443
|
+
self.button_controls.append(control)
|
|
444
|
+
self._bind_button_control(control, button_name)
|
|
445
|
+
|
|
446
|
+
for axis_name in _axis_mapping.values():
|
|
447
|
+
control = self.device.controls[axis_name]
|
|
448
|
+
self.relative_axis_controls.append(control)
|
|
449
|
+
self._bind_axis_control(control, axis_name, Sign.DEFAULT)
|
|
450
|
+
|
|
451
|
+
def rumble_play_weak(self, strength: float = 1.0, duration: float = 0.5) -> None:
|
|
452
|
+
if self.device and self.device.weak_motor_engine:
|
|
453
|
+
strength_clamp = int(max(min(1.0, strength), 0))
|
|
454
|
+
self.device.weak_motor_engine.trigger_event(strength_clamp, sharpness=1.0, duration=duration)
|
|
455
|
+
|
|
456
|
+
def rumble_play_strong(self, strength: float = 1.0, duration: float = 0.5) -> None:
|
|
457
|
+
if self.device and self.device.strong_motor_engine:
|
|
458
|
+
strength_clamp = int(max(min(1.0, strength), 0))
|
|
459
|
+
self.device.strong_motor_engine.trigger_event(strength_clamp, sharpness=1.0, duration=duration)
|
|
460
|
+
|
|
461
|
+
def rumble_stop_weak(self) -> None:
|
|
462
|
+
if self.device and self.device.weak_motor_engine:
|
|
463
|
+
self.device.weak_motor_engine.stop_event()
|
|
464
|
+
|
|
465
|
+
def rumble_stop_strong(self) -> None:
|
|
466
|
+
if self.device and self.device.strong_motor_engine:
|
|
467
|
+
self.device.strong_motor_engine.stop_event()
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
class _AppleControllerManager_Implementation(EventDispatcher):
|
|
471
|
+
_PygletAppleControllerManager = ObjCSubclass('NSObject', '_PygletAppleControllerManager')
|
|
472
|
+
|
|
473
|
+
@_PygletAppleControllerManager.method(b'@' + PyObjectEncoding)
|
|
474
|
+
def initWithDispatcher(self, dispatcher: _SingletonAppleDispatcher):
|
|
475
|
+
self = ObjCInstance(send_super(self, 'init'))
|
|
476
|
+
if self is None:
|
|
477
|
+
return None
|
|
478
|
+
|
|
479
|
+
center = NSNotificationCenter.defaultCenter()
|
|
480
|
+
center.addObserver_selector_name_object_(
|
|
481
|
+
self,
|
|
482
|
+
get_selector("controllerConnected:"),
|
|
483
|
+
GCControllerDidConnectNotification,
|
|
484
|
+
None,
|
|
485
|
+
)
|
|
486
|
+
center.addObserver_selector_name_object_(
|
|
487
|
+
self,
|
|
488
|
+
get_selector("controllerDisconnected:"),
|
|
489
|
+
GCControllerDidDisconnectNotification,
|
|
490
|
+
None,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
controllers = {}
|
|
494
|
+
self.associate("controllers", controllers)
|
|
495
|
+
|
|
496
|
+
self.dispatcher = dispatcher
|
|
497
|
+
|
|
498
|
+
GCController.startWirelessControllerDiscoveryWithCompletionHandler_(None)
|
|
499
|
+
return self
|
|
500
|
+
|
|
501
|
+
@_PygletAppleControllerManager.method('v@')
|
|
502
|
+
def controllerConnected_(self, notification: NSNotification):
|
|
503
|
+
device: GCController = notification.object()
|
|
504
|
+
|
|
505
|
+
wrapper = AppleGamepad(self, device)
|
|
506
|
+
controller = AppleController(wrapper, {"name": wrapper.device_name, "guid": "MFI_APPLE_CONTROLLER"})
|
|
507
|
+
|
|
508
|
+
self.controllers[device] = controller
|
|
509
|
+
|
|
510
|
+
self.dispatcher.dispatch_event('on_connect', controller)
|
|
511
|
+
|
|
512
|
+
@_PygletAppleControllerManager.method('v@')
|
|
513
|
+
def controllerDisconnected_(self, notification: NSNotification):
|
|
514
|
+
device: GCController = notification.object()
|
|
515
|
+
|
|
516
|
+
if device in self.controllers:
|
|
517
|
+
self.dispatcher.dispatch_event('on_disconnect', self.controllers[device])
|
|
518
|
+
|
|
519
|
+
del self.controllers[device]
|
|
520
|
+
|
|
521
|
+
def get_controllers(self):
|
|
522
|
+
return list(self.controllers.values())
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
_PygletAppleControllerManager = ObjCClass('_PygletAppleControllerManager')
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
class _SingletonAppleDispatcher(EventDispatcher):
|
|
529
|
+
"""Only keep this as we only need one."""
|
|
530
|
+
|
|
531
|
+
def __init__(self):
|
|
532
|
+
self._obj_mgr = _PygletAppleControllerManager.alloc().initWithDispatcher(self)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
_SingletonAppleDispatcher.register_event_type("on_connect")
|
|
536
|
+
_SingletonAppleDispatcher.register_event_type("on_disconnect")
|
|
537
|
+
|
|
538
|
+
_apple_controller = _SingletonAppleDispatcher()
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
class AppleControllerManager(ControllerManager):
|
|
542
|
+
|
|
543
|
+
def __init__(self):
|
|
544
|
+
self._controllers = {}
|
|
545
|
+
|
|
546
|
+
@_apple_controller.event
|
|
547
|
+
def on_connect(controller):
|
|
548
|
+
pyglet.app.platform_event_loop.post_event(self, 'on_connect', controller)
|
|
549
|
+
|
|
550
|
+
@_apple_controller.event
|
|
551
|
+
def on_disconnect(controller):
|
|
552
|
+
pyglet.app.platform_event_loop.post_event(self, 'on_disconnect', controller)
|
|
553
|
+
|
|
554
|
+
def get_controllers(self):
|
|
555
|
+
return list(self._controllers.values())
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def get_controllers(display=None):
|
|
559
|
+
return []
|
pyglet/input/win32/__init__.py
CHANGED
|
@@ -20,7 +20,7 @@ except:
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
_xinput_enabled = False
|
|
23
|
-
if not pyglet.options
|
|
23
|
+
if not pyglet.options.win32_disable_xinput:
|
|
24
24
|
try:
|
|
25
25
|
from pyglet.input.win32.xinput import XInputControllerManager, XInputController, XInputDevice
|
|
26
26
|
from pyglet.input.win32.xinput import _device_manager as _xinput_device_manager
|