pyglet 2.1.5__py3-none-any.whl → 2.1.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. pyglet/__init__.py +27 -42
  2. pyglet/app/base.py +2 -2
  3. pyglet/clock.py +1 -1
  4. pyglet/display/base.py +31 -21
  5. pyglet/display/cocoa.py +25 -1
  6. pyglet/display/headless.py +1 -1
  7. pyglet/display/win32.py +134 -18
  8. pyglet/display/xlib.py +285 -70
  9. pyglet/event.py +17 -1
  10. pyglet/experimental/README.md +1 -1
  11. pyglet/experimental/jobs.py +1 -1
  12. pyglet/experimental/multitexture_sprite.py +2 -2
  13. pyglet/font/__init__.py +1 -1
  14. pyglet/font/base.py +8 -5
  15. pyglet/font/dwrite/__init__.py +13 -8
  16. pyglet/font/dwrite/dwrite_lib.py +1 -1
  17. pyglet/font/user.py +1 -1
  18. pyglet/gl/base.py +8 -4
  19. pyglet/gl/cocoa.py +4 -0
  20. pyglet/gl/gl.py +4 -3
  21. pyglet/gl/gl.pyi +2320 -0
  22. pyglet/gl/gl_compat.py +7 -18
  23. pyglet/gl/gl_compat.pyi +3097 -0
  24. pyglet/gl/xlib.py +24 -0
  25. pyglet/graphics/shader.py +34 -20
  26. pyglet/graphics/vertexbuffer.py +1 -1
  27. pyglet/gui/frame.py +2 -2
  28. pyglet/gui/widgets.py +1 -1
  29. pyglet/image/__init__.py +3 -3
  30. pyglet/image/buffer.py +3 -3
  31. pyglet/input/base.py +8 -8
  32. pyglet/input/linux/evdev.py +1 -1
  33. pyglet/libs/darwin/cocoapy/cocoalibs.py +3 -1
  34. pyglet/libs/win32/__init__.py +12 -0
  35. pyglet/libs/win32/constants.py +4 -0
  36. pyglet/libs/win32/types.py +97 -0
  37. pyglet/libs/x11/xrandr.py +166 -0
  38. pyglet/libs/x11/xrender.py +43 -0
  39. pyglet/libs/x11/xsync.py +43 -0
  40. pyglet/math.py +40 -49
  41. pyglet/media/buffered_logger.py +1 -1
  42. pyglet/media/codecs/ffmpeg.py +18 -34
  43. pyglet/media/codecs/gstreamer.py +3 -3
  44. pyglet/media/codecs/pyogg.py +1 -1
  45. pyglet/media/codecs/wave.py +6 -0
  46. pyglet/media/codecs/wmf.py +33 -7
  47. pyglet/media/devices/win32.py +1 -1
  48. pyglet/media/drivers/base.py +1 -1
  49. pyglet/media/drivers/directsound/interface.py +4 -0
  50. pyglet/media/drivers/listener.py +2 -2
  51. pyglet/media/drivers/xaudio2/interface.py +6 -2
  52. pyglet/media/drivers/xaudio2/lib_xaudio2.py +1 -1
  53. pyglet/media/instrumentation.py +2 -2
  54. pyglet/media/player.py +2 -2
  55. pyglet/media/player_worker_thread.py +1 -1
  56. pyglet/media/synthesis.py +1 -1
  57. pyglet/model/codecs/gltf.py +1 -1
  58. pyglet/shapes.py +25 -24
  59. pyglet/sprite.py +1 -1
  60. pyglet/text/caret.py +44 -5
  61. pyglet/text/layout/base.py +3 -3
  62. pyglet/util.py +1 -1
  63. pyglet/window/__init__.py +54 -14
  64. pyglet/window/cocoa/__init__.py +27 -0
  65. pyglet/window/mouse.py +11 -1
  66. pyglet/window/win32/__init__.py +40 -14
  67. pyglet/window/xlib/__init__.py +21 -7
  68. {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/METADATA +1 -1
  69. {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/RECORD +71 -67
  70. {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/LICENSE +0 -0
  71. {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/WHEEL +0 -0
pyglet/display/xlib.py CHANGED
@@ -1,25 +1,21 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ctypes
4
- from ctypes import c_int, c_char_p, c_buffer, POINTER, byref, cast
5
-
6
- from pyglet.util import asbytes
4
+ import warnings
5
+ from ctypes import POINTER, byref, c_buffer, c_char_p, c_int, cast
6
+ from typing import TYPE_CHECKING
7
7
 
8
+ import pyglet
8
9
  from pyglet import app
9
10
  from pyglet.app.xlib import XlibSelectDevice
11
+ from pyglet.libs.x11 import xlib
12
+ from pyglet.util import asbytes
10
13
 
11
- from ..libs.x11.xlib import Window
12
14
  from . import xlib_vidmoderestore
13
15
  from .base import Canvas, Display, Screen, ScreenMode
14
16
 
15
-
16
- # XXX
17
- # from pyglet.window import NoSuchDisplayException
18
- class NoSuchDisplayException(Exception):
19
- pass
20
-
21
-
22
- from pyglet.libs.x11 import xlib
17
+ if TYPE_CHECKING:
18
+ from pyglet.gl import Config
23
19
 
24
20
  try:
25
21
  from pyglet.libs.x11 import xinerama
@@ -42,6 +38,17 @@ try:
42
38
  except:
43
39
  _have_xf86vmode = False
44
40
 
41
+ try:
42
+ from pyglet.libs.x11 import xrandr
43
+
44
+ _have_xrandr = True
45
+ except ImportError:
46
+ _have_xrandr = False
47
+
48
+
49
+ class NoSuchDisplayException(Exception):
50
+ pass
51
+
45
52
 
46
53
  # Set up error handler
47
54
  def _error_handler(display, event):
@@ -52,7 +59,6 @@ def _error_handler(display, event):
52
59
  # driver bugs (and so the reports are useless). Nevertheless, set
53
60
  # environment variable PYGLET_DEBUG_X11 to 1 to get dumps of the error
54
61
  # and a traceback (execution will continue).
55
- import pyglet
56
62
  if pyglet.options['debug_x11']:
57
63
  event = event.contents
58
64
  buf = c_buffer(1024)
@@ -64,6 +70,7 @@ def _error_handler(display, event):
64
70
  print(' resource:', event.resourceid)
65
71
 
66
72
  import traceback
73
+
67
74
  print('Python stack trace (innermost last):')
68
75
  traceback.print_stack()
69
76
  return 0
@@ -79,9 +86,11 @@ class XlibDisplay(XlibSelectDevice, Display):
79
86
  _x_im = None # X input method
80
87
  # TODO close _x_im when display connection closed.
81
88
  _enable_xsync = False
82
- _screens = None # Cache of get_screens()
89
+ _screens: list[XlibScreen | XlibScreenXrandr | XlibScreenXinerama]
83
90
 
84
91
  def __init__(self, name=None, x_screen=None):
92
+ self._screens = []
93
+
85
94
  if x_screen is None:
86
95
  x_screen = 0
87
96
 
@@ -107,54 +116,95 @@ class XlibDisplay(XlibSelectDevice, Display):
107
116
  if _have_xsync:
108
117
  event_base = c_int()
109
118
  error_base = c_int()
110
- if xsync.XSyncQueryExtension(self._display,
111
- byref(event_base),
112
- byref(error_base)):
119
+ if xsync.XSyncQueryExtension(self._display, byref(event_base), byref(error_base)):
113
120
  major_version = c_int()
114
121
  minor_version = c_int()
115
- if xsync.XSyncInitialize(self._display,
116
- byref(major_version),
117
- byref(minor_version)):
122
+ if xsync.XSyncInitialize(self._display, byref(major_version), byref(minor_version)):
118
123
  self._enable_xsync = True
119
124
 
120
125
  # Add to event loop select list. Assume we never go away.
121
126
  app.platform_event_loop.select_devices.add(self)
122
127
 
123
- def get_screens(self):
124
- if self._screens:
125
- return self._screens
126
-
127
- if _have_xinerama and xinerama.XineramaIsActive(self._display):
128
+ def get_default_screen(self) -> Screen:
129
+ screens = self.get_screens()
130
+ if _have_xrandr:
131
+ for screen in screens:
132
+ if screen.is_primary:
133
+ return screen
134
+
135
+ # Couldn't find a default screen, use the first in the list.
136
+ return self._screens[0]
137
+
138
+ def get_screens(self) -> list[XlibScreen]:
139
+ self._screens = []
140
+
141
+ # Use XRandr if available, as it appears more maintained and widely supported.
142
+ if _have_xrandr:
143
+ root = xlib.XDefaultRootWindow(self._display)
144
+
145
+ res_ptr = xrandr.XRRGetScreenResources(self._display, root)
146
+ if res_ptr:
147
+ primary = xrandr.XRRGetOutputPrimary(self._display, root)
148
+
149
+ res = res_ptr.contents
150
+ for i in range(res.noutput):
151
+ output_id = res.outputs[i]
152
+ output_info_ptr = xrandr.XRRGetOutputInfo(self._display, res_ptr, output_id)
153
+ if not output_info_ptr:
154
+ xrandr.XRRFreeOutputInfo(output_info_ptr)
155
+ continue
156
+
157
+ output_info: xrandr.XRROutputInfo = output_info_ptr.contents
158
+
159
+ crtc_info_ptr = xrandr.XRRGetCrtcInfo(self._display, res_ptr, output_info.crtc)
160
+ if not crtc_info_ptr:
161
+ xrandr.XRRFreeCrtcInfo(crtc_info_ptr)
162
+ continue
163
+
164
+ crtc_info: xrandr.XRRCrtcInfo = crtc_info_ptr.contents
165
+
166
+ self._screens.append(
167
+ XlibScreenXrandr(
168
+ self,
169
+ crtc_info.x,
170
+ crtc_info.y,
171
+ crtc_info.width,
172
+ crtc_info.height,
173
+ output_info.crtc,
174
+ output_id,
175
+ ctypes.string_at(output_info.name, output_info.nameLen).decode(),
176
+ output_id == primary,
177
+ ),
178
+ )
179
+ xrandr.XRRFreeCrtcInfo(crtc_info_ptr)
180
+ xrandr.XRRFreeOutputInfo(output_info_ptr)
181
+
182
+ xrandr.XRRFreeScreenResources(res_ptr)
183
+
184
+ if not self._screens and _have_xinerama and xinerama.XineramaIsActive(self._display):
128
185
  number = c_int()
129
186
  infos = xinerama.XineramaQueryScreens(self._display, byref(number))
130
187
  infos = cast(infos, POINTER(xinerama.XineramaScreenInfo * number.value)).contents
131
- self._screens = []
188
+
132
189
  using_xinerama = number.value > 1
133
- for info in infos:
134
- self._screens.append(XlibScreen(self,
135
- info.x_org,
136
- info.y_org,
137
- info.width,
138
- info.height,
139
- using_xinerama))
190
+ for idx, info in enumerate(infos):
191
+ self._screens.append(
192
+ XlibScreenXinerama(self, info.x_org, info.y_org, info.width, info.height, using_xinerama, idx),
193
+ )
140
194
  xlib.XFree(infos)
141
- else:
195
+ elif not self._screens:
142
196
  # No xinerama
143
197
  screen_info = xlib.XScreenOfDisplay(self._display, self.x_screen)
144
- screen = XlibScreen(self,
145
- 0, 0,
146
- screen_info.contents.width,
147
- screen_info.contents.height,
148
- False)
198
+ screen = XlibScreen(self, 0, 0, screen_info.contents.width, screen_info.contents.height)
149
199
  self._screens = [screen]
150
200
  return self._screens
151
201
 
152
202
  # XlibSelectDevice interface
153
203
 
154
- def fileno(self):
204
+ def fileno(self) -> int:
155
205
  return self._fileno
156
206
 
157
- def select(self):
207
+ def select(self) -> None:
158
208
  e = xlib.XEvent()
159
209
  while xlib.XPending(self._display):
160
210
  xlib.XNextEvent(self._display, e)
@@ -171,18 +221,17 @@ class XlibDisplay(XlibSelectDevice, Display):
171
221
 
172
222
  dispatch(e)
173
223
 
174
- def poll(self):
224
+ def poll(self) -> int:
175
225
  return xlib.XPending(self._display)
176
226
 
177
227
 
178
228
  class XlibScreen(Screen):
179
229
  _initial_mode = None
180
230
 
181
- def __init__(self, display, x, y, width, height, xinerama):
231
+ def __init__(self, display: XlibDisplay, x: int, y: int, width: int, height: int):
182
232
  super().__init__(display, x, y, width, height)
183
- self._xinerama = xinerama
184
233
 
185
- def get_dpi(self):
234
+ def get_dpi(self) -> int:
186
235
  resource = xlib.XResourceManagerString(self.display._display)
187
236
  dpi = 96
188
237
  if resource:
@@ -192,8 +241,7 @@ class XlibScreen(Screen):
192
241
  if db:
193
242
  rs_type = c_char_p()
194
243
  value = xlib.XrmValue()
195
- if xlib.XrmGetResource(db, asbytes("Xft.dpi"), asbytes("Xft.dpi"),
196
- byref(rs_type), byref(value)):
244
+ if xlib.XrmGetResource(db, asbytes("Xft.dpi"), asbytes("Xft.dpi"), byref(rs_type), byref(value)):
197
245
  if value.addr and rs_type.value == b'String':
198
246
  dpi = int(value.addr)
199
247
 
@@ -201,10 +249,10 @@ class XlibScreen(Screen):
201
249
 
202
250
  return dpi
203
251
 
204
- def get_scale(self):
252
+ def get_scale(self) -> float:
205
253
  return self.get_dpi() / 96
206
254
 
207
- def get_matching_configs(self, template):
255
+ def get_matching_configs(self, template: Config):
208
256
  canvas = XlibCanvas(self.display, None)
209
257
  configs = template.match(canvas)
210
258
  # XXX deprecate
@@ -212,41 +260,40 @@ class XlibScreen(Screen):
212
260
  config.screen = self
213
261
  return configs
214
262
 
215
- def get_modes(self):
263
+ def get_modes(self) -> list[XlibScreenModeXF86]:
216
264
  if not _have_xf86vmode:
217
265
  return []
218
266
 
219
- if self._xinerama:
220
- # If Xinerama/TwinView is enabled, xf86vidmode's modelines
221
- # correspond to metamodes, which don't distinguish one screen from
222
- # another. XRandR (broken) or NV (complicated) extensions needed.
223
- return []
224
-
225
267
  count = ctypes.c_int()
226
268
  info_array = ctypes.POINTER(ctypes.POINTER(xf86vmode.XF86VidModeModeInfo))()
227
269
  xf86vmode.XF86VidModeGetAllModeLines(self.display._display, self.display.x_screen, count, info_array)
228
270
 
271
+ depth = xlib.XDefaultDepth(self.display._display, self.display.x_screen)
272
+
229
273
  # Copy modes out of list and free list
230
274
  modes = []
231
275
  for i in range(count.value):
232
276
  info = xf86vmode.XF86VidModeModeInfo()
233
- ctypes.memmove(ctypes.byref(info),
234
- ctypes.byref(info_array.contents[i]),
235
- ctypes.sizeof(info))
236
- modes.append(XlibScreenMode(self, info))
277
+ ctypes.memmove(
278
+ ctypes.byref(info),
279
+ ctypes.byref(info_array.contents[i]),
280
+ ctypes.sizeof(info),
281
+ )
282
+
283
+ modes.append(XlibScreenModeXF86(self, info, depth))
237
284
  if info.privsize:
238
285
  xlib.XFree(info.private)
239
286
  xlib.XFree(info_array)
240
287
 
241
288
  return modes
242
289
 
243
- def get_mode(self):
290
+ def get_mode(self) -> XlibScreenMode:
244
291
  modes = self.get_modes()
245
292
  if modes:
246
293
  return modes[0]
247
294
  return None
248
295
 
249
- def set_mode(self, mode):
296
+ def set_mode(self, mode: XlibScreenModeXF86):
250
297
  assert mode.screen is self
251
298
 
252
299
  if not self._initial_mode:
@@ -265,24 +312,192 @@ class XlibScreen(Screen):
265
312
  if self._initial_mode:
266
313
  self.set_mode(self._initial_mode)
267
314
 
315
+ def get_display_id(self) -> int:
316
+ # No real unique ID is available, just hash together the properties.
317
+ return hash((self.x, self.y, self.width, self.height))
318
+
319
+ def get_monitor_name(self) -> str:
320
+ # No way to get any screen name without XRandr or EDID information.
321
+ return "Unknown"
322
+
268
323
  def __repr__(self):
269
- return f"{self.__class__.__name__}(display={self.display!r}, x={self.x}, y={self.y}, " \
270
- f"width={self.width}, height={self.height}, xinerama={self._xinerama})"
324
+ return (
325
+ f"{self.__class__.__name__}(display={self.display!r}, x={self.x}, y={self.y}, "
326
+ f"width={self.width}, height={self.height})"
327
+ )
328
+
329
+
330
+ class XlibScreenXinerama(XlibScreen):
331
+ def __init__(self, display: XlibDisplay, x: int, y: int, width: int, height: int, using_xinerama: bool, idx: int) -> None:
332
+ super().__init__(display, x, y, width, height)
333
+ self._xinerama = using_xinerama
334
+ self.idx = idx
335
+
336
+ def get_display_id(self) -> int:
337
+ # No real unique ID is available, just hash together the properties.
338
+ return hash((self.idx, self.x, self.y, self.width, self.height))
339
+
340
+ def get_modes(self):
341
+ if self._xinerama:
342
+ # If Xinerama/TwinView is enabled, xf86vidmode's modelines
343
+ # correspond to metamodes, which don't distinguish one screen from
344
+ # another. XRandR (broken) or NV (complicated) extensions needed.
345
+ return []
346
+
347
+ return super().get_modes()
348
+
349
+
350
+ class XlibScreenXrandr(XlibScreen):
351
+ def __init__(
352
+ self,
353
+ display: XlibDisplay,
354
+ x: int,
355
+ y: int,
356
+ width: int,
357
+ height: int,
358
+ crtc_id: int,
359
+ output_id: int,
360
+ name: str,
361
+ is_primary: bool,
362
+ ):
363
+ super().__init__(display, x, y, width, height)
364
+ self.crtc_id = crtc_id
365
+ self.output_id = output_id
366
+ self.name = name
367
+ self._is_primary = is_primary
368
+
369
+ @property
370
+ def is_primary(self):
371
+ return self._is_primary
372
+
373
+ @staticmethod
374
+ def _get_mode_info(resource: xrandr.XRRScreenResources, rrmode_id: int) -> xrandr.XRRModeInfo | None:
375
+ for i in range(resource.nmode):
376
+ if resource.modes[i].id == rrmode_id:
377
+ return resource.modes[i]
378
+
379
+ return None
380
+
381
+ def set_mode(self, mode: XlibScreenModeXrandr) -> None:
382
+ assert mode.screen is self
383
+
384
+ if not self._initial_mode:
385
+ self._initial_mode = self.get_mode()
386
+
387
+ root = xlib.XDefaultRootWindow(self.display._display)
388
+ res_ptr = xrandr.XRRGetScreenResourcesCurrent(self.display._display, root)
389
+ if res_ptr:
390
+ crtc_info_ptr = xrandr.XRRGetCrtcInfo(self.display._display, res_ptr, self.crtc_id)
391
+ if crtc_info_ptr:
392
+ crtc_info = crtc_info_ptr.contents
393
+ status = xrandr.XRRSetCrtcConfig(
394
+ self.display._display,
395
+ res_ptr,
396
+ self.crtc_id,
397
+ xlib.CurrentTime,
398
+ crtc_info.x,
399
+ crtc_info.y,
400
+ mode.mode_id,
401
+ crtc_info.rotation,
402
+ crtc_info.outputs,
403
+ crtc_info.noutput,
404
+ )
405
+ if status != 0 and pyglet.options['debug_x11']:
406
+ warnings.warn(f"Could not set screen mode: {status}")
407
+ xlib.XFlush(self.display._display)
408
+
409
+ self.width = mode.width
410
+ self.height = mode.height
411
+
412
+ xrandr.XRRFreeCrtcInfo(crtc_info_ptr)
413
+ xrandr.XRRFreeScreenResources(res_ptr)
414
+
415
+ def get_modes(self) -> list[XlibScreenModeXrandr]:
416
+ modes = []
417
+ root = xlib.XDefaultRootWindow(self.display._display)
418
+ res_ptr = xrandr.XRRGetScreenResourcesCurrent(self.display._display, root)
419
+ output_info_ptr = xrandr.XRRGetOutputInfo(self.display._display, res_ptr, self.output_id)
420
+
421
+ res = res_ptr.contents
422
+ output_info = output_info_ptr.contents
423
+
424
+ depth = xlib.XDefaultDepth(self.display._display, self.display.x_screen)
425
+
426
+ for i in range(output_info_ptr.contents.nmode):
427
+ mode_id = output_info.modes[i]
428
+ xrandr_mode = self._get_mode_info(res, mode_id)
429
+ if xrandr_mode:
430
+ mode = XlibScreenModeXrandr(self, xrandr_mode, mode_id, depth)
431
+ modes.append(mode)
432
+
433
+ xrandr.XRRFreeOutputInfo(output_info_ptr)
434
+ xrandr.XRRFreeScreenResources(res_ptr)
435
+ return modes
436
+
437
+ def get_mode(self) -> XlibScreenModeXrandr | None:
438
+ # Return the current mode.
439
+ root = xlib.XDefaultRootWindow(self.display._display)
440
+ res_ptr = xrandr.XRRGetScreenResourcesCurrent(self.display._display, root)
441
+ crtc_info_ptr = xrandr.XRRGetCrtcInfo(self.display._display, res_ptr, self.crtc_id)
442
+
443
+ crtc_info = crtc_info_ptr.contents
444
+ found_mode = None
445
+
446
+ for mode in self.get_modes():
447
+ if crtc_info.mode == mode.mode_id:
448
+ found_mode = mode
449
+ break
450
+
451
+ xrandr.XRRFreeCrtcInfo(crtc_info_ptr)
452
+ xrandr.XRRFreeScreenResources(res_ptr)
453
+ return found_mode
454
+
455
+ def get_display_id(self) -> int:
456
+ return self.output_id
457
+
458
+ def get_monitor_name(self) -> str:
459
+ return self.name
271
460
 
272
461
 
273
462
  class XlibScreenMode(ScreenMode):
274
- def __init__(self, screen, info):
463
+ def __init__(self, screen: XlibScreen, width: int, height: int, rate: int, depth: int):
275
464
  super().__init__(screen)
465
+ self.width = width
466
+ self.height = height
467
+ self.rate = rate
468
+ self.depth = depth
469
+
470
+
471
+ class XlibScreenModeXF86(XlibScreenMode):
472
+ def __init__(self, screen: XlibScreen, info: xf86vmode.XF86VidModeModeInfo, depth: int) -> None:
276
473
  self.info = info
277
- self.width = info.hdisplay
278
- self.height = info.vdisplay
279
- self.rate = (info.dotclock * 1000) / (info.htotal * info.vtotal)
280
- self.depth = None
474
+ width = info.hdisplay
475
+ height = info.vdisplay
476
+ rate = round((info.dotclock * 1000) / (info.htotal * info.vtotal))
477
+ super().__init__(screen, width, height, rate, depth)
478
+
479
+ def __repr__(self) -> str:
480
+ return f'XlibScreenMode(width={self.width!r}, height={self.height!r}, depth={self.depth!r}, rate={self.rate})'
481
+
482
+
483
+ class XlibScreenModeXrandr(XlibScreenMode):
484
+ def __init__(self, screen: XlibScreen, mode_info: xrandr.XRRModeInfo, mode_id: int, depth: int) -> None:
485
+ self.mode_id = mode_id
486
+ super().__init__(screen, mode_info.width, mode_info.height, self._calculate_refresh_rate(mode_info), depth)
487
+
488
+ @staticmethod
489
+ def _calculate_refresh_rate(mode_info: xrandr.XRRModeInfo) -> int:
490
+ if mode_info.hTotal > 0 and mode_info.vTotal > 0:
491
+ return round(mode_info.dotClock / (mode_info.hTotal * mode_info.vTotal))
492
+ return 0
493
+
494
+ def __repr__(self) -> str:
495
+ return f'XlibScreenMode(width={self.width!r}, height={self.height!r}, depth={self.depth!r}, rate={self.rate})'
281
496
 
282
497
 
283
498
  class XlibCanvas(Canvas): # noqa: D101
284
499
  display: XlibDisplay
285
500
 
286
- def __init__(self, display: XlibDisplay, x_window: Window) -> None: # noqa: D107
501
+ def __init__(self, display: XlibDisplay, x_window: xlib.Window) -> None: # noqa: D107
287
502
  super().__init__(display)
288
503
  self.x_window = x_window
pyglet/event.py CHANGED
@@ -120,10 +120,13 @@ from __future__ import annotations
120
120
 
121
121
  import inspect
122
122
  import os.path
123
+
123
124
  from functools import partial
124
125
  from typing import TYPE_CHECKING, Literal, Union
125
126
  from weakref import WeakMethod
126
127
 
128
+ import pyglet
129
+
127
130
  if TYPE_CHECKING:
128
131
  from typing import Any, Callable, Generator
129
132
 
@@ -328,7 +331,7 @@ class EventDispatcher:
328
331
  handlers down the stack will receive this event.
329
332
 
330
333
  This method has several possible return values. If any event
331
- hander has returned ``EVENT_HANDLED``, then this method will
334
+ handler has returned ``EVENT_HANDLED``, then this method will
332
335
  also return ``EVENT_HANDLED``. If not, this method will return
333
336
  ``EVENT_UNHANDLED``. If there were no events registered to
334
337
  receive this event, ``False`` is returned.
@@ -381,6 +384,19 @@ class EventDispatcher:
381
384
 
382
385
  return False
383
386
 
387
+ def post_event(self, event_type: str, *args: Any) -> bool | None:
388
+ """Post an event to the main application thread.
389
+
390
+ Unlike the :py:meth:`~pyglet.event.EventDispatcher.dispatch_event`
391
+ method, this method does not dispatch events directly. Instead, it
392
+ hands off the dispatch call to the main application thread. This
393
+ ensures that any event handlers are also executed in the main thread.
394
+
395
+ This method aliases :py:meth:`~pyglet.app.PlatformEventLoop.post_event`,
396
+ which can be seen for more information on behavior.
397
+ """
398
+ pyglet.app.platform_event_loop.post_event(self, event_type, *args)
399
+
384
400
  def _raise_dispatch_exception(self, event_type: str, args: Any, handler: Callable, exception: Exception) -> None:
385
401
  # A common problem in applications is having the wrong number of
386
402
  # arguments in an event handler. This is caught as a TypeError in
@@ -2,5 +2,5 @@ Experimental modules
2
2
  ====================
3
3
 
4
4
  This package contains experimental modules, which are included here for
5
- wider testing and feedback. Anything contined within may be broken, refactored,
5
+ wider testing and feedback. Anything contained within may be broken, refactored,
6
6
  or removed without notice.
@@ -92,7 +92,7 @@ class JobExecutor:
92
92
 
93
93
  All JobExecutor workers are Daemon Threads, so it is not strictly
94
94
  necessary to call shutdown() at program termination. However, if
95
- it is no longer needed, shutdown() can be called ot free up the
95
+ it is no longer needed, shutdown() can be called to free up the
96
96
  thread resources.
97
97
  """
98
98
  if not self._queue:
@@ -378,7 +378,7 @@ class MultiTextureSprite(pyglet.sprite.Sprite):
378
378
  if new_tex.id is not self._textures[key].id:
379
379
  # Need to make a shallow copy to allow the batch object
380
380
  # to correctly split this sprite from other sprite's groups.
381
- # if not then you will be modifing all othe the other sprites
381
+ # if not then you will be modifying all the other sprites
382
382
  # textures dict object as well.
383
383
  self._textures = self._textures.copy()
384
384
  self._textures[key] = new_tex
@@ -467,7 +467,7 @@ class MultiTextureSprite(pyglet.sprite.Sprite):
467
467
  The Image or Animation to set
468
468
  """
469
469
  if name in self._animations:
470
- # Need to stop all animations temporarly so we can swap the layer out
470
+ # Need to stop all animations temporarily so we can swap the layer out
471
471
  pyglet.clock.unschedule(self._animate)
472
472
  self._animations.pop(name)
473
473
 
pyglet/font/__init__.py CHANGED
@@ -68,7 +68,7 @@ def add_user_font(font: UserDefinedFontBase) -> None:
68
68
  Exception: If font provided is not derived from :py:class:`~pyglet.font.user.UserDefinedFontBase`.
69
69
  """
70
70
  if not isinstance(font, UserDefinedFontBase):
71
- msg = "Font is not must be created fromm the UserDefinedFontBase."
71
+ msg = "Font must be created from the UserDefinedFontBase."
72
72
  raise Exception(msg)
73
73
 
74
74
  # Locate or create font cache
pyglet/font/base.py CHANGED
@@ -7,10 +7,9 @@ classes as a documented interface to the concrete classes.
7
7
  from __future__ import annotations
8
8
 
9
9
  import abc
10
- from dataclasses import dataclass
11
-
12
10
  import unicodedata
13
- from typing import BinaryIO, ClassVar
11
+ from dataclasses import dataclass
12
+ from typing import Any, BinaryIO, ClassVar
14
13
 
15
14
  from pyglet import image
16
15
  from pyglet.gl import GL_LINEAR, GL_RGBA, GL_TEXTURE_2D
@@ -63,7 +62,7 @@ def grapheme_break(left: str, left_cc: str, right: str, right_cc: str) -> bool:
63
62
  return False
64
63
 
65
64
  # GB9b: Do not break after Prepend characters
66
- if left in _LOGICAL_ORDER_EXCEPTION: # noqa: SIM103
65
+ if left in _LOGICAL_ORDER_EXCEPTION:
67
66
  return False
68
67
 
69
68
  # GB999: Default to break
@@ -259,7 +258,11 @@ class Font:
259
258
  ``GL_NEAREST`` to prevent aliasing with pixelated fonts.
260
259
  """
261
260
  #: :meta private:
262
- glyphs: dict[str | int, Glyph]
261
+ glyphs: dict[str | int | tuple[Any, int], Glyph]
262
+ # Glyphs can be cached in various ways:
263
+ # str: if no text shaping
264
+ # int: glyph index, if no fallback behavior.
265
+ # tuple: with a unique font identifier and glyph index
263
266
 
264
267
  texture_width: int = 512
265
268
  texture_height: int = 512