pyglet 2.1.8__py3-none-any.whl → 2.1.10__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 +3 -3
- pyglet/app/cocoa.py +2 -0
- pyglet/display/headless.py +11 -0
- pyglet/graphics/shader.py +9 -2
- pyglet/input/base.py +14 -4
- pyglet/input/linux/evdev.py +99 -25
- pyglet/input/linux/evdev_constants.py +2 -1
- pyglet/input/win32/xinput.py +6 -3
- pyglet/libs/ioctl.py +2 -2
- pyglet/math.py +25 -5
- pyglet/media/codecs/base.py +23 -32
- pyglet/media/codecs/ffmpeg.py +16 -20
- pyglet/media/codecs/ffmpeg_lib/compat.py +1 -0
- pyglet/media/codecs/ffmpeg_lib/libavcodec.py +20 -16
- pyglet/media/codecs/ffmpeg_lib/libavformat.py +6 -6
- pyglet/media/codecs/ffmpeg_lib/libavutil.py +22 -7
- pyglet/media/codecs/ffmpeg_lib/libswresample.py +2 -2
- pyglet/media/codecs/ffmpeg_lib/libswscale.py +2 -2
- pyglet/text/__init__.py +2 -2
- pyglet/text/document.py +2 -2
- pyglet/text/layout/base.py +12 -9
- pyglet/window/__init__.py +3 -2
- pyglet/window/cocoa/__init__.py +9 -9
- pyglet/window/headless/__init__.py +1 -1
- pyglet/window/win32/__init__.py +1 -1
- pyglet/window/xlib/__init__.py +1 -1
- {pyglet-2.1.8.dist-info → pyglet-2.1.10.dist-info}/METADATA +1 -1
- {pyglet-2.1.8.dist-info → pyglet-2.1.10.dist-info}/RECORD +30 -30
- {pyglet-2.1.8.dist-info → pyglet-2.1.10.dist-info}/LICENSE +0 -0
- {pyglet-2.1.8.dist-info → pyglet-2.1.10.dist-info}/WHEEL +0 -0
pyglet/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
|
|
15
15
|
from typing import Any, Callable, ItemsView, Sized
|
|
16
16
|
|
|
17
17
|
#: The release version
|
|
18
|
-
version = '2.1.
|
|
18
|
+
version = '2.1.10'
|
|
19
19
|
__version__ = version
|
|
20
20
|
|
|
21
21
|
MIN_PYTHON_VERSION = 3, 8
|
|
@@ -255,8 +255,8 @@ class Options:
|
|
|
255
255
|
|
|
256
256
|
.. versionadded:: 2.0.5"""
|
|
257
257
|
|
|
258
|
-
dpi_scaling: Literal["real", "scaled", "stretch", "platform"] = "
|
|
259
|
-
"""For 'HiDPI' displays, Window behavior can differ between operating systems. Defaults to `'
|
|
258
|
+
dpi_scaling: Literal["real", "scaled", "stretch", "platform"] = "platform"
|
|
259
|
+
"""For 'HiDPI' displays, Window behavior can differ between operating systems. Defaults to `'platform'`.
|
|
260
260
|
|
|
261
261
|
The current options are an attempt to create consistent behavior across all of the operating systems.
|
|
262
262
|
|
pyglet/app/cocoa.py
CHANGED
pyglet/display/headless.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
1
5
|
import pyglet
|
|
2
6
|
import warnings
|
|
3
7
|
|
|
@@ -69,3 +73,10 @@ class HeadlessScreen(Screen):
|
|
|
69
73
|
|
|
70
74
|
def restore_mode(self):
|
|
71
75
|
pass
|
|
76
|
+
|
|
77
|
+
def get_display_id(self) -> str | int:
|
|
78
|
+
# No real unique ID is available, just hash together the properties.
|
|
79
|
+
return hash((self.x, self.y, self.width, self.height))
|
|
80
|
+
|
|
81
|
+
def get_monitor_name(self) -> str | Literal["Unknown"]:
|
|
82
|
+
return "Headless"
|
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
|
|
@@ -158,7 +159,7 @@ _uniform_setters: dict[int, tuple[GLDataType, GLFunc, GLFunc, int]] = {
|
|
|
158
159
|
gl.GL_UNSIGNED_INT_SAMPLER_3D: (gl.GLint, gl.glUniform1iv, gl.glProgramUniform1iv, 1),
|
|
159
160
|
|
|
160
161
|
gl.GL_FLOAT_MAT2: (gl.GLfloat, gl.glUniformMatrix2fv, gl.glProgramUniformMatrix2fv, 4),
|
|
161
|
-
gl.GL_FLOAT_MAT3: (gl.GLfloat, gl.glUniformMatrix3fv, gl.glProgramUniformMatrix3fv,
|
|
162
|
+
gl.GL_FLOAT_MAT3: (gl.GLfloat, gl.glUniformMatrix3fv, gl.glProgramUniformMatrix3fv, 9),
|
|
162
163
|
gl.GL_FLOAT_MAT4: (gl.GLfloat, gl.glUniformMatrix4fv, gl.glProgramUniformMatrix4fv, 16),
|
|
163
164
|
|
|
164
165
|
# TODO: test/implement these:
|
|
@@ -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/input/base.py
CHANGED
|
@@ -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
|
|
@@ -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):
|
|
@@ -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:
|
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,26 @@ 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 _EvdevInfo:
|
|
40
|
+
event_type: int
|
|
41
|
+
event_code: int
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class EvdevButton(Button, _EvdevInfo):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class EvdevAbsoluteAxis(AbsoluteAxis, _EvdevInfo):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class EvdevRelativeAxis(RelativeAxis, _EvdevInfo):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
34
56
|
# Structures from /linux/blob/master/include/uapi/linux/input.h
|
|
35
57
|
|
|
36
58
|
class Timeval(ctypes.Structure):
|
|
@@ -158,11 +180,13 @@ class FFEvent(ctypes.Structure):
|
|
|
158
180
|
)
|
|
159
181
|
|
|
160
182
|
|
|
183
|
+
# Helper "macros" for file io:
|
|
161
184
|
EVIOCGVERSION = _IOR('E', 0x01, ctypes.c_int)
|
|
162
185
|
EVIOCGID = _IOR('E', 0x02, InputID)
|
|
163
186
|
EVIOCGNAME = _IOR_str('E', 0x06)
|
|
164
187
|
EVIOCGPHYS = _IOR_str('E', 0x07)
|
|
165
188
|
EVIOCGUNIQ = _IOR_str('E', 0x08)
|
|
189
|
+
EVIOCGKEY = _IOR_len('E', 0x18)
|
|
166
190
|
EVIOCSFF = _IOW('E', 0x80, FFEvent)
|
|
167
191
|
|
|
168
192
|
|
|
@@ -170,9 +194,13 @@ def EVIOCGBIT(fileno, ev, buffer):
|
|
|
170
194
|
return _IOR_len('E', 0x20 + ev)(fileno, buffer)
|
|
171
195
|
|
|
172
196
|
|
|
173
|
-
def EVIOCGABS(fileno,
|
|
174
|
-
|
|
175
|
-
|
|
197
|
+
def EVIOCGABS(fileno, ev, buffer=InputABSInfo()):
|
|
198
|
+
return _IOR_len('E', 0x40 + ev)(fileno, buffer)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def get_key_state(fileno, event_code, buffer=KeyMaxArray()):
|
|
202
|
+
buffer = EVIOCGKEY(fileno, buffer)
|
|
203
|
+
return bool(buffer[event_code // 8] & (1 << (event_code % 8)))
|
|
176
204
|
|
|
177
205
|
|
|
178
206
|
def get_set_bits(bytestring):
|
|
@@ -217,18 +245,16 @@ def _create_control(fileno, event_type, event_code):
|
|
|
217
245
|
value = absinfo.value
|
|
218
246
|
minimum = absinfo.minimum
|
|
219
247
|
maximum = absinfo.maximum
|
|
220
|
-
control =
|
|
248
|
+
control = EvdevAbsoluteAxis(name, minimum, maximum, raw_name, inverted=name == 'hat_y')
|
|
221
249
|
control.value = value
|
|
222
|
-
if name == 'hat_y':
|
|
223
|
-
control.inverted = True
|
|
224
250
|
elif event_type == EV_REL:
|
|
225
251
|
raw_name = rel_raw_names.get(event_code, f'EV_REL({event_code:x})')
|
|
226
252
|
name = _rel_names.get(event_code)
|
|
227
|
-
control =
|
|
253
|
+
control = EvdevRelativeAxis(name, raw_name)
|
|
228
254
|
elif event_type == EV_KEY:
|
|
229
255
|
raw_name = key_raw_names.get(event_code, f'EV_KEY({event_code:x})')
|
|
230
256
|
name = None
|
|
231
|
-
control =
|
|
257
|
+
control = EvdevButton(name, raw_name)
|
|
232
258
|
else:
|
|
233
259
|
return None
|
|
234
260
|
control.event_type = event_type
|
|
@@ -248,7 +274,8 @@ event_types = {
|
|
|
248
274
|
|
|
249
275
|
|
|
250
276
|
class EvdevDevice(XlibSelectDevice, Device):
|
|
251
|
-
_fileno
|
|
277
|
+
_fileno: int | None
|
|
278
|
+
_poll: "select.poll | None"
|
|
252
279
|
|
|
253
280
|
def __init__(self, display, filename):
|
|
254
281
|
self._filename = filename
|
|
@@ -301,11 +328,14 @@ class EvdevDevice(XlibSelectDevice, Device):
|
|
|
301
328
|
self.control_map[(event_type, event_code)] = control
|
|
302
329
|
self.controls.append(control)
|
|
303
330
|
|
|
304
|
-
self.controls.sort(key=lambda
|
|
331
|
+
self.controls.sort(key=lambda ctrl: ctrl.event_code)
|
|
305
332
|
os.close(fileno)
|
|
306
333
|
|
|
334
|
+
self._poll = select.poll()
|
|
307
335
|
self._event_size = ctypes.sizeof(InputEvent)
|
|
308
336
|
self._event_buffer = (InputEvent * 64)()
|
|
337
|
+
self._syn_dropped = False
|
|
338
|
+
self._event_queue = []
|
|
309
339
|
|
|
310
340
|
super().__init__(display, name)
|
|
311
341
|
|
|
@@ -321,6 +351,7 @@ class EvdevDevice(XlibSelectDevice, Device):
|
|
|
321
351
|
def open(self, window=None, exclusive=False):
|
|
322
352
|
try:
|
|
323
353
|
self._fileno = os.open(self._filename, os.O_RDWR | os.O_NONBLOCK)
|
|
354
|
+
self._poll.register(self._fileno, select.POLLIN | select.POLLPRI)
|
|
324
355
|
except OSError as e:
|
|
325
356
|
raise DeviceOpenException(e)
|
|
326
357
|
|
|
@@ -333,6 +364,9 @@ class EvdevDevice(XlibSelectDevice, Device):
|
|
|
333
364
|
if not self._fileno:
|
|
334
365
|
return
|
|
335
366
|
|
|
367
|
+
if self._poll:
|
|
368
|
+
self._poll.unregister(self._fileno)
|
|
369
|
+
|
|
336
370
|
pyglet.app.platform_event_loop.select_devices.remove(self)
|
|
337
371
|
os.close(self._fileno)
|
|
338
372
|
self._fileno = None
|
|
@@ -340,6 +374,20 @@ class EvdevDevice(XlibSelectDevice, Device):
|
|
|
340
374
|
def get_controls(self):
|
|
341
375
|
return self.controls
|
|
342
376
|
|
|
377
|
+
def _resync_control_state(self):
|
|
378
|
+
"""Manually resync all Control state.
|
|
379
|
+
|
|
380
|
+
This method queries and resets the state of each Control using the appropriate
|
|
381
|
+
ioctl calls. If this causes the Control value to change, the associated events
|
|
382
|
+
will be dispatched. This is a somewhat expensive operation, but it is necessary
|
|
383
|
+
to perform in some cases (such as when a SYN_DROPPED event is received).
|
|
384
|
+
"""
|
|
385
|
+
for control in self.control_map.values():
|
|
386
|
+
if isinstance(control, EvdevButton):
|
|
387
|
+
control.value = get_key_state(self._fileno, control.event_code)
|
|
388
|
+
if isinstance(control, EvdevAbsoluteAxis):
|
|
389
|
+
control.value = EVIOCGABS(self._fileno, control.event_code).value
|
|
390
|
+
|
|
343
391
|
# Force Feedback methods
|
|
344
392
|
|
|
345
393
|
def ff_upload_effect(self, structure):
|
|
@@ -351,25 +399,51 @@ class EvdevDevice(XlibSelectDevice, Device):
|
|
|
351
399
|
return self._fileno
|
|
352
400
|
|
|
353
401
|
def poll(self):
|
|
354
|
-
return False
|
|
402
|
+
return True if self._poll.poll(0) else False
|
|
355
403
|
|
|
356
404
|
def select(self):
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
405
|
+
"""When the file descriptor is ready, read and process InputEvents.
|
|
406
|
+
|
|
407
|
+
This method has the following behavior:
|
|
408
|
+
- Read and queue all incoming input events.
|
|
409
|
+
- When a SYN_REPORT event is received, dispatch all queued events.
|
|
410
|
+
- If a SYN_DROPPED event is received, set a flag. When the next
|
|
411
|
+
SYN_REPORT event appears, drop all queued events & manually resync
|
|
412
|
+
all Control state.
|
|
413
|
+
"""
|
|
360
414
|
try:
|
|
361
415
|
bytes_read = _readv(self._fileno, self._event_buffer)
|
|
416
|
+
n_events = bytes_read // self._event_size
|
|
362
417
|
except OSError:
|
|
363
418
|
self.close()
|
|
364
419
|
return
|
|
365
420
|
|
|
366
|
-
n_events = bytes_read // self._event_size
|
|
367
|
-
|
|
368
421
|
for event in self._event_buffer[:n_events]:
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
422
|
+
|
|
423
|
+
# Mark the current chain of events as invalid and continue:
|
|
424
|
+
if (event.type, event.code) == (EV_SYN, SYN_DROPPED):
|
|
425
|
+
self._syn_dropped = True
|
|
426
|
+
continue
|
|
427
|
+
|
|
428
|
+
# Dispatch queued events when SYN_REPORT comes in:
|
|
429
|
+
if (event.type, event.code) == (EV_SYN, SYN_REPORT):
|
|
430
|
+
|
|
431
|
+
# Unless a SYN_DROPPED event has been received,
|
|
432
|
+
# in which case discard all queued events and resync:
|
|
433
|
+
if self._syn_dropped:
|
|
434
|
+
self._event_queue.clear()
|
|
435
|
+
self._syn_dropped = False
|
|
436
|
+
self._resync_control_state()
|
|
437
|
+
|
|
438
|
+
# Dispatch all queued events, then clear the queue:
|
|
439
|
+
for queued_event in self._event_queue:
|
|
440
|
+
if control := self.control_map.get((queued_event.type, queued_event.code)):
|
|
441
|
+
control.value = queued_event.value
|
|
442
|
+
self._event_queue.clear()
|
|
443
|
+
|
|
444
|
+
# This is not a SYN_REPORT or SYN_DROPPED event, so it is probably
|
|
445
|
+
# an input event. Queue it until the next SYN_REPORT event comes in:
|
|
446
|
+
self._event_queue.append(event)
|
|
373
447
|
|
|
374
448
|
|
|
375
449
|
class FFController(Controller):
|
|
@@ -467,10 +541,9 @@ class EvdevControllerManager(ControllerManager, XlibSelectDevice):
|
|
|
467
541
|
else:
|
|
468
542
|
return # No device could be created
|
|
469
543
|
|
|
470
|
-
|
|
471
|
-
if controller := self._controllers.get(name, _create_controller(device)):
|
|
544
|
+
if controller := _create_controller(device):
|
|
472
545
|
self._controllers[name] = controller
|
|
473
|
-
#
|
|
546
|
+
# Post the event in the main thread:
|
|
474
547
|
self.post_event('on_connect', controller)
|
|
475
548
|
|
|
476
549
|
def select(self):
|
|
@@ -486,6 +559,7 @@ class EvdevControllerManager(ControllerManager, XlibSelectDevice):
|
|
|
486
559
|
|
|
487
560
|
for name in disappeared:
|
|
488
561
|
if controller := self._controllers.get(name):
|
|
562
|
+
del self._controllers[name]
|
|
489
563
|
self.dispatch_event('on_disconnect', controller)
|
|
490
564
|
|
|
491
565
|
def get_controllers(self) -> list[Controller]:
|
|
@@ -551,8 +625,8 @@ def _detect_controller_mapping(device):
|
|
|
551
625
|
ABS_Z: 'lefttrigger', ABS_RZ: 'righttrigger',
|
|
552
626
|
ABS_X: 'leftx', ABS_Y: 'lefty', ABS_RX: 'rightx', ABS_RY: 'righty'}
|
|
553
627
|
|
|
554
|
-
button_controls = [control for control in device.controls if isinstance(control,
|
|
555
|
-
axis_controls = [control for control in device.controls if isinstance(control,
|
|
628
|
+
button_controls = [control for control in device.controls if isinstance(control, EvdevButton)]
|
|
629
|
+
axis_controls = [control for control in device.controls if isinstance(control, EvdevAbsoluteAxis)]
|
|
556
630
|
hat_controls = [control for control in device.controls if control.name in ('hat_x', 'hat_y')]
|
|
557
631
|
|
|
558
632
|
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):
|
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
|
|
pyglet/math.py
CHANGED
|
@@ -1387,7 +1387,10 @@ class Mat4(_typing.NamedTuple):
|
|
|
1387
1387
|
|
|
1388
1388
|
|
|
1389
1389
|
class Quaternion(_typing.NamedTuple):
|
|
1390
|
-
"""Quaternion.
|
|
1390
|
+
"""Quaternion.
|
|
1391
|
+
|
|
1392
|
+
Quaternions are 4-dimensional complex numbers, useful for describing 3D rotations.
|
|
1393
|
+
"""
|
|
1391
1394
|
|
|
1392
1395
|
w: float = 1.0
|
|
1393
1396
|
x: float = 0.0
|
|
@@ -1403,6 +1406,8 @@ class Quaternion(_typing.NamedTuple):
|
|
|
1403
1406
|
raise NotImplementedError
|
|
1404
1407
|
|
|
1405
1408
|
def to_mat4(self) -> Mat4:
|
|
1409
|
+
"""Calculate a 4x4 transform matrix which applies a rotation."""
|
|
1410
|
+
|
|
1406
1411
|
w = self.w
|
|
1407
1412
|
x = self.x
|
|
1408
1413
|
y = self.y
|
|
@@ -1428,6 +1433,8 @@ class Quaternion(_typing.NamedTuple):
|
|
|
1428
1433
|
return Mat4(a, b, c, 0.0, e, f, g, 0.0, i, j, k, 0.0, 0.0, 0.0, 0.0, 1.0)
|
|
1429
1434
|
|
|
1430
1435
|
def to_mat3(self) -> Mat3:
|
|
1436
|
+
"""Create a 3x3 rotation matrix."""
|
|
1437
|
+
|
|
1431
1438
|
w = self.w
|
|
1432
1439
|
x = self.x
|
|
1433
1440
|
y = self.y
|
|
@@ -1453,21 +1460,34 @@ class Quaternion(_typing.NamedTuple):
|
|
|
1453
1460
|
return Mat3(*(a, b, c, e, f, g, i, j, k))
|
|
1454
1461
|
|
|
1455
1462
|
def length(self) -> float:
|
|
1456
|
-
"""Calculate the length of the
|
|
1457
|
-
|
|
1458
|
-
The distance between the coordinates and the origin.
|
|
1459
|
-
"""
|
|
1463
|
+
"""Calculate the length of the quaternion from the origin."""
|
|
1460
1464
|
return _math.sqrt(self.w**2 + self.x**2 + self.y**2 + self.z**2)
|
|
1461
1465
|
|
|
1462
1466
|
def conjugate(self) -> Quaternion:
|
|
1467
|
+
"""Calculate the conjugate of this quaternion.
|
|
1468
|
+
|
|
1469
|
+
This operation:
|
|
1470
|
+
#. leaves the :py:attr:`.w` component alone
|
|
1471
|
+
#. inverts the sign of the :py:attr:`.x`, :py:attr:`.y`, and :py:attr:`.z` components
|
|
1472
|
+
|
|
1473
|
+
"""
|
|
1463
1474
|
return Quaternion(self.w, -self.x, -self.y, -self.z)
|
|
1464
1475
|
|
|
1465
1476
|
def dot(self, other: Quaternion) -> float:
|
|
1477
|
+
"""Calculate the dot product with another quaternion."""
|
|
1466
1478
|
a, b, c, d = self
|
|
1467
1479
|
e, f, g, h = other
|
|
1468
1480
|
return a * e + b * f + c * g + d * h
|
|
1469
1481
|
|
|
1470
1482
|
def normalize(self) -> Quaternion:
|
|
1483
|
+
"""Calculate a unit quaternion from the instance.
|
|
1484
|
+
|
|
1485
|
+
The returned quaternion will be a scaled-down version
|
|
1486
|
+
of the instance which has:
|
|
1487
|
+
|
|
1488
|
+
* a length of ``1``
|
|
1489
|
+
* the same relative of its components
|
|
1490
|
+
"""
|
|
1471
1491
|
m = self.length()
|
|
1472
1492
|
if m == 0:
|
|
1473
1493
|
return self
|
pyglet/media/codecs/base.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import ctypes
|
|
2
4
|
import io
|
|
5
|
+
from dataclasses import dataclass
|
|
3
6
|
from typing import TYPE_CHECKING, BinaryIO, List, Optional, Union
|
|
4
7
|
|
|
5
|
-
from pyglet.media.exceptions import
|
|
6
|
-
from pyglet.util import next_or_equal_power_of_two
|
|
8
|
+
from pyglet.media.exceptions import CannotSeekException, MediaException
|
|
7
9
|
|
|
8
10
|
if TYPE_CHECKING:
|
|
9
11
|
from pyglet.image import AbstractImage
|
|
@@ -77,7 +79,7 @@ class AudioFormat:
|
|
|
77
79
|
self.__class__.__name__, self.channels, self.sample_size,
|
|
78
80
|
self.sample_rate)
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
@dataclass
|
|
81
83
|
class VideoFormat:
|
|
82
84
|
"""Video details.
|
|
83
85
|
|
|
@@ -98,20 +100,10 @@ class VideoFormat:
|
|
|
98
100
|
|
|
99
101
|
.. versionadded:: 1.2
|
|
100
102
|
"""
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
self.sample_aspect = sample_aspect
|
|
106
|
-
self.frame_rate = None
|
|
107
|
-
|
|
108
|
-
def __eq__(self, other) -> bool:
|
|
109
|
-
if isinstance(other, VideoFormat):
|
|
110
|
-
return (self.width == other.width and
|
|
111
|
-
self.height == other.height and
|
|
112
|
-
self.sample_aspect == other.sample_aspect and
|
|
113
|
-
self.frame_rate == other.frame_rate)
|
|
114
|
-
return False
|
|
103
|
+
width: int
|
|
104
|
+
height: int
|
|
105
|
+
sample_aspect: float = 0.0
|
|
106
|
+
frame_rate: float | None = None
|
|
115
107
|
|
|
116
108
|
|
|
117
109
|
class AudioData:
|
|
@@ -132,14 +124,14 @@ class AudioData:
|
|
|
132
124
|
`timestamp` and `duration` are unused and will be removed eventually.
|
|
133
125
|
"""
|
|
134
126
|
|
|
135
|
-
__slots__ = 'data', '
|
|
127
|
+
__slots__ = 'data', 'duration', 'events', 'length', 'pointer', 'timestamp'
|
|
136
128
|
|
|
137
129
|
def __init__(self,
|
|
138
|
-
data:
|
|
130
|
+
data: bytes | ctypes.Array,
|
|
139
131
|
length: int,
|
|
140
132
|
timestamp: float = 0.0,
|
|
141
133
|
duration: float = 0.0,
|
|
142
|
-
events:
|
|
134
|
+
events: list[MediaEvent] | None = None) -> None:
|
|
143
135
|
|
|
144
136
|
if isinstance(data, bytes):
|
|
145
137
|
# bytes are treated specially by ctypes and can be cast to a void pointer, get
|
|
@@ -163,6 +155,7 @@ class AudioData:
|
|
|
163
155
|
self.events = [] if events is None else events
|
|
164
156
|
|
|
165
157
|
|
|
158
|
+
@dataclass
|
|
166
159
|
class SourceInfo:
|
|
167
160
|
"""Source metadata information.
|
|
168
161
|
|
|
@@ -180,15 +173,14 @@ class SourceInfo:
|
|
|
180
173
|
|
|
181
174
|
.. versionadded:: 1.2
|
|
182
175
|
"""
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
genre = ''
|
|
176
|
+
title: str = ''
|
|
177
|
+
author: str = ''
|
|
178
|
+
copyright: str = ''
|
|
179
|
+
comment: str = ''
|
|
180
|
+
album: str = ''
|
|
181
|
+
year: int = 0
|
|
182
|
+
track: int = 0
|
|
183
|
+
genre: str = ''
|
|
192
184
|
|
|
193
185
|
|
|
194
186
|
class Source:
|
|
@@ -255,9 +247,8 @@ class Source:
|
|
|
255
247
|
player.on_player_eos = _on_player_eos
|
|
256
248
|
return player
|
|
257
249
|
|
|
258
|
-
def get_animation(self) ->
|
|
259
|
-
"""
|
|
260
|
-
Import all video frames into memory.
|
|
250
|
+
def get_animation(self) -> Animation:
|
|
251
|
+
"""Import all video frames into memory.
|
|
261
252
|
|
|
262
253
|
An empty animation will be returned if the source has no video.
|
|
263
254
|
Otherwise, the animation will contain all unplayed video frames (the
|