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.
- 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 +1 -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 +34 -20
- 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 +8 -8
- pyglet/input/linux/evdev.py +1 -1
- pyglet/libs/darwin/cocoapy/cocoalibs.py +3 -1
- 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 +40 -49
- 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 +25 -24
- 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.5.dist-info → pyglet-2.1.8.dist-info}/METADATA +1 -1
- {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/RECORD +71 -67
- {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/LICENSE +0 -0
- {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/WHEEL +0 -0
pyglet/__init__.py
CHANGED
|
@@ -6,16 +6,16 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
8
|
import sys
|
|
9
|
-
|
|
9
|
+
import warnings
|
|
10
|
+
from collections.abc import Sequence
|
|
10
11
|
from dataclasses import dataclass
|
|
11
12
|
from typing import TYPE_CHECKING, Literal
|
|
12
|
-
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from types import FrameType
|
|
15
15
|
from typing import Any, Callable, ItemsView, Sized
|
|
16
16
|
|
|
17
17
|
#: The release version
|
|
18
|
-
version = '2.1.
|
|
18
|
+
version = '2.1.8'
|
|
19
19
|
__version__ = version
|
|
20
20
|
|
|
21
21
|
MIN_PYTHON_VERSION = 3, 8
|
|
@@ -30,20 +30,16 @@ compat_platform = sys.platform
|
|
|
30
30
|
if "bsd" in compat_platform:
|
|
31
31
|
compat_platform = "linux-compat"
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
_SPECIAL_OPTION_VALIDATORS = {
|
|
39
|
-
"audio": lambda x: isinstance(x, Sequence),
|
|
40
|
-
"vsync": lambda x: x is None or isinstance(x, bool),
|
|
41
|
-
}
|
|
33
|
+
if compat_platform == "cygwin":
|
|
34
|
+
# This hack pretends that the posix-like ctypes provides windows
|
|
35
|
+
# functionality. COM does not work with this hack, so there is no
|
|
36
|
+
# DirectSound support.
|
|
37
|
+
import ctypes
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
ctypes.windll = ctypes.cdll
|
|
40
|
+
ctypes.oledll = ctypes.cdll
|
|
41
|
+
ctypes.WINFUNCTYPE = ctypes.CFUNCTYPE
|
|
42
|
+
ctypes.HRESULT = ctypes.c_long
|
|
47
43
|
|
|
48
44
|
|
|
49
45
|
@dataclass
|
|
@@ -115,7 +111,7 @@ class Options:
|
|
|
115
111
|
that utilize COM calls. Only applies to the Windows platform."""
|
|
116
112
|
|
|
117
113
|
debug_win32: bool = False
|
|
118
|
-
"""If ``True``, prints error messages related to Windows library calls. Usually
|
|
114
|
+
"""If ``True``, prints error messages related to Windows library calls. Usually gets information from
|
|
119
115
|
``Kernel32.GetLastError``. This information is output to a file called ``debug_win32.log``."""
|
|
120
116
|
|
|
121
117
|
debug_input: bool = False
|
|
@@ -311,42 +307,31 @@ class Options:
|
|
|
311
307
|
return self.__dict__[item]
|
|
312
308
|
|
|
313
309
|
def __setitem__(self, key: str, value: Any) -> None:
|
|
314
|
-
assert key in self.__annotations__, f"Invalid option name: '{key}'"
|
|
315
|
-
assert (_SPECIAL_OPTION_VALIDATORS.get(key) or _OPTION_TYPE_VALIDATORS[self.__annotations__[key]])(value), \
|
|
316
|
-
f"Invalid type: '{type(value)}' for '{key}'"
|
|
317
310
|
self.__dict__[key] = value
|
|
318
311
|
|
|
319
312
|
|
|
320
313
|
#: Instance of :py:class:`~pyglet.Options` used to set runtime options.
|
|
321
314
|
options: Options = Options()
|
|
322
315
|
|
|
323
|
-
_OPTION_TYPE_REMAPS = {
|
|
324
|
-
"audio": "sequence",
|
|
325
|
-
"vsync": "bool",
|
|
326
|
-
}
|
|
327
316
|
|
|
328
|
-
for
|
|
317
|
+
for _option_name, _type_str in options.__annotations__.items():
|
|
329
318
|
"""Check Environment Variables for pyglet options"""
|
|
330
|
-
if _value := os.environ.get(f"PYGLET_{
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
319
|
+
if _value := os.environ.get(f"PYGLET_{_option_name.upper()}"):
|
|
320
|
+
if 'Sequence' in _type_str:
|
|
321
|
+
setattr(options, _option_name, _value.split(","))
|
|
322
|
+
elif 'bool' in _type_str:
|
|
323
|
+
setattr(options, _option_name, _value in ("true", "TRUE", "True", "1"))
|
|
324
|
+
elif 'int' in _type_str:
|
|
325
|
+
setattr(options, _option_name, int(_value))
|
|
326
|
+
elif 'Literal' in _type_str and _value in _type_str:
|
|
327
|
+
setattr(options, _option_name, _value)
|
|
328
|
+
else:
|
|
329
|
+
warnings.warn(f"Invalid value '{_value}' for {_option_name}. Expecting {_type_str}")
|
|
338
330
|
|
|
339
331
|
|
|
340
|
-
if
|
|
341
|
-
|
|
342
|
-
# functionality. COM does not work with this hack, so there is no
|
|
343
|
-
# DirectSound support.
|
|
344
|
-
import ctypes
|
|
332
|
+
if (__debug__ is False) or getattr(sys, "frozen", False):
|
|
333
|
+
options.debug_gl = False
|
|
345
334
|
|
|
346
|
-
ctypes.windll = ctypes.cdll
|
|
347
|
-
ctypes.oledll = ctypes.cdll
|
|
348
|
-
ctypes.WINFUNCTYPE = ctypes.CFUNCTYPE
|
|
349
|
-
ctypes.HRESULT = ctypes.c_long
|
|
350
335
|
|
|
351
336
|
# Call tracing
|
|
352
337
|
# ------------
|
pyglet/app/base.py
CHANGED
|
@@ -199,7 +199,7 @@ class EventLoop(event.EventDispatcher):
|
|
|
199
199
|
if self._interval is None:
|
|
200
200
|
self._redraw_windows(dt)
|
|
201
201
|
|
|
202
|
-
# Update
|
|
202
|
+
# Update timeout
|
|
203
203
|
timeout = self.clock.get_sleep_time(True)
|
|
204
204
|
app.platform_event_loop.set_timer(self._blocking_timer, timeout)
|
|
205
205
|
|
|
@@ -231,7 +231,7 @@ class EventLoop(event.EventDispatcher):
|
|
|
231
231
|
dt = self.clock.update_time()
|
|
232
232
|
self.clock.call_scheduled_functions(dt)
|
|
233
233
|
|
|
234
|
-
# Update
|
|
234
|
+
# Update timeout
|
|
235
235
|
return self.clock.get_sleep_time(True)
|
|
236
236
|
|
|
237
237
|
@property
|
pyglet/clock.py
CHANGED
|
@@ -303,7 +303,7 @@ class Clock:
|
|
|
303
303
|
|
|
304
304
|
The result is the average of a sliding window of the last "n" updates,
|
|
305
305
|
where "n" is some number designed to cover approximately 1 second.
|
|
306
|
-
This is the clock
|
|
306
|
+
This is the clock frequency, **not** the Window redraw rate (fps).
|
|
307
307
|
"""
|
|
308
308
|
if not self.cumulative_time:
|
|
309
309
|
return 0
|
pyglet/display/base.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import abc
|
|
4
|
+
from typing import TYPE_CHECKING, Literal
|
|
4
5
|
|
|
5
|
-
from pyglet import gl
|
|
6
|
-
from pyglet import app
|
|
7
|
-
from pyglet import window
|
|
8
|
-
from pyglet import display
|
|
6
|
+
from pyglet import app, display, gl, window
|
|
9
7
|
|
|
10
8
|
if TYPE_CHECKING:
|
|
11
9
|
from pyglet.gl import Config
|
|
@@ -55,9 +53,7 @@ class Display:
|
|
|
55
53
|
raise NotImplementedError('abstract')
|
|
56
54
|
|
|
57
55
|
def get_default_screen(self) -> Screen:
|
|
58
|
-
"""Get the default (primary) screen as specified by the user's operating system
|
|
59
|
-
preferences.
|
|
60
|
-
"""
|
|
56
|
+
"""Get the default (primary) screen as specified by the user's operating system preferences."""
|
|
61
57
|
screens = self.get_screens()
|
|
62
58
|
for screen in screens:
|
|
63
59
|
if screen.x == 0 and screen.y == 0:
|
|
@@ -71,7 +67,7 @@ class Display:
|
|
|
71
67
|
return [win for win in app.windows if win.display is self]
|
|
72
68
|
|
|
73
69
|
|
|
74
|
-
class Screen:
|
|
70
|
+
class Screen(abc.ABC):
|
|
75
71
|
"""A virtual monitor that supports fullscreen windows.
|
|
76
72
|
|
|
77
73
|
Screens typically map onto a physical display such as a
|
|
@@ -84,12 +80,12 @@ class Screen:
|
|
|
84
80
|
give the global location of the top-left corner of the screen. This is
|
|
85
81
|
useful for determining if screens are arranged above or next to one
|
|
86
82
|
another.
|
|
87
|
-
|
|
83
|
+
|
|
88
84
|
Use :func:`~Display.get_screens` or :func:`~Display.get_default_screen`
|
|
89
85
|
to obtain an instance of this class.
|
|
90
86
|
"""
|
|
91
87
|
|
|
92
|
-
def __init__(self, display: Display, x: int, y: int, width: int, height: int):
|
|
88
|
+
def __init__(self, display: Display, x: int, y: int, width: int, height: int) -> None:
|
|
93
89
|
self.display = display
|
|
94
90
|
"""Display this screen belongs to."""
|
|
95
91
|
self.x = x
|
|
@@ -208,29 +204,43 @@ class Screen:
|
|
|
208
204
|
def set_mode(self, mode: ScreenMode) -> None:
|
|
209
205
|
"""Set the display mode for this screen.
|
|
210
206
|
|
|
211
|
-
The mode must be one previously returned by :meth:`get_mode` or
|
|
207
|
+
The mode must be one previously returned by :meth:`get_mode` or
|
|
212
208
|
:meth:`get_modes`.
|
|
213
209
|
"""
|
|
214
210
|
raise NotImplementedError('abstract')
|
|
215
211
|
|
|
216
212
|
def restore_mode(self) -> None:
|
|
217
|
-
"""Restore the screen mode to the user's default.
|
|
218
|
-
"""
|
|
213
|
+
"""Restore the screen mode to the user's default."""
|
|
219
214
|
raise NotImplementedError('abstract')
|
|
220
215
|
|
|
221
216
|
def get_dpi(self):
|
|
222
|
-
"""Get the DPI of the screen.
|
|
217
|
+
"""Get the DPI of the screen."""
|
|
218
|
+
raise NotImplementedError('abstract')
|
|
223
219
|
|
|
224
|
-
|
|
225
|
-
"""
|
|
220
|
+
def get_scale(self) -> float:
|
|
221
|
+
"""Get the pixel scale ratio of the screen."""
|
|
226
222
|
raise NotImplementedError('abstract')
|
|
227
223
|
|
|
228
|
-
|
|
229
|
-
|
|
224
|
+
@abc.abstractmethod
|
|
225
|
+
def get_display_id(self) -> str | int:
|
|
226
|
+
"""Get a unique identifier for the screen.
|
|
230
227
|
|
|
231
|
-
:
|
|
228
|
+
.. versionadded: 2.1.8
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
@abc.abstractmethod
|
|
232
|
+
def get_monitor_name(self) -> str | Literal["Unknown"]:
|
|
233
|
+
"""Get a friendly name for the screen if available.
|
|
234
|
+
|
|
235
|
+
Windows and Mac OSX report what the system will see the screen as.
|
|
236
|
+
|
|
237
|
+
On Linux, there is no API for retrieving the monitor name, other than manually decoding the EDID information.
|
|
238
|
+
Instead, it will return the connection name.
|
|
239
|
+
|
|
240
|
+
If no name can be queried, a default name of Unknown will be returned.
|
|
241
|
+
|
|
242
|
+
.. versionadded: 2.1.8
|
|
232
243
|
"""
|
|
233
|
-
raise NotImplementedError('abstract')
|
|
234
244
|
|
|
235
245
|
|
|
236
246
|
class ScreenMode:
|
pyglet/display/cocoa.py
CHANGED
|
@@ -19,8 +19,17 @@ class CocoaDisplay(Display):
|
|
|
19
19
|
activeDisplays = (CGDirectDisplayID * maxDisplays)()
|
|
20
20
|
count = c_uint32()
|
|
21
21
|
quartz.CGGetActiveDisplayList(maxDisplays, activeDisplays, byref(count))
|
|
22
|
-
return [CocoaScreen(self, displayID) for displayID in list(activeDisplays)[:count.value]]
|
|
22
|
+
return [CocoaScreen(self, displayID) for displayID in list(activeDisplays)[: count.value]]
|
|
23
23
|
|
|
24
|
+
def get_default_screen(self) -> Screen:
|
|
25
|
+
main_id = quartz.CGMainDisplayID()
|
|
26
|
+
|
|
27
|
+
screens = self.get_screens()
|
|
28
|
+
for screen in screens:
|
|
29
|
+
if screen.get_display_id() == main_id:
|
|
30
|
+
return screen
|
|
31
|
+
|
|
32
|
+
return screens[0]
|
|
24
33
|
|
|
25
34
|
class CocoaScreen(Screen):
|
|
26
35
|
|
|
@@ -36,6 +45,11 @@ class CocoaScreen(Screen):
|
|
|
36
45
|
# Save the default mode so we can restore to it.
|
|
37
46
|
self._default_mode = self.get_mode()
|
|
38
47
|
self._ns_screen = self.get_nsscreen()
|
|
48
|
+
self._friendly_name = "Unknown"
|
|
49
|
+
if self._ns_screen is not None:
|
|
50
|
+
screen_name = self._ns_screen.localizedName()
|
|
51
|
+
if screen_name:
|
|
52
|
+
self._friendly_name = cfstring_to_string(screen_name)
|
|
39
53
|
|
|
40
54
|
def get_nsscreen(self):
|
|
41
55
|
"""Returns the NSScreen instance that matches our CGDirectDisplayID."""
|
|
@@ -107,6 +121,16 @@ class CocoaScreen(Screen):
|
|
|
107
121
|
def release_display(self):
|
|
108
122
|
quartz.CGDisplayRelease(self._cg_display_id)
|
|
109
123
|
|
|
124
|
+
def get_display_id(self) -> str:
|
|
125
|
+
"""Get a unique identifier for the screen."""
|
|
126
|
+
return self._cg_display_id
|
|
127
|
+
|
|
128
|
+
def get_monitor_name(self) -> str:
|
|
129
|
+
"""Get a friendly name, if available.
|
|
130
|
+
|
|
131
|
+
For example, the make and model of the screen: Dell S2716DG
|
|
132
|
+
"""
|
|
133
|
+
return self._friendly_name
|
|
110
134
|
|
|
111
135
|
class CocoaScreenMode(ScreenMode):
|
|
112
136
|
|
pyglet/display/headless.py
CHANGED
|
@@ -21,7 +21,7 @@ class HeadlessDisplay(Display):
|
|
|
21
21
|
if num_devices.value > 0:
|
|
22
22
|
headless_device = pyglet.options['headless_device']
|
|
23
23
|
if headless_device < 0 or headless_device >= num_devices.value:
|
|
24
|
-
raise ValueError(f'Invalid EGL
|
|
24
|
+
raise ValueError(f'Invalid EGL device id: {headless_device}')
|
|
25
25
|
devices = (eglext.EGLDeviceEXT * num_devices.value)()
|
|
26
26
|
eglext.eglQueryDevicesEXT(num_devices.value, devices, byref(num_devices))
|
|
27
27
|
self._display_connection = eglext.eglGetPlatformDisplayEXT(
|
pyglet/display/win32.py
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
1
4
|
from ctypes import byref, sizeof
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
2
6
|
|
|
3
7
|
from pyglet.libs.win32 import _gdi32, _shcore, _user32
|
|
4
8
|
from pyglet.libs.win32.constants import (
|
|
5
9
|
CDS_FULLSCREEN,
|
|
6
10
|
DISP_CHANGE_SUCCESSFUL,
|
|
11
|
+
DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME,
|
|
12
|
+
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,
|
|
7
13
|
ENUM_CURRENT_SETTINGS,
|
|
8
14
|
LOGPIXELSX,
|
|
9
15
|
LOGPIXELSY,
|
|
16
|
+
MONITORINFOF_PRIMARY,
|
|
17
|
+
QDC_ONLY_ACTIVE_PATHS,
|
|
10
18
|
USER_DEFAULT_SCREEN_DPI,
|
|
11
19
|
WINDOWS_8_1_OR_GREATER,
|
|
12
20
|
WINDOWS_10_CREATORS_UPDATE_OR_GREATER,
|
|
@@ -15,15 +23,23 @@ from pyglet.libs.win32.constants import (
|
|
|
15
23
|
from pyglet.libs.win32.context_managers import device_context
|
|
16
24
|
from pyglet.libs.win32.types import (
|
|
17
25
|
DEVMODE,
|
|
26
|
+
DISPLAY_DEVICEW,
|
|
27
|
+
DISPLAYCONFIG_PATH_INFO,
|
|
28
|
+
DISPLAYCONFIG_SOURCE_DEVICE_NAME,
|
|
29
|
+
DISPLAYCONFIG_TARGET_DEVICE_NAME,
|
|
18
30
|
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
|
|
19
31
|
MONITORENUMPROC,
|
|
20
32
|
MONITORINFOEX,
|
|
21
33
|
PROCESS_PER_MONITOR_DPI_AWARE,
|
|
22
34
|
UINT,
|
|
35
|
+
UINT32,
|
|
23
36
|
)
|
|
24
37
|
|
|
25
38
|
from .base import Canvas, Display, Screen, ScreenMode
|
|
26
39
|
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from ctypes.wintypes import HDC, HMONITOR, LPARAM, LPRECT
|
|
42
|
+
|
|
27
43
|
|
|
28
44
|
def set_dpi_awareness() -> None:
|
|
29
45
|
"""Setting DPI varies per Windows version.
|
|
@@ -41,11 +57,19 @@ def set_dpi_awareness() -> None:
|
|
|
41
57
|
set_dpi_awareness()
|
|
42
58
|
|
|
43
59
|
|
|
44
|
-
class Win32Display(Display):
|
|
45
|
-
def
|
|
60
|
+
class Win32Display(Display): # noqa: D101
|
|
61
|
+
def get_default_screen(self) -> Screen:
|
|
62
|
+
screens = self.get_screens()
|
|
63
|
+
for screen in screens:
|
|
64
|
+
if screen.is_primary:
|
|
65
|
+
return screen
|
|
66
|
+
|
|
67
|
+
return screens[0]
|
|
68
|
+
|
|
69
|
+
def get_screens(self) -> list[Win32Screen]:
|
|
46
70
|
screens = []
|
|
47
71
|
|
|
48
|
-
def enum_proc(hMonitor, hdcMonitor, lprcMonitor, dwData):
|
|
72
|
+
def enum_proc(hMonitor: HMONITOR, hdcMonitor: HDC, lprcMonitor: LPRECT, dwData: LPARAM) -> bool: # noqa: N803, ARG001
|
|
49
73
|
r = lprcMonitor.contents
|
|
50
74
|
width = r.right - r.left
|
|
51
75
|
height = r.bottom - r.top
|
|
@@ -58,12 +82,88 @@ class Win32Display(Display):
|
|
|
58
82
|
return screens
|
|
59
83
|
|
|
60
84
|
|
|
61
|
-
class Win32Screen(Screen):
|
|
85
|
+
class Win32Screen(Screen): # noqa: D101
|
|
86
|
+
_handle: HMONITOR
|
|
62
87
|
_initial_mode = None
|
|
63
88
|
|
|
64
|
-
def __init__(self, display, handle, x, y, width, height):
|
|
89
|
+
def __init__(self, display: Win32Display, handle: HMONITOR, x: int, y: int, width: int, height: int) -> None: # noqa: D107
|
|
65
90
|
super().__init__(display, x, y, width, height)
|
|
66
91
|
self._handle = handle
|
|
92
|
+
self._device_name = self.get_device_name() # \\.\DISPLAY1
|
|
93
|
+
self._friendly_name = self._get_friendly_name()
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def is_primary(self) -> bool:
|
|
97
|
+
"""If the screen is considered the primary according to the operating system."""
|
|
98
|
+
info = self._get_monitor_info()
|
|
99
|
+
return info.dwFlags & MONITORINFOF_PRIMARY
|
|
100
|
+
|
|
101
|
+
def _get_friendly_name_display_config_api(self) -> str:
|
|
102
|
+
"""Get the friendly name of a monitor using the newer Display Configuration API.
|
|
103
|
+
|
|
104
|
+
This API is meant to replace EnumDisplayDevicesW, and should be more accurate.
|
|
105
|
+
|
|
106
|
+
Requires Windows Vista or higher.
|
|
107
|
+
"""
|
|
108
|
+
path_count = UINT32()
|
|
109
|
+
mode_count = UINT32()
|
|
110
|
+
|
|
111
|
+
result = _user32.GetDisplayConfigBufferSizes(
|
|
112
|
+
QDC_ONLY_ACTIVE_PATHS, ctypes.byref(path_count), ctypes.byref(mode_count),
|
|
113
|
+
)
|
|
114
|
+
if result != 0:
|
|
115
|
+
return "Unknown"
|
|
116
|
+
|
|
117
|
+
paths = (DISPLAYCONFIG_PATH_INFO * path_count.value)()
|
|
118
|
+
modes = ctypes.create_string_buffer(64 * mode_count.value) # dummy buffer
|
|
119
|
+
|
|
120
|
+
result = _user32.QueryDisplayConfig(
|
|
121
|
+
QDC_ONLY_ACTIVE_PATHS, ctypes.byref(path_count), paths, ctypes.byref(mode_count), modes, 0,
|
|
122
|
+
)
|
|
123
|
+
if result != 0:
|
|
124
|
+
return "Unknown"
|
|
125
|
+
|
|
126
|
+
for i in range(path_count.value):
|
|
127
|
+
path = paths[i]
|
|
128
|
+
|
|
129
|
+
source_name = DISPLAYCONFIG_SOURCE_DEVICE_NAME()
|
|
130
|
+
source_name.header.adapterId = path.sourceInfo.adapterId
|
|
131
|
+
source_name.header.id = path.sourceInfo.id
|
|
132
|
+
source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME
|
|
133
|
+
source_name.header.size = ctypes.sizeof(source_name)
|
|
134
|
+
|
|
135
|
+
result = _user32.DisplayConfigGetDeviceInfo(ctypes.byref(source_name.header))
|
|
136
|
+
if result != 0:
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
if source_name.viewGdiDeviceName != self._device_name:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
if not path.targetInfo.targetAvailable:
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
target_name = DISPLAYCONFIG_TARGET_DEVICE_NAME()
|
|
146
|
+
|
|
147
|
+
target_name.header.adapterId = path.targetInfo.adapterId
|
|
148
|
+
target_name.header.id = path.targetInfo.id
|
|
149
|
+
target_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME
|
|
150
|
+
target_name.header.size = ctypes.sizeof(target_name)
|
|
151
|
+
|
|
152
|
+
if _user32.DisplayConfigGetDeviceInfo(ctypes.byref(target_name.header)) == 0:
|
|
153
|
+
return target_name.monitorFriendlyDeviceName
|
|
154
|
+
|
|
155
|
+
return "Unknown"
|
|
156
|
+
|
|
157
|
+
def _get_friendly_name(self) -> str:
|
|
158
|
+
if WINDOWS_VISTA_OR_GREATER:
|
|
159
|
+
return self._get_friendly_name_display_config_api()
|
|
160
|
+
|
|
161
|
+
dd = DISPLAY_DEVICEW()
|
|
162
|
+
dd.cb = ctypes.sizeof(dd)
|
|
163
|
+
if _user32.EnumDisplayDevicesW(self._device_name, 0, ctypes.byref(dd), 0):
|
|
164
|
+
return dd.DeviceString
|
|
165
|
+
|
|
166
|
+
return "Unknown"
|
|
67
167
|
|
|
68
168
|
def get_matching_configs(self, template):
|
|
69
169
|
with device_context(None) as hdc:
|
|
@@ -75,13 +175,23 @@ class Win32Screen(Screen):
|
|
|
75
175
|
|
|
76
176
|
return configs
|
|
77
177
|
|
|
78
|
-
def
|
|
178
|
+
def _get_monitor_info(self) -> MONITORINFOEX:
|
|
79
179
|
info = MONITORINFOEX()
|
|
80
180
|
info.cbSize = sizeof(MONITORINFOEX)
|
|
81
181
|
_user32.GetMonitorInfoW(self._handle, byref(info))
|
|
182
|
+
return info
|
|
183
|
+
|
|
184
|
+
def get_display_id(self) -> str:
|
|
185
|
+
return self._device_name
|
|
186
|
+
|
|
187
|
+
def get_monitor_name(self) -> str:
|
|
188
|
+
return self._friendly_name
|
|
189
|
+
|
|
190
|
+
def get_device_name(self) -> str:
|
|
191
|
+
info = self._get_monitor_info()
|
|
82
192
|
return info.szDevice
|
|
83
193
|
|
|
84
|
-
def get_dpi(self):
|
|
194
|
+
def get_dpi(self) -> int:
|
|
85
195
|
if WINDOWS_8_1_OR_GREATER:
|
|
86
196
|
xdpi = UINT()
|
|
87
197
|
ydpi = UINT()
|
|
@@ -95,11 +205,11 @@ class Win32Screen(Screen):
|
|
|
95
205
|
|
|
96
206
|
return xdpi
|
|
97
207
|
|
|
98
|
-
def get_scale(self):
|
|
208
|
+
def get_scale(self) -> float:
|
|
99
209
|
xdpi = self.get_dpi()
|
|
100
210
|
return xdpi / USER_DEFAULT_SCREEN_DPI
|
|
101
211
|
|
|
102
|
-
def get_modes(self):
|
|
212
|
+
def get_modes(self) -> list[Win32ScreenMode]:
|
|
103
213
|
device_name = self.get_device_name()
|
|
104
214
|
i = 0
|
|
105
215
|
modes = []
|
|
@@ -115,7 +225,7 @@ class Win32Screen(Screen):
|
|
|
115
225
|
|
|
116
226
|
return modes
|
|
117
227
|
|
|
118
|
-
def get_mode(self):
|
|
228
|
+
def get_mode(self) -> Win32ScreenMode:
|
|
119
229
|
mode = DEVMODE()
|
|
120
230
|
mode.dmSize = sizeof(DEVMODE)
|
|
121
231
|
_user32.EnumDisplaySettingsW(self.get_device_name(),
|
|
@@ -123,7 +233,7 @@ class Win32Screen(Screen):
|
|
|
123
233
|
byref(mode))
|
|
124
234
|
return Win32ScreenMode(self, mode)
|
|
125
235
|
|
|
126
|
-
def set_mode(self, mode):
|
|
236
|
+
def set_mode(self, mode: Win32ScreenMode) -> None:
|
|
127
237
|
assert mode.screen is self
|
|
128
238
|
|
|
129
239
|
if not self._initial_mode:
|
|
@@ -137,13 +247,18 @@ class Win32Screen(Screen):
|
|
|
137
247
|
self.width = mode.width
|
|
138
248
|
self.height = mode.height
|
|
139
249
|
|
|
140
|
-
def restore_mode(self):
|
|
250
|
+
def restore_mode(self) -> None:
|
|
141
251
|
if self._initial_mode:
|
|
142
252
|
self.set_mode(self._initial_mode)
|
|
143
253
|
|
|
144
254
|
|
|
145
|
-
|
|
146
|
-
|
|
255
|
+
_win32_scale_name = {
|
|
256
|
+
0: "default",
|
|
257
|
+
1: "center",
|
|
258
|
+
2: "stretch",
|
|
259
|
+
}
|
|
260
|
+
class Win32ScreenMode(ScreenMode): # noqa: D101
|
|
261
|
+
def __init__(self, screen: Win32Screen, mode: DEVMODE) -> None: # noqa: D107
|
|
147
262
|
super().__init__(screen)
|
|
148
263
|
self._mode = mode
|
|
149
264
|
self.width = mode.dmPelsWidth
|
|
@@ -152,11 +267,12 @@ class Win32ScreenMode(ScreenMode):
|
|
|
152
267
|
self.rate = mode.dmDisplayFrequency
|
|
153
268
|
self.scaling = mode.dmDisplayFixedOutput
|
|
154
269
|
|
|
155
|
-
def __repr__(self):
|
|
156
|
-
return f'{self.__class__.__name__}(width={self.width!r}, height={self.height!r}, depth={self.depth!r},
|
|
270
|
+
def __repr__(self) -> str:
|
|
271
|
+
return (f'{self.__class__.__name__}(width={self.width!r}, height={self.height!r}, depth={self.depth!r},'
|
|
272
|
+
f'rate={self.rate}, scaling={_win32_scale_name.get(self.scaling)})')
|
|
157
273
|
|
|
158
|
-
class Win32Canvas(Canvas):
|
|
159
|
-
def __init__(self, display, hwnd, hdc):
|
|
274
|
+
class Win32Canvas(Canvas): # noqa: D101
|
|
275
|
+
def __init__(self, display: Win32Display, hwnd: HWND, hdc: HDC) -> None: # noqa: D107
|
|
160
276
|
super().__init__(display)
|
|
161
277
|
self.hwnd = hwnd
|
|
162
278
|
self.hdc = hdc
|