pyglet 2.1.6__py3-none-any.whl → 2.1.9__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 +27 -42
- pyglet/app/base.py +2 -2
- pyglet/clock.py +1 -1
- pyglet/display/base.py +31 -21
- pyglet/display/cocoa.py +25 -1
- pyglet/display/headless.py +12 -1
- pyglet/display/win32.py +134 -18
- pyglet/display/xlib.py +285 -70
- pyglet/event.py +17 -1
- pyglet/experimental/README.md +1 -1
- pyglet/experimental/jobs.py +1 -1
- pyglet/experimental/multitexture_sprite.py +2 -2
- pyglet/font/__init__.py +1 -1
- pyglet/font/base.py +8 -5
- pyglet/font/dwrite/__init__.py +13 -8
- pyglet/font/dwrite/dwrite_lib.py +1 -1
- pyglet/font/user.py +1 -1
- pyglet/gl/base.py +8 -4
- pyglet/gl/cocoa.py +4 -0
- pyglet/gl/gl.py +4 -3
- pyglet/gl/gl.pyi +2320 -0
- pyglet/gl/gl_compat.py +7 -18
- pyglet/gl/gl_compat.pyi +3097 -0
- pyglet/gl/xlib.py +24 -0
- pyglet/graphics/shader.py +8 -1
- pyglet/graphics/vertexbuffer.py +1 -1
- pyglet/gui/frame.py +2 -2
- pyglet/gui/widgets.py +1 -1
- pyglet/image/__init__.py +3 -3
- pyglet/image/buffer.py +3 -3
- pyglet/input/base.py +22 -12
- pyglet/input/linux/evdev.py +96 -23
- pyglet/input/linux/evdev_constants.py +2 -1
- pyglet/input/win32/xinput.py +6 -3
- pyglet/libs/darwin/cocoapy/cocoalibs.py +3 -1
- pyglet/libs/ioctl.py +2 -2
- pyglet/libs/win32/__init__.py +12 -0
- pyglet/libs/win32/constants.py +4 -0
- pyglet/libs/win32/types.py +97 -0
- pyglet/libs/x11/xrandr.py +166 -0
- pyglet/libs/x11/xrender.py +43 -0
- pyglet/libs/x11/xsync.py +43 -0
- pyglet/math.py +65 -54
- pyglet/media/buffered_logger.py +1 -1
- pyglet/media/codecs/ffmpeg.py +18 -34
- pyglet/media/codecs/gstreamer.py +3 -3
- pyglet/media/codecs/pyogg.py +1 -1
- pyglet/media/codecs/wave.py +6 -0
- pyglet/media/codecs/wmf.py +33 -7
- pyglet/media/devices/win32.py +1 -1
- pyglet/media/drivers/base.py +1 -1
- pyglet/media/drivers/directsound/interface.py +4 -0
- pyglet/media/drivers/listener.py +2 -2
- pyglet/media/drivers/xaudio2/interface.py +6 -2
- pyglet/media/drivers/xaudio2/lib_xaudio2.py +1 -1
- pyglet/media/instrumentation.py +2 -2
- pyglet/media/player.py +2 -2
- pyglet/media/player_worker_thread.py +1 -1
- pyglet/media/synthesis.py +1 -1
- pyglet/model/codecs/gltf.py +1 -1
- pyglet/shapes.py +1 -1
- pyglet/sprite.py +1 -1
- pyglet/text/caret.py +44 -5
- pyglet/text/layout/base.py +3 -3
- pyglet/util.py +1 -1
- pyglet/window/__init__.py +54 -14
- pyglet/window/cocoa/__init__.py +27 -0
- pyglet/window/mouse.py +11 -1
- pyglet/window/win32/__init__.py +40 -14
- pyglet/window/xlib/__init__.py +21 -7
- {pyglet-2.1.6.dist-info → pyglet-2.1.9.dist-info}/METADATA +1 -1
- {pyglet-2.1.6.dist-info → pyglet-2.1.9.dist-info}/RECORD +74 -70
- {pyglet-2.1.6.dist-info → pyglet-2.1.9.dist-info}/LICENSE +0 -0
- {pyglet-2.1.6.dist-info → pyglet-2.1.9.dist-info}/WHEEL +0 -0
pyglet/gl/xlib.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
+
from _ctypes import _Pointer
|
|
4
5
|
from ctypes import POINTER, byref, c_int, c_uint, cast
|
|
5
6
|
from typing import TYPE_CHECKING, NoReturn
|
|
6
7
|
|
|
@@ -13,6 +14,8 @@ from pyglet.gl import lib
|
|
|
13
14
|
from pyglet.gl.base import Config, DisplayConfig, Context
|
|
14
15
|
|
|
15
16
|
from pyglet.display.xlib import XlibCanvas
|
|
17
|
+
from pyglet.libs.x11 import xlib
|
|
18
|
+
from pyglet.libs.x11.xrender import XRenderFindVisualFormat
|
|
16
19
|
|
|
17
20
|
if TYPE_CHECKING:
|
|
18
21
|
from pyglet.libs.x11.xlib import Display
|
|
@@ -52,6 +55,10 @@ class XlibConfig(Config): # noqa: D101
|
|
|
52
55
|
|
|
53
56
|
result = [XlibDisplayConfig(canvas, info, c, self) for c in configs]
|
|
54
57
|
|
|
58
|
+
# If we intend to have a transparent framebuffer.
|
|
59
|
+
if self.transparent_framebuffer:
|
|
60
|
+
result = [fb_cf for fb_cf in result if fb_cf.transparent]
|
|
61
|
+
|
|
55
62
|
# Can't free array until all XlibGLConfig's are GC'd. Too much
|
|
56
63
|
# hassle, live with leak. XXX
|
|
57
64
|
# xlib.XFree(configs)
|
|
@@ -102,6 +109,7 @@ class XlibDisplayConfig(DisplayConfig): # noqa: D101
|
|
|
102
109
|
|
|
103
110
|
self.glx_info = info
|
|
104
111
|
self.fbconfig = fbconfig
|
|
112
|
+
self.transparent = False
|
|
105
113
|
|
|
106
114
|
for name, attr in self.attribute_ids.items():
|
|
107
115
|
value = c_int()
|
|
@@ -110,6 +118,22 @@ class XlibDisplayConfig(DisplayConfig): # noqa: D101
|
|
|
110
118
|
if result >= 0:
|
|
111
119
|
setattr(self, name, value.value)
|
|
112
120
|
|
|
121
|
+
# If user intends for a transparent framebuffer, the visual info needs to be
|
|
122
|
+
# queried for it. Even if a config supports alpha_size 8 and depth_size 32, there is no
|
|
123
|
+
# guarantee the visual info supports that same configuration.
|
|
124
|
+
if config.transparent_framebuffer:
|
|
125
|
+
xvi_ptr = glx.glXGetVisualFromFBConfig(canvas.display._display, self.fbconfig) # noqa: SLF001
|
|
126
|
+
if xvi_ptr:
|
|
127
|
+
self.transparent = self._is_visual_transparent(xvi_ptr.contents.visual) # noqa: SLF001
|
|
128
|
+
xlib.XFree(xvi_ptr)
|
|
129
|
+
|
|
130
|
+
def _is_visual_transparent(self, visual: _Pointer[xlib.Visual]) -> bool:
|
|
131
|
+
if not XRenderFindVisualFormat:
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
xrender_format = XRenderFindVisualFormat(self.canvas.display._display, visual)
|
|
135
|
+
return xrender_format and xrender_format.contents.direct.alphaMask != 0
|
|
136
|
+
|
|
113
137
|
def get_visual_info(self) -> glx.XVisualInfo:
|
|
114
138
|
return glx.glXGetVisualFromFBConfig(self.canvas.display._display, self.fbconfig).contents # noqa: SLF001
|
|
115
139
|
|
pyglet/graphics/shader.py
CHANGED
|
@@ -58,6 +58,7 @@ from pyglet.gl.gl import (
|
|
|
58
58
|
glUnmapBuffer,
|
|
59
59
|
glUseProgram,
|
|
60
60
|
glVertexAttribDivisor,
|
|
61
|
+
glVertexAttribIPointer,
|
|
61
62
|
glVertexAttribPointer,
|
|
62
63
|
)
|
|
63
64
|
from pyglet.graphics.vertexbuffer import AttributeBufferObject, BufferObject
|
|
@@ -257,6 +258,9 @@ class Attribute:
|
|
|
257
258
|
self.element_size = sizeof(self.c_type)
|
|
258
259
|
self.stride = count * self.element_size
|
|
259
260
|
|
|
261
|
+
self._is_int = gl_type in (gl.GL_INT, gl.GL_SHORT, gl.GL_BYTE, gl.GL_UNSIGNED_INT,
|
|
262
|
+
gl.GL_UNSIGNED_SHORT, gl.GL_UNSIGNED_BYTE) and self.normalize is False
|
|
263
|
+
|
|
260
264
|
def enable(self) -> None:
|
|
261
265
|
"""Enable the attribute."""
|
|
262
266
|
glEnableVertexAttribArray(self.location)
|
|
@@ -271,7 +275,10 @@ class Attribute:
|
|
|
271
275
|
Pointer offset to the currently bound buffer for this attribute.
|
|
272
276
|
|
|
273
277
|
"""
|
|
274
|
-
|
|
278
|
+
if self._is_int:
|
|
279
|
+
glVertexAttribIPointer(self.location, self.count, self.gl_type, self.stride, ptr)
|
|
280
|
+
else:
|
|
281
|
+
glVertexAttribPointer(self.location, self.count, self.gl_type, self.normalize, self.stride, ptr)
|
|
275
282
|
|
|
276
283
|
def set_divisor(self) -> None:
|
|
277
284
|
glVertexAttribDivisor(self.location, 1)
|
pyglet/graphics/vertexbuffer.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""OpenGL Buffer Objects.
|
|
2
2
|
|
|
3
|
-
:py:class:`~BufferObject` and a :py:class:`~BackedBufferObject` are
|
|
3
|
+
:py:class:`~BufferObject` and a :py:class:`~BackedBufferObject` are provided.
|
|
4
4
|
The first is a lightweight abstraction over an OpenGL buffer, as created
|
|
5
5
|
with ``glGenBuffers``. The backed buffer object is similar, but provides a
|
|
6
6
|
full mirror of the data in CPU memory. This allows for delayed uploading of
|
pyglet/gui/frame.py
CHANGED
|
@@ -24,7 +24,7 @@ class Frame:
|
|
|
24
24
|
|
|
25
25
|
Args:
|
|
26
26
|
window:
|
|
27
|
-
The SpatialHash will
|
|
27
|
+
The SpatialHash will receive events from this Window.
|
|
28
28
|
Appropriate events will be passed on to all added Widgets.
|
|
29
29
|
enable:
|
|
30
30
|
Whether to enable frame.
|
|
@@ -168,7 +168,7 @@ class MovableFrame(Frame):
|
|
|
168
168
|
|
|
169
169
|
Args:
|
|
170
170
|
window:
|
|
171
|
-
The SpatialHash will
|
|
171
|
+
The SpatialHash will receive events from this Window.
|
|
172
172
|
Appropriate events will be passed on to all added Widgets.
|
|
173
173
|
enable:
|
|
174
174
|
Whether to enable frame.
|
pyglet/gui/widgets.py
CHANGED
|
@@ -601,7 +601,7 @@ class TextEntry(WidgetBase):
|
|
|
601
601
|
self._caret.on_text_motion_select(motion)
|
|
602
602
|
|
|
603
603
|
def on_commit(self, widget: TextEntry, text: str) -> None:
|
|
604
|
-
"""Event: dispatches the current text when
|
|
604
|
+
"""Event: dispatches the current text when committed via Enter/Return key."""
|
|
605
605
|
|
|
606
606
|
|
|
607
607
|
TextEntry.register_event_type('on_commit')
|
pyglet/image/__init__.py
CHANGED
|
@@ -556,7 +556,7 @@ class ImageData(AbstractImage):
|
|
|
556
556
|
Note:
|
|
557
557
|
Conversion to another format is done on the CPU, and can be
|
|
558
558
|
somewhat costly for larger images. Consider performing conversion
|
|
559
|
-
at load time for framerate sensitive
|
|
559
|
+
at load time for framerate sensitive applications.
|
|
560
560
|
"""
|
|
561
561
|
fmt = fmt or self._desired_format
|
|
562
562
|
pitch = pitch or self._current_pitch
|
|
@@ -1201,7 +1201,7 @@ class Texture(AbstractImage):
|
|
|
1201
1201
|
layer: int = 0, access: int = GL_READ_WRITE, fmt: int = GL_RGBA32F):
|
|
1202
1202
|
"""Bind as an ImageTexture for use with a :py:class:`~pyglet.shader.ComputeShaderProgram`.
|
|
1203
1203
|
|
|
1204
|
-
.. note:: OpenGL 4.3, or 4.2 with the GL_ARB_compute_shader
|
|
1204
|
+
.. note:: OpenGL 4.3, or 4.2 with the GL_ARB_compute_shader extension is required.
|
|
1205
1205
|
"""
|
|
1206
1206
|
glBindImageTexture(unit, self.id, level, layered, layer, access, fmt)
|
|
1207
1207
|
|
|
@@ -1211,7 +1211,7 @@ class Texture(AbstractImage):
|
|
|
1211
1211
|
fmt: int = GL_RGBA, blank_data: bool = True) -> Texture:
|
|
1212
1212
|
"""Create a Texture
|
|
1213
1213
|
|
|
1214
|
-
Create a Texture with the specified
|
|
1214
|
+
Create a Texture with the specified dimensions, target and format.
|
|
1215
1215
|
On return, the texture will be bound.
|
|
1216
1216
|
|
|
1217
1217
|
Args:
|
pyglet/image/buffer.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""OpenGL Framebuffer abstractions.
|
|
2
2
|
|
|
3
3
|
This module provides classes for working with Framebuffers & Renderbuffers
|
|
4
|
-
and their attachments.
|
|
4
|
+
and their attachments. Attachments can be pyglet Texture objects, which allows
|
|
5
5
|
easily accessing their data, saving to disk, etc. Renderbuffers can be used
|
|
6
6
|
if you don't need to access their data at a later time. For example::
|
|
7
7
|
|
|
@@ -40,7 +40,7 @@ if TYPE_CHECKING:
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def get_max_color_attachments() -> int:
|
|
43
|
-
"""Get the maximum allow Framebuffer Color
|
|
43
|
+
"""Get the maximum allow Framebuffer Color attachments."""
|
|
44
44
|
number = GLint()
|
|
45
45
|
glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, number)
|
|
46
46
|
return number.value
|
|
@@ -144,7 +144,7 @@ class Framebuffer:
|
|
|
144
144
|
|
|
145
145
|
Unbind should be called to prevent further rendering
|
|
146
146
|
to the framebuffer, or if you wish to access data
|
|
147
|
-
from its Texture
|
|
147
|
+
from its Texture attachments.
|
|
148
148
|
"""
|
|
149
149
|
glBindFramebuffer(self.target, 0)
|
|
150
150
|
|
pyglet/input/base.py
CHANGED
|
@@ -71,7 +71,7 @@ class Device:
|
|
|
71
71
|
Args:
|
|
72
72
|
window:
|
|
73
73
|
Optional window to associate with the device. The behaviour
|
|
74
|
-
of this parameter is device and operating system
|
|
74
|
+
of this parameter is device and operating system dependent.
|
|
75
75
|
It can usually be omitted for most devices.
|
|
76
76
|
exclusive:
|
|
77
77
|
If ``True`` the device will be opened exclusively so that no
|
|
@@ -103,7 +103,7 @@ class Device:
|
|
|
103
103
|
string. This is generated from the hardware identifiers,
|
|
104
104
|
and is in the same format as was popularized by SDL2.
|
|
105
105
|
GUIDs differ between platforms, but are generally 32
|
|
106
|
-
|
|
106
|
+
hexadecimal characters.
|
|
107
107
|
"""
|
|
108
108
|
raise NotImplementedError('abstract')
|
|
109
109
|
|
|
@@ -506,7 +506,7 @@ class Controller(EventDispatcher):
|
|
|
506
506
|
"""
|
|
507
507
|
|
|
508
508
|
def __init__(self, device: Device, mapping: dict):
|
|
509
|
-
"""Create a Controller
|
|
509
|
+
"""Create a Controller instance mapped to a Device.
|
|
510
510
|
|
|
511
511
|
.. versionadded:: 2.0
|
|
512
512
|
"""
|
|
@@ -520,6 +520,7 @@ class Controller(EventDispatcher):
|
|
|
520
520
|
#: The unique guid for this Device
|
|
521
521
|
self.guid: str = mapping.get('guid')
|
|
522
522
|
|
|
523
|
+
# Pollable
|
|
523
524
|
self.a: bool = False
|
|
524
525
|
self.b: bool = False
|
|
525
526
|
self.x: bool = False
|
|
@@ -534,6 +535,10 @@ class Controller(EventDispatcher):
|
|
|
534
535
|
|
|
535
536
|
self.lefttrigger: float = 0.0
|
|
536
537
|
self.righttrigger: float = 0.0
|
|
538
|
+
self.dpad: Vec2 = Vec2()
|
|
539
|
+
self.leftanalog: Vec2 = Vec2()
|
|
540
|
+
self.rightanalog: Vec2 = Vec2()
|
|
541
|
+
|
|
537
542
|
self.leftx: float = 0.0
|
|
538
543
|
self.lefty: float = 0.0
|
|
539
544
|
self.rightx: float = 0.0
|
|
@@ -575,7 +580,7 @@ class Controller(EventDispatcher):
|
|
|
575
580
|
A string, currently one of "PS", "XB", or "GENERIC".
|
|
576
581
|
"""
|
|
577
582
|
product_id = None
|
|
578
|
-
# TODO: add more checks for
|
|
583
|
+
# TODO: add more checks for vendor hardware ids.
|
|
579
584
|
|
|
580
585
|
# Windows
|
|
581
586
|
if self.name == 'XINPUTCONTROLLER':
|
|
@@ -617,12 +622,14 @@ class Controller(EventDispatcher):
|
|
|
617
622
|
@control.event
|
|
618
623
|
def on_change(value):
|
|
619
624
|
self.dpady = round(value * scale + bias) * sign # normalized
|
|
625
|
+
self.dpad = Vec2(self.dpadx, self.dpady)
|
|
620
626
|
self.dispatch_event('on_dpad_motion', self, Vec2(self.dpadx, self.dpady))
|
|
621
627
|
|
|
622
628
|
elif axis_name in ("dpleft", "dpright"):
|
|
623
629
|
@control.event
|
|
624
630
|
def on_change(value):
|
|
625
631
|
self.dpadx = round(value * scale + bias) * sign # normalized
|
|
632
|
+
self.dpad = Vec2(self.dpadx, self.dpady)
|
|
626
633
|
self.dispatch_event('on_dpad_motion', self, Vec2(self.dpadx, self.dpady))
|
|
627
634
|
|
|
628
635
|
elif axis_name in ("lefttrigger", "righttrigger"):
|
|
@@ -637,14 +644,16 @@ class Controller(EventDispatcher):
|
|
|
637
644
|
def on_change(value):
|
|
638
645
|
normalized_value = value * scale + bias
|
|
639
646
|
setattr(self, axis_name, normalized_value)
|
|
640
|
-
self.
|
|
647
|
+
self.left_analog = Vec2(self.leftx, -self.lefty)
|
|
648
|
+
self.dispatch_event('on_stick_motion', self, "leftstick", self.left_analog)
|
|
641
649
|
|
|
642
650
|
elif axis_name in ("rightx", "righty"):
|
|
643
651
|
@control.event
|
|
644
652
|
def on_change(value):
|
|
645
653
|
normalized_value = value * scale + bias
|
|
646
654
|
setattr(self, axis_name, normalized_value)
|
|
647
|
-
self.
|
|
655
|
+
self.right_analog = Vec2(self.rightx, -self.righty)
|
|
656
|
+
self.dispatch_event('on_stick_motion', self, "rightstick", self.right_analog)
|
|
648
657
|
|
|
649
658
|
def _bind_button_control(self, relation: Relation, control: Button, button_name: str) -> None:
|
|
650
659
|
if button_name in ("dpleft", "dpright", "dpup", "dpdown"):
|
|
@@ -655,7 +664,8 @@ class Controller(EventDispatcher):
|
|
|
655
664
|
def on_change(value):
|
|
656
665
|
target, bias = defaults[button_name]
|
|
657
666
|
setattr(self, target, bias * value)
|
|
658
|
-
self.
|
|
667
|
+
self.dpad = Vec2(self.dpadx, self.dpady)
|
|
668
|
+
self.dispatch_event('on_dpad_motion', self, self.dpad)
|
|
659
669
|
else:
|
|
660
670
|
@control.event
|
|
661
671
|
def on_change(value):
|
|
@@ -672,7 +682,7 @@ class Controller(EventDispatcher):
|
|
|
672
682
|
def _bind_dedicated_hat(self, relation: Relation, control: AbsoluteAxis) -> None:
|
|
673
683
|
# 8-directional hat encoded as a single control (Windows/Mac)
|
|
674
684
|
_vecs = (Vec2(0.0, 1.0), Vec2(1.0, 1.0), Vec2(1.0, 0.0), Vec2(1.0, -1.0), # n, ne, e, se
|
|
675
|
-
|
|
685
|
+
Vec2(0.0, -1.0), Vec2(-1.0, -1.0), Vec2(-1.0, 0.0), Vec2(-1.0, 1.0)) # s, sw, w, nw
|
|
676
686
|
_input_map = {key: val for key, val in zip(range(int(control.min), int(control.max + 1)), _vecs)}
|
|
677
687
|
|
|
678
688
|
# For some Directinput devices:
|
|
@@ -681,7 +691,7 @@ class Controller(EventDispatcher):
|
|
|
681
691
|
@control.event
|
|
682
692
|
def on_change(value):
|
|
683
693
|
vector = _input_map.get(value // _scale, Vec2(0.0, 0.0))
|
|
684
|
-
self.
|
|
694
|
+
self.dpad = vector
|
|
685
695
|
self.dispatch_event('on_dpad_motion', self, vector)
|
|
686
696
|
|
|
687
697
|
def _initialize_controls(self) -> None:
|
|
@@ -730,8 +740,8 @@ class Controller(EventDispatcher):
|
|
|
730
740
|
|
|
731
741
|
self._bind_axis_control(relation, control, dpname)
|
|
732
742
|
|
|
733
|
-
except IndexError:
|
|
734
|
-
warnings.warn(f"Could not map '{relation}' to '{name}'")
|
|
743
|
+
except (IndexError, AttributeError, KeyError):
|
|
744
|
+
warnings.warn(f"Could not map physical Control '{relation}' to '{name}'")
|
|
735
745
|
continue
|
|
736
746
|
|
|
737
747
|
def open(self, window: None | BaseWindow = None, exclusive: bool = False) -> None:
|
|
@@ -1096,7 +1106,7 @@ class ControllerManager(EventDispatcher):
|
|
|
1096
1106
|
|
|
1097
1107
|
def on_connect(self, controller) -> Controller:
|
|
1098
1108
|
"""A Controller has been connected. If this is
|
|
1099
|
-
a previously
|
|
1109
|
+
a previously disconnected Controller that is
|
|
1100
1110
|
being re-connected, the same Controller instance
|
|
1101
1111
|
will be returned.
|
|
1102
1112
|
"""
|
pyglet/input/linux/evdev.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
import time
|
|
5
5
|
import ctypes
|
|
6
|
+
import select
|
|
6
7
|
import warnings
|
|
7
8
|
import threading
|
|
8
9
|
|
|
@@ -11,6 +12,7 @@ from ctypes import c_int16 as _s16
|
|
|
11
12
|
from ctypes import c_uint32 as _u32
|
|
12
13
|
from ctypes import c_int32 as _s32
|
|
13
14
|
from ctypes import c_int64 as _s64
|
|
15
|
+
from ctypes import c_byte as _c_byte
|
|
14
16
|
|
|
15
17
|
import pyglet
|
|
16
18
|
|
|
@@ -31,6 +33,24 @@ except ImportError:
|
|
|
31
33
|
return c.read(fd, buffers, 3072)
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
KeyMaxArray = _c_byte * ((KEY_MAX // 8) + 1)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class EvdevButton(Button):
|
|
40
|
+
event_type: int
|
|
41
|
+
event_code: int
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class EvdevAbsoluteAxis(AbsoluteAxis):
|
|
45
|
+
event_type: int
|
|
46
|
+
event_code: int
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class EvdevRelativeAxis(RelativeAxis):
|
|
50
|
+
event_type: int
|
|
51
|
+
event_code: int
|
|
52
|
+
|
|
53
|
+
|
|
34
54
|
# Structures from /linux/blob/master/include/uapi/linux/input.h
|
|
35
55
|
|
|
36
56
|
class Timeval(ctypes.Structure):
|
|
@@ -158,11 +178,13 @@ class FFEvent(ctypes.Structure):
|
|
|
158
178
|
)
|
|
159
179
|
|
|
160
180
|
|
|
181
|
+
# Helper "macros" for file io:
|
|
161
182
|
EVIOCGVERSION = _IOR('E', 0x01, ctypes.c_int)
|
|
162
183
|
EVIOCGID = _IOR('E', 0x02, InputID)
|
|
163
184
|
EVIOCGNAME = _IOR_str('E', 0x06)
|
|
164
185
|
EVIOCGPHYS = _IOR_str('E', 0x07)
|
|
165
186
|
EVIOCGUNIQ = _IOR_str('E', 0x08)
|
|
187
|
+
EVIOCGKEY = _IOR_len('E', 0x18)
|
|
166
188
|
EVIOCSFF = _IOW('E', 0x80, FFEvent)
|
|
167
189
|
|
|
168
190
|
|
|
@@ -170,9 +192,14 @@ def EVIOCGBIT(fileno, ev, buffer):
|
|
|
170
192
|
return _IOR_len('E', 0x20 + ev)(fileno, buffer)
|
|
171
193
|
|
|
172
194
|
|
|
173
|
-
def EVIOCGABS(fileno,
|
|
174
|
-
|
|
175
|
-
return _IOR_len('E', 0x40 +
|
|
195
|
+
def EVIOCGABS(fileno, ev, buffer=InputABSInfo()):
|
|
196
|
+
print("absbuffer instance:", buffer)
|
|
197
|
+
return _IOR_len('E', 0x40 + ev)(fileno, buffer)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def get_key_state(fileno, event_code, buffer=KeyMaxArray()):
|
|
201
|
+
buffer = EVIOCGKEY(fileno, buffer)
|
|
202
|
+
return bool(buffer[event_code // 8] & (1 << (event_code % 8)))
|
|
176
203
|
|
|
177
204
|
|
|
178
205
|
def get_set_bits(bytestring):
|
|
@@ -217,18 +244,16 @@ def _create_control(fileno, event_type, event_code):
|
|
|
217
244
|
value = absinfo.value
|
|
218
245
|
minimum = absinfo.minimum
|
|
219
246
|
maximum = absinfo.maximum
|
|
220
|
-
control =
|
|
247
|
+
control = EvdevAbsoluteAxis(name, minimum, maximum, raw_name, inverted=name == 'hat_y')
|
|
221
248
|
control.value = value
|
|
222
|
-
if name == 'hat_y':
|
|
223
|
-
control.inverted = True
|
|
224
249
|
elif event_type == EV_REL:
|
|
225
250
|
raw_name = rel_raw_names.get(event_code, f'EV_REL({event_code:x})')
|
|
226
251
|
name = _rel_names.get(event_code)
|
|
227
|
-
control =
|
|
252
|
+
control = EvdevRelativeAxis(name, raw_name)
|
|
228
253
|
elif event_type == EV_KEY:
|
|
229
254
|
raw_name = key_raw_names.get(event_code, f'EV_KEY({event_code:x})')
|
|
230
255
|
name = None
|
|
231
|
-
control =
|
|
256
|
+
control = EvdevButton(name, raw_name)
|
|
232
257
|
else:
|
|
233
258
|
return None
|
|
234
259
|
control.event_type = event_type
|
|
@@ -248,7 +273,8 @@ event_types = {
|
|
|
248
273
|
|
|
249
274
|
|
|
250
275
|
class EvdevDevice(XlibSelectDevice, Device):
|
|
251
|
-
_fileno
|
|
276
|
+
_fileno: int | None
|
|
277
|
+
_poll: "select.poll | None"
|
|
252
278
|
|
|
253
279
|
def __init__(self, display, filename):
|
|
254
280
|
self._filename = filename
|
|
@@ -301,11 +327,14 @@ class EvdevDevice(XlibSelectDevice, Device):
|
|
|
301
327
|
self.control_map[(event_type, event_code)] = control
|
|
302
328
|
self.controls.append(control)
|
|
303
329
|
|
|
304
|
-
self.controls.sort(key=lambda
|
|
330
|
+
self.controls.sort(key=lambda ctrl: ctrl.event_code)
|
|
305
331
|
os.close(fileno)
|
|
306
332
|
|
|
333
|
+
self._poll = select.poll()
|
|
307
334
|
self._event_size = ctypes.sizeof(InputEvent)
|
|
308
335
|
self._event_buffer = (InputEvent * 64)()
|
|
336
|
+
self._syn_dropped = False
|
|
337
|
+
self._event_queue = []
|
|
309
338
|
|
|
310
339
|
super().__init__(display, name)
|
|
311
340
|
|
|
@@ -321,6 +350,7 @@ class EvdevDevice(XlibSelectDevice, Device):
|
|
|
321
350
|
def open(self, window=None, exclusive=False):
|
|
322
351
|
try:
|
|
323
352
|
self._fileno = os.open(self._filename, os.O_RDWR | os.O_NONBLOCK)
|
|
353
|
+
self._poll.register(self._fileno, select.POLLIN | select.POLLPRI)
|
|
324
354
|
except OSError as e:
|
|
325
355
|
raise DeviceOpenException(e)
|
|
326
356
|
|
|
@@ -333,6 +363,9 @@ class EvdevDevice(XlibSelectDevice, Device):
|
|
|
333
363
|
if not self._fileno:
|
|
334
364
|
return
|
|
335
365
|
|
|
366
|
+
if self._poll:
|
|
367
|
+
self._poll.unregister(self._fileno)
|
|
368
|
+
|
|
336
369
|
pyglet.app.platform_event_loop.select_devices.remove(self)
|
|
337
370
|
os.close(self._fileno)
|
|
338
371
|
self._fileno = None
|
|
@@ -340,6 +373,20 @@ class EvdevDevice(XlibSelectDevice, Device):
|
|
|
340
373
|
def get_controls(self):
|
|
341
374
|
return self.controls
|
|
342
375
|
|
|
376
|
+
def _resync_control_state(self):
|
|
377
|
+
"""Manually resync all Control state.
|
|
378
|
+
|
|
379
|
+
This method queries and resets the state of each Control using the appropriate
|
|
380
|
+
ioctl calls. If this causes the Control value to change, the associated events
|
|
381
|
+
will be dispatched. This is a somewhat expensive operation, but it is necessary
|
|
382
|
+
to perform in some cases (such as when a SYN_DROPPED event is received).
|
|
383
|
+
"""
|
|
384
|
+
for control in self.control_map.values():
|
|
385
|
+
if isinstance(control, EvdevButton):
|
|
386
|
+
control.value = get_key_state(self._fileno, control.event_code)
|
|
387
|
+
if isinstance(control, EvdevAbsoluteAxis):
|
|
388
|
+
control.value = EVIOCGABS(self._fileno, control.event_code).value
|
|
389
|
+
|
|
343
390
|
# Force Feedback methods
|
|
344
391
|
|
|
345
392
|
def ff_upload_effect(self, structure):
|
|
@@ -351,25 +398,51 @@ class EvdevDevice(XlibSelectDevice, Device):
|
|
|
351
398
|
return self._fileno
|
|
352
399
|
|
|
353
400
|
def poll(self):
|
|
354
|
-
return False
|
|
401
|
+
return True if self._poll.poll(0) else False
|
|
355
402
|
|
|
356
403
|
def select(self):
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
404
|
+
"""When the file descriptor is ready, read and process InputEvents.
|
|
405
|
+
|
|
406
|
+
This method has the following behavior:
|
|
407
|
+
- Read and queue all incoming input events.
|
|
408
|
+
- When a SYN_REPORT event is received, dispatch all queued events.
|
|
409
|
+
- If a SYN_DROPPED event is received, set a flag. When the next
|
|
410
|
+
SYN_REPORT event appears, drop all queued events & manually resync
|
|
411
|
+
all Control state.
|
|
412
|
+
"""
|
|
360
413
|
try:
|
|
361
414
|
bytes_read = _readv(self._fileno, self._event_buffer)
|
|
415
|
+
n_events = bytes_read // self._event_size
|
|
362
416
|
except OSError:
|
|
363
417
|
self.close()
|
|
364
418
|
return
|
|
365
419
|
|
|
366
|
-
n_events = bytes_read // self._event_size
|
|
367
|
-
|
|
368
420
|
for event in self._event_buffer[:n_events]:
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
421
|
+
|
|
422
|
+
# Mark the current chain of events as invalid and continue:
|
|
423
|
+
if (event.type, event.code) == (EV_SYN, SYN_DROPPED):
|
|
424
|
+
self._syn_dropped = True
|
|
425
|
+
continue
|
|
426
|
+
|
|
427
|
+
# Dispatch queued events when SYN_REPORT comes in:
|
|
428
|
+
if (event.type, event.code) == (EV_SYN, SYN_REPORT):
|
|
429
|
+
|
|
430
|
+
# Unless a SYN_DROPPED event has been received,
|
|
431
|
+
# in which case discard all queued events and resync:
|
|
432
|
+
if self._syn_dropped:
|
|
433
|
+
self._event_queue.clear()
|
|
434
|
+
self._syn_dropped = False
|
|
435
|
+
self._resync_control_state()
|
|
436
|
+
|
|
437
|
+
# Dispatch all queued events, then clear the queue:
|
|
438
|
+
for queued_event in self._event_queue:
|
|
439
|
+
if control := self.control_map.get((queued_event.type, queued_event.code)):
|
|
440
|
+
control.value = queued_event.value
|
|
441
|
+
self._event_queue.clear()
|
|
442
|
+
|
|
443
|
+
# This is not a SYN_REPORT or SYN_DROPPED event, so it is probably
|
|
444
|
+
# an input event. Queue it until the next SYN_REPORT event comes in:
|
|
445
|
+
self._event_queue.append(event)
|
|
373
446
|
|
|
374
447
|
|
|
375
448
|
class FFController(Controller):
|
|
@@ -471,7 +544,7 @@ class EvdevControllerManager(ControllerManager, XlibSelectDevice):
|
|
|
471
544
|
if controller := self._controllers.get(name, _create_controller(device)):
|
|
472
545
|
self._controllers[name] = controller
|
|
473
546
|
# Dispatch event in main thread:
|
|
474
|
-
|
|
547
|
+
self.post_event('on_connect', controller)
|
|
475
548
|
|
|
476
549
|
def select(self):
|
|
477
550
|
"""Triggered whenever the devices_file changes."""
|
|
@@ -551,8 +624,8 @@ def _detect_controller_mapping(device):
|
|
|
551
624
|
ABS_Z: 'lefttrigger', ABS_RZ: 'righttrigger',
|
|
552
625
|
ABS_X: 'leftx', ABS_Y: 'lefty', ABS_RX: 'rightx', ABS_RY: 'righty'}
|
|
553
626
|
|
|
554
|
-
button_controls = [control for control in device.controls if isinstance(control,
|
|
555
|
-
axis_controls = [control for control in device.controls if isinstance(control,
|
|
627
|
+
button_controls = [control for control in device.controls if isinstance(control, EvdevButton)]
|
|
628
|
+
axis_controls = [control for control in device.controls if isinstance(control, EvdevAbsoluteAxis)]
|
|
556
629
|
hat_controls = [control for control in device.controls if control.name in ('hat_x', 'hat_y')]
|
|
557
630
|
|
|
558
631
|
for i, control in enumerate(button_controls):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Event constants from /usr/include/linux/input.h
|
|
1
|
+
"""Event constants from /usr/include/linux/input-event-codes.h"""
|
|
2
2
|
|
|
3
3
|
EV_SYN = 0x00
|
|
4
4
|
EV_KEY = 0x01
|
|
@@ -17,6 +17,7 @@ EV_MAX = 0x1f
|
|
|
17
17
|
|
|
18
18
|
SYN_REPORT = 0
|
|
19
19
|
SYN_CONFIG = 1
|
|
20
|
+
SYN_DROPPED = 3
|
|
20
21
|
|
|
21
22
|
# Keys and buttons
|
|
22
23
|
|
pyglet/input/win32/xinput.py
CHANGED
|
@@ -575,14 +575,16 @@ class XInputController(Controller):
|
|
|
575
575
|
def on_change(value):
|
|
576
576
|
normalized_value = value * scale + bias
|
|
577
577
|
setattr(self, name, normalized_value)
|
|
578
|
-
self.
|
|
578
|
+
self.leftanalog = Vec2(self.leftx, self.lefty)
|
|
579
|
+
self.dispatch_event('on_stick_motion', self, "leftstick", self.leftanalog)
|
|
579
580
|
|
|
580
581
|
elif name in ("rightx", "righty"):
|
|
581
582
|
@control.event
|
|
582
583
|
def on_change(value):
|
|
583
584
|
normalized_value = value * scale + bias
|
|
584
585
|
setattr(self, name, normalized_value)
|
|
585
|
-
self.
|
|
586
|
+
self.rightanalog = Vec2(self.rightx, self.righty)
|
|
587
|
+
self.dispatch_event('on_stick_motion', self, "rightstick", self.rightanalog)
|
|
586
588
|
|
|
587
589
|
def _add_button(self, control, name):
|
|
588
590
|
|
|
@@ -592,7 +594,8 @@ class XInputController(Controller):
|
|
|
592
594
|
target, bias = {'dpleft': ('dpadx', -1.0), 'dpright': ('dpadx', 1.0),
|
|
593
595
|
'dpdown': ('dpady', -1.0), 'dpup': ('dpady', 1.0)}[name]
|
|
594
596
|
setattr(self, target, bias * value)
|
|
595
|
-
self.
|
|
597
|
+
self.dpad = Vec2(self.dpadx, self.dpady)
|
|
598
|
+
self.dispatch_event('on_dpad_motion', self, self.dpad)
|
|
596
599
|
else:
|
|
597
600
|
@control.event
|
|
598
601
|
def on_change(value):
|
|
@@ -232,6 +232,8 @@ NSDeviceSize = c_void_p.in_dll(appkit, 'NSDeviceSize')
|
|
|
232
232
|
NSDeviceResolution = c_void_p.in_dll(appkit, 'NSDeviceResolution')
|
|
233
233
|
NSDragOperationGeneric = 4
|
|
234
234
|
|
|
235
|
+
NSStatusWindowLevel = 25
|
|
236
|
+
|
|
235
237
|
|
|
236
238
|
# /System/Library/Frameworks/AppKit.framework/Headers/NSEvent.h
|
|
237
239
|
NSAnyEventMask = 0xFFFFFFFF # NSUIntegerMax
|
|
@@ -336,7 +338,7 @@ NSOpenGLProfileVersion3_2Core = 0x3200 # choose an OpenGL 3.2 Core Implementa
|
|
|
336
338
|
NSOpenGLProfileVersion4_1Core = 0x4100 # choose an OpenGL 4.1 Core Implementation
|
|
337
339
|
|
|
338
340
|
NSOpenGLCPSwapInterval = 222
|
|
339
|
-
|
|
341
|
+
NSOpenGLCPSurfaceOpacity = 236
|
|
340
342
|
|
|
341
343
|
# /System/Library/Frameworks/ApplicationServices.framework/Frameworks/...
|
|
342
344
|
# CoreGraphics.framework/Headers/CGImage.h
|
pyglet/libs/ioctl.py
CHANGED
|
@@ -41,7 +41,7 @@ from typing import TYPE_CHECKING
|
|
|
41
41
|
if TYPE_CHECKING:
|
|
42
42
|
from ctypes import Structure, c_int, c_uint
|
|
43
43
|
from typing import Callable, Union
|
|
44
|
-
c_data =
|
|
44
|
+
c_data = type[Structure] | type[c_int] | type[c_uint]
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
_IOC_NRBITS = 8
|
|
@@ -64,7 +64,7 @@ _IOC_READ = 2
|
|
|
64
64
|
# 'code' instead of 'type' to indicate the ioctl "magic number" ('H', 'E', etc.).
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def _IOC(io_dir: _IOC_NONE
|
|
67
|
+
def _IOC(io_dir: Union[_IOC_NONE, _IOC_READ, _IOC_WRITE], code: int, nr: int, size: int) -> int:
|
|
68
68
|
return (io_dir << _IOC_DIRSHIFT) | (code << _IOC_TYPESHIFT) | (nr << _IOC_NRSHIFT) | (size << _IOC_SIZESHIFT)
|
|
69
69
|
|
|
70
70
|
|