From 31c3d90e5da84904f6e2a5de588756c1af6ccb85 Mon Sep 17 00:00:00 2001 From: Halldor Fannar Date: Mon, 20 Apr 2026 19:25:11 +0000 Subject: [PATCH 1/2] Adjust Windows to match the Linux backend setup --- docs/source/release-history/v10.2.0.md | 18 +++++++- src/mss/base.py | 5 +- src/mss/windows/__init__.py | 63 ++++++++++++++++++++++++++ src/mss/{windows.py => windows/gdi.py} | 37 +++------------ src/tests/test_windows.py | 9 ++++ 5 files changed, 99 insertions(+), 33 deletions(-) create mode 100644 src/mss/windows/__init__.py rename src/mss/{windows.py => windows/gdi.py} (93%) diff --git a/docs/source/release-history/v10.2.0.md b/docs/source/release-history/v10.2.0.md index a8ed52d4..d6d116a9 100644 --- a/docs/source/release-history/v10.2.0.md +++ b/docs/source/release-history/v10.2.0.md @@ -155,7 +155,11 @@ Actual performance improvements will vary depending on factors such as: - hardware - whether the shared-memory capture path is available -## Windows Capture Improvements +## Windows Improvements + +Windows has received both capture-reliability improvements and a package restructure that prepares the codebase for additional backends. + +### Capture with `CreateDIBSection` The Windows screenshot implementation now uses **`CreateDIBSection`** instead of `GetDIBits`. @@ -166,6 +170,18 @@ Additional improvements include: - improved Win32 error handling and diagnostics - fixes for capture failures during extended recordings +### Package Restructure + +`mss.windows` is now a **package** rather than a single module, with a `choose_impl()` dispatcher that mirrors the `mss.linux` architecture. This prepares the project for future Windows backends (such as a DXGI-based implementation) that will sit alongside the existing GDI code. + +The existing GDI implementation has moved to `mss.windows.gdi`, but the previously-public names remain importable from `mss.windows` unchanged: + +- `mss.windows.MSS` +- `mss.windows.MSSImplWindows` +- `mss.windows.BACKENDS` + +Existing code does not need to be updated. + ## macOS Stability Fix A memory leak in the macOS backend has been fixed. diff --git a/src/mss/base.py b/src/mss/base.py index ef2c3e24..85cee1c8 100644 --- a/src/mss/base.py +++ b/src/mss/base.py @@ -158,9 +158,10 @@ def _choose_impl(**kwargs: Any) -> MSSImplementation: return choose_impl_linux(**kwargs) if os_ == "windows": - from mss.windows import MSSImplWindows # noqa: PLC0415 + from mss.windows import choose_impl as choose_impl_windows # noqa: PLC0415 - return MSSImplWindows(**kwargs) + # Windows has its own factory to choose the backend. + return choose_impl_windows(**kwargs) msg = f"System {os_!r} not (yet?) implemented." raise ScreenShotError(msg) diff --git a/src/mss/windows/__init__.py b/src/mss/windows/__init__.py new file mode 100644 index 00000000..e5cf3781 --- /dev/null +++ b/src/mss/windows/__init__.py @@ -0,0 +1,63 @@ +"""Microsoft Windows backend dispatcher for MSS screenshot implementations.""" + +from __future__ import annotations + +import warnings +from typing import Any + +from mss.base import MSS as _MSS +from mss.base import MSSImplementation +from mss.exception import ScreenShotError + +# Re-export the GDI implementation so existing code that references +# ``mss.windows.MSSImplWindows`` keeps working. +from mss.windows.gdi import MSSImplWindows + +__all__ = ["MSS"] + +BACKENDS = ["default"] + + +class MSS(_MSS): + """Deprecated Microsoft Windows compatibility constructor. + + Use :class:`mss.MSS` instead. + """ + + def __init__(self, /, **kwargs: Any) -> None: + # TODO(jholveck): #493 Remove compatibility constructor after 10.x transition period. + warnings.warn( + "mss.windows.MSS is deprecated and will be removed in 11.0; use mss.MSS instead", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(**kwargs) + + +def choose_impl(backend: str = "default", **kwargs: Any) -> MSSImplementation: + """Return a backend-specific MSS implementation for Microsoft Windows. + + Selects and instantiates the appropriate Windows backend based on the + ``backend`` parameter. + + :param backend: Backend selector. Valid values: + + - ``"default"`` (default): GDI-based backend using ``BitBlt`` and + ``CreateDIBSection`` for direct memory access to pixel data. + + .. versionadded:: 10.3.0 Prior to this version, Windows had a single + implementation selected through :class:`mss.windows.MSS`. + + :param kwargs: Additional keyword arguments passed to the backend class. + :returns: An MSS backend implementation. + + .. versionadded:: 10.3.0 Prior to this version, this didn't exist: + Windows had a single implementation selected through + :class:`mss.windows.MSS`. + """ + backend = backend.lower() + if backend == "default": + return MSSImplWindows(**kwargs) + assert backend not in BACKENDS # noqa: S101 + msg = f"Backend {backend!r} not (yet?) implemented." + raise ScreenShotError(msg) diff --git a/src/mss/windows.py b/src/mss/windows/gdi.py similarity index 93% rename from src/mss/windows.py rename to src/mss/windows/gdi.py index 0db68173..60102024 100644 --- a/src/mss/windows.py +++ b/src/mss/windows/gdi.py @@ -1,4 +1,4 @@ -"""Windows GDI-based backend for MSS. +"""GDI-based backend for MSS on Microsoft Windows. Uses user32/gdi32 APIs to capture the desktop and enumerate monitors. This implementation uses CreateDIBSection for direct memory access to pixel data. @@ -8,7 +8,6 @@ import ctypes import sys -import warnings from ctypes import POINTER, WINFUNCTYPE, Structure, WinError, _Pointer from ctypes.wintypes import ( BOOL, @@ -31,7 +30,6 @@ ) from typing import TYPE_CHECKING -from mss.base import MSS as _MSS from mss.base import MSSImplementation from mss.exception import ScreenShotError @@ -40,25 +38,7 @@ from mss.models import CFunctionsErrChecked, Monitor, Monitors -__all__ = ("MSS",) - -BACKENDS = ["default"] - - -class MSS(_MSS): - """Deprecated Windows compatibility constructor. - - Use :class:`mss.MSS` instead. - """ - - def __init__(self, /, **kwargs: Any) -> None: - # TODO(jholveck): #493 Remove compatibility constructor after 10.x transition period. - warnings.warn( - "mss.windows.MSS is deprecated and will be removed in 11.0; use mss.MSS instead", - DeprecationWarning, - stacklevel=2, - ) - super().__init__(**kwargs) +__all__ = ("MSSImplWindows",) LPCRECT = POINTER(RECT) # Actually a const pointer, but ctypes has no const. @@ -128,7 +108,7 @@ class DISPLAY_DEVICEW(Structure): # noqa: N801 MONITORNUMPROC = WINFUNCTYPE(BOOL, HMONITOR, HDC, POINTER(RECT), LPARAM) -def _errcheck(result: BOOL | _Pointer, func: Callable, arguments: tuple) -> tuple: +def _errcheck(result: int | _Pointer, func: Callable, arguments: tuple) -> tuple: """If the result is zero, raise an exception.""" if not result: # Notably, the errno that is in winerror may not be relevant. Use the winerror and strerror attributes @@ -183,13 +163,14 @@ def _errcheck(result: BOOL | _Pointer, func: Callable, arguments: tuple) -> tupl class MSSImplWindows(MSSImplementation): - """Multiple ScreenShots implementation for Microsoft Windows. + """Multiple ScreenShots implementation for Microsoft Windows (GDI backend). This implementation uses CreateDIBSection for direct memory access to pixel data, which eliminates the need for GetDIBits. The DIB pixel data is written directly to system-managed memory that we can read from. - This has no Windows-specific constructor parameters. + This backend is selected by ``backend="default"`` and has no Windows-specific + constructor parameters. .. seealso:: @@ -209,13 +190,9 @@ class MSSImplWindows(MSSImplementation): "user32", } - def __init__(self, *, backend: str = "default") -> None: + def __init__(self) -> None: super().__init__() - if backend != "default": - msg = 'The only valid backend on this platform is "default".' - raise ScreenShotError(msg) - # user32 and gdi32 should not be changed after initialization. self.user32 = ctypes.WinDLL("user32", use_last_error=True) self.gdi32 = ctypes.WinDLL("gdi32", use_last_error=True) diff --git a/src/tests/test_windows.py b/src/tests/test_windows.py index 27211c19..d0f7da67 100644 --- a/src/tests/test_windows.py +++ b/src/tests/test_windows.py @@ -9,6 +9,7 @@ import pytest import mss +from mss.exception import ScreenShotError try: import mss.windows @@ -16,6 +17,14 @@ pytestmark = pytest.mark.skip +def test_choose_impl_unknown_backend_raises() -> None: + """``choose_impl()`` must reject backends that are not registered in ``BACKENDS``.""" + bogus = "definitely-not-a-real-backend" + + with pytest.raises(ScreenShotError, match=bogus): + mss.windows.choose_impl(backend=bogus) + + def test_region_caching() -> None: """The region to grab is cached, ensure this is well-done.""" with mss.MSS() as sct: From 77afe8ad2912d9a15df8f4d42db0213d2e7c7a00 Mon Sep 17 00:00:00 2001 From: Halldor Fannar Date: Mon, 20 Apr 2026 20:08:56 +0000 Subject: [PATCH 2/2] Fix leftover references to removed windows.py --- src/tests/test_setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tests/test_setup.py b/src/tests/test_setup.py index 5b63bdde..943a319f 100644 --- a/src/tests/test_setup.py +++ b/src/tests/test_setup.py @@ -84,7 +84,8 @@ def test_sdist() -> None: f"mss-{__version__}/src/mss/py.typed", f"mss-{__version__}/src/mss/screenshot.py", f"mss-{__version__}/src/mss/tools.py", - f"mss-{__version__}/src/mss/windows.py", + f"mss-{__version__}/src/mss/windows/__init__.py", + f"mss-{__version__}/src/mss/windows/gdi.py", f"mss-{__version__}/src/tests/__init__.py", f"mss-{__version__}/src/tests/bench_bgra2rgb.py", f"mss-{__version__}/src/tests/bench_general.py", @@ -157,5 +158,6 @@ def test_wheel() -> None: "mss/py.typed", "mss/screenshot.py", "mss/tools.py", - "mss/windows.py", + "mss/windows/__init__.py", + "mss/windows/gdi.py", ]