Skip to content

Move to a strategy implementation design#9

Draft
jholveck wants to merge 13 commits intomainfrom
strategy
Draft

Move to a strategy implementation design#9
jholveck wants to merge 13 commits intomainfrom
strategy

Conversation

@jholveck
Copy link
Copy Markdown
Owner

@jholveck jholveck commented Apr 8, 2026

See also BoboTiG/python-mss issue BoboTiG#486.

The user will always work with a single class: mss.MSS. Differences in implementation, such as platform or capture strategy, are hidden in an internal implementation object held by the mss.MSS object.

This allows us to change the implementation, with arbitrary class hierarchies, without worrying about preserving compatibility with internal class names.

This deprecates the existing mss factory function, although it can easily be kept for as long as needed to give users time to adapt.

It also deprecates the existing mss.{platform}.MSS types. These are exposed to the user, so somebody calling mss.{platform}.MSS() in 10.x can still reasonably expect to get a mss.{platform}.MSS object back. However, in 11.0, we can remove the type entirely, and either remove those symbols, or make them deprecated aliases for mss.MSS.

Where possible, deprecated functionality emits a DeprecationWarning. However, note that these are ignored by default, unless triggered by code in main.

Many of the API docs are removed, since this change removes much of the API surface. However, they are still in available for backwards-compatibility.

This change adds tests for everything that was in the 10.1 docs, examples, etc, at least at a basic level: for instance, it tests that mss.linux.MSS still works as both a constructor and a type (for isinstance), and that mss.linux.ZPIXMAP still exists (it was listed in the 10.1 docs).

The existing code, tests, and docs are changed to use mss.MSS.

--

As a contextual note, 10.1 (the most recent release) had only a single Linux backend, one based on Xlib.

One major change in 10.2 is the idea of us managing different backends, such as some that might be based on XComposite or capture to CUDA or do other things that require very different implementations. Another use case for multiple backends would be the different screen capture APIs that Windows exposes, which may or may not be applicable depending on the Windows version and use case.

Originally, I added the new Linux XCB-based backends (one using XShmGetImage and one using XGetImage), and made mss.linux.MSS - previously the class that was the only Linux implementation - be a factory function. But then I realized that this would break type declarations.

As I considered this further, I realized that a larger shift may be necessary, to allow us to have a consistent, stable API, while we change the internals. Rather than having the implementations be classes that inherit from the base class, it would make more sense to use a strategy pattern.

One important thing about this history lesson: the new Linux backends have not yet been part of a public release. That also includes the mss.linux.mss factory function that existed in main prior to this change; we can safely remove that, since it was never in a public release.

Summary by CodeRabbit

  • New Features

    • Introduced new stable public API mss.MSS() as the recommended screen capture interface
    • Added performance_status property for backend health monitoring
  • Documentation

    • Updated all examples and guides to demonstrate the new mss.MSS() API usage
  • Deprecations

    • Legacy mss.mss() factory function deprecated in favor of mss.MSS(); still available in v10.2 for compatibility

jholveck and others added 13 commits March 30, 2026 20:49
See also BoboTiG/python-mss issue BoboTiG#486.

The user will always work with a single class: mss.MSS.  Differences
in implementation, such as platform or capture strategy, are hidden in
an internal implementation object held by the mss.MSS object.

This allows us to change the implementation, with arbitrary class
hierarchies, without worrying about preserving compatibility with
internal class names.

This deprecates the existing `mss` factory function, although it can
easily be kept for as long as needed to give users time to adapt.

It also deprecates the existing `mss.{platform}.MSS` types.  These are
exposed to the user, so somebody calling `mss.{platform}.MSS()` in
10.x can still reasonably expect to get a `mss.{platform}.MSS` object
back.  However, in 11.0, we can remove the type entirely, and either
remove those symbols, or make them deprecated aliases for `mss.MSS`.

Where possible, deprecated functionality emits a `DeprecationWarning`.
However, note that these are ignored by default, unless triggered by
code in `__main__`.

Many of the API docs are removed, since this change removes much of
the API surface.  However, they are still in available for
backwards-compatibility.

This change adds tests for everything that was in the 10.1 docs,
examples, etc, at least at a basic level: for instance, it tests that
`mss.linux.MSS` still works as both a constructor and a type (for
`isinstance`), and that `mss.linux.ZPIXMAP` still exists (it was
listed in the 10.1 docs).

The existing code, tests, and docs are changed to use `mss.MSS`.
The mss_impl fixture would add an implicit display= argument,
regardless of platform.  The code at that time would ignore it, but we
should be (and in the previous commit, were) more strict.  Change
mss_impl to only use display= if appropriate, so we can be more strict
in the future.

In 10.1, these were allowed at all times, and ignored if the platform
didn't use them.  Emulate this behavior in mss.MSS (and mss.mss), with
DeprecationWarnings, and test.
I'm pretty sure it's unnecessary there.  Not sure why it was being done.
This lets IDEs and code checkers know what is acceptable.  This lets
them autocomplete, and identify incorrect options in user code.
Co-authored-by: Mickaël Schoentgen <contact@tiger-222.fr>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a new stable public API mss.MSS replacing the legacy mss.mss() factory function while maintaining backward compatibility. Platform-specific implementations are refactored to subclass a new MSSImplementation ABC, with a unified dispatcher replacing conditional imports. Documentation and all examples are updated to use the new API.

Changes

Cohort / File(s) Summary
Documentation & Changelog
CHANGELOG.md, CHANGES.md, README.md, docs/source/api.rst, docs/source/conf.py, docs/source/examples.rst, docs/source/index.rst, docs/source/usage.rst
Updated API references from mss.mss() to mss.MSS(), documented new unified public API, removed references to deprecated internal backend modules, marked legacy factory as deprecated.
Demo & Example Files
demos/cat-detector.py, demos/tinytv-stream-simple.py, demos/tinytv-stream.py, demos/video-capture-simple.py, demos/video-capture.py, docs/source/examples/*
Replaced all mss.mss() instantiations with mss.MSS() constructor calls throughout example code.
Core API & Public Exports
src/mss/__init__.py, src/mss/factory.py
Added MSS and ScreenShot to public exports; simplified deprecated factory to emit warning and delegate to new MSS class.
Base Implementation Architecture
src/mss/base.py
Introduced new MSSImplementation ABC and _choose_impl() dispatcher; transformed MSS into unified public wrapper class with platform detection and delegated method calls; added _PlatformSpecific sentinel for platform-specific kwargs; deprecated platform-specific kwargs now emit warnings; added MSSBase = MSS compatibility alias.
CLI Module
src/mss/__main__.py
Replaced factory usage with direct MSS(**kwargs) instantiation; refactored platform-specific argument handling with deprecation warnings for unsupported platforms; restructured screenshot argument assembly.
macOS Implementation
src/mss/darwin.py
Renamed implementation class to MSSImplDarwin(MSSImplementation) with adjusted constructor and method signatures; added deprecated MSS wrapper class; changed grab/monitor/cursor methods to return raw data instead of ScreenShot objects.
Windows Implementation
src/mss/windows.py
Renamed implementation class to MSSImplWindows(MSSImplementation) with explicit backend validation; replaced internal method hooks with public method overrides; added deprecated MSS wrapper; changed grab/monitors methods to return raw data.
Linux Base & Implementations
src/mss/linux/__init__.py, src/mss/linux/base.py, src/mss/linux/xlib.py, src/mss/linux/xgetimage.py, src/mss/linux/xshmgetimage.py
Introduced choose_impl() dispatcher and MSSImplXlib/MSSImplXCBBase/MSSImplXGetImage/MSSImplXShmGetImage implementation classes; refactored monitor enumeration from mutation to returns; changed grab methods to return raw bytearray; added deprecated MSS wrappers; updated method signatures and cursor handling.
Test Infrastructure & Compatibility
src/tests/conftest.py, src/tests/test_compat_10_1.py, src/tests/test_compat_exports.py, src/tests/test_compat_linux_api.py, src/tests/test_setup.py
Updated fixture to use MSS instead of factory; added new compatibility test modules validating deprecated 10.1 APIs emit warnings; updated build test to include new test files.
Core Test Updates
src/tests/test_*.py, src/tests/third_party/test_*.py, src/tests/bench_*.py
Updated type annotations from MSSBase to MSS across all test files; replaced mss.mss() with mss.MSS() in test instantiations; updated implementation assertions to reference _impl attribute; adjusted mock patches to target new implementation class names.

Sequence Diagram(s)

sequenceDiagram
    participant User as User Code
    participant MSS as mss.MSS (Public API)
    participant Dispatcher as _choose_impl()
    participant Impl as MSSImplementation<br/>(Platform-Specific)
    participant System as Platform Layer

    User->>MSS: MSS(backend=?, display=?, ...)
    activate MSS
    MSS->>MSS: __init__(validate platform-specific kwargs)
    MSS->>Dispatcher: _choose_impl(platform, kwargs)
    activate Dispatcher
    Dispatcher->>Dispatcher: detect platform via platform.system()
    Dispatcher->>Impl: instantiate MSSImplDarwin/Linux/Windows
    Dispatcher-->>MSS: return MSSImplementation instance
    deactivate Dispatcher
    MSS->>MSS: store as self._impl
    deactivate MSS

    User->>MSS: with MSS() as sct:
    User->>MSS: sct.grab(monitor)
    activate MSS
    MSS->>Impl: self._impl.grab(monitor)
    activate Impl
    Impl->>System: platform-specific capture (CTCore/X11/Win32)
    System-->>Impl: raw pixel bytearray + Size
    Impl-->>MSS: return (bytearray, Size)
    deactivate Impl
    MSS->>MSS: construct ScreenShot(data, region)
    MSS-->>User: ScreenShot object
    deactivate MSS

    User->>MSS: sct.monitors
    activate MSS
    MSS->>Impl: self._impl.monitors()
    activate Impl
    Impl->>System: query available displays
    System-->>Impl: monitor list
    Impl->>Impl: build Monitors dict
    Impl-->>MSS: return Monitors
    deactivate Impl
    MSS-->>User: Monitors (cached)
    deactivate MSS
Loading
sequenceDiagram
    participant User as Legacy Code<br/>(v10.1 API)
    participant Factory as mss.mss()<br/>(Deprecated Factory)
    participant PublicMSS as mss.MSS<br/>(New Public)
    participant Compat as Deprecated Wrappers<br/>(platform.MSS)

    User->>Factory: mss.mss(display=?, backend=?)
    activate Factory
    Note over Factory: Emit DeprecationWarning<br/>(stacklevel=2)
    Factory->>PublicMSS: MSS(**kwargs)
    activate PublicMSS
    PublicMSS-->>Factory: return MSS instance
    deactivate PublicMSS
    Factory-->>User: return MSS (as MSSBase type)
    deactivate Factory

    User->>Compat: mss.linux.MSS() or<br/>mss.darwin.MSS()
    activate Compat
    Note over Compat: Emit DeprecationWarning<br/>(platform-specific wrapper)
    Compat->>PublicMSS: call super().__init__()
    PublicMSS-->>Compat: initialized
    Compat-->>User: return MSS instance
    deactivate Compat

    Note over User,Factory: Both paths return compatible<br/>MSS objects with same interface<br/>MSSBase is now alias for MSS
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hop, hop, refactoring delight!
The MSS rabbit claims new height,
Old mss.mss() bids farewell with grace,
While MSS takes center place,
Platform-specific backends aligned,
A cleaner API, perfectly designed! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Move to a strategy implementation design' directly and concisely describes the primary architectural change introduced in this changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch strategy

Comment @coderabbitai help to get the list of available commands and usage tips.

@jholveck
Copy link
Copy Markdown
Owner Author

jholveck commented Apr 8, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/mss/linux/base.py (1)

427-435: ⚠️ Potential issue | 🟡 Minor

Update _grab_xgetimage()'s return contract.

The helper now returns raw pixel bytes, but the docstring still says it returns a screenshot object. That mismatch will mislead the next implementation that reuses this fallback.

📝 Proposed doc fix
-        :returns: A screenshot object containing the captured region.
+        :returns: Raw pixel bytes for the captured region.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mss/linux/base.py` around lines 427 - 435, The docstring for
_grab_xgetimage(Monitor) is outdated: the function now returns raw pixel bytes
(bytearray) instead of a screenshot object; update the docstring to state it
returns a bytearray of raw pixel data (include pixel format/stride/order if
known) and describe the shape/ordering (e.g., RGB/RGBA per pixel, row-major,
bytes per pixel) so callers using _grab_xgetimage or the XShmGetImage fallback
know how to interpret the returned bytearray.
🧹 Nitpick comments (10)
docs/source/examples/fps.py (1)

43-43: Use context manager for resource cleanup and ensure documentation consistency.

The fps.py example is the only one in the documentation that doesn't use the recommended with mss.MSS() as sct: pattern. All other examples follow this pattern for proper resource management. The web search confirms that context managers are the recommended approach to ensure close() is called via __exit__, even if an exception occurs.

Apply the refactor to move the while loop inside the context manager:

Proposed refactor
-    sct = mss.MSS()
-    last_time = time.time()
-
-    while time.time() - last_time < 1:
-        img = np.asarray(sct.grab(mon))
-        fps += 1
-
-        cv2.imshow(title, img)
-        if cv2.waitKey(25) & 0xFF == ord("q"):
-            cv2.destroyAllWindows()
-            break
+    last_time = time.time()
+    with mss.MSS() as sct:
+        while time.time() - last_time < 1:
+            img = np.asarray(sct.grab(mon))
+            fps += 1
+
+            cv2.imshow(title, img)
+            if cv2.waitKey(25) & 0xFF == ord("q"):
+                cv2.destroyAllWindows()
+                break
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/source/examples/fps.py` at line 43, The fps.py example creates the MSS
instance without a context manager (sct = mss.MSS()), so refactor to use the
recommended context-manager pattern: replace the standalone sct = mss.MSS() with
a with mss.MSS() as sct: block and move the existing while loop and any code
that uses sct inside that block so the MSS instance is automatically closed via
__exit__; update any related variable scope accordingly to match other examples.
CHANGES.md (1)

6-6: Spell out the deprecated entry points here.

This entry introduces mss.MSS, but it currently reads like an additive API note. The migration action users need is that mss.mss() and mss.{platform}.MSS are now deprecated compatibility entry points.

📝 Suggested wording
-- (This affected almost every file in some respect, but base.py was the most affected.)  Introduced a new API, mss.MSS.  This class can be used instead of the previous various MSSBase subclasses, so that the user can work with a consistent class regardless of implementation details.  The methods implemented by MSSBase subclasses were renamed, and moved to MSSImplementation subclasses.  The mss.MSS class now holds an MSSImplementation instance as an instance variable. (`#486`, `#494`)
+- (This affected almost every file in some respect, but base.py was the most affected.) Introduced `mss.MSS` as the stable constructor. `mss.mss()` and `mss.{platform}.MSS` remain available as deprecated compatibility entry points during the 10.x transition. Internally, `mss.MSS` now delegates to an `MSSImplementation` instance. (`#486`, `#494`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGES.md` at line 6, Update the changelog text to explicitly list the
deprecated compatibility entry points and the recommended replacement: state
that the old factory function mss.mss() and any platform-specific classes
mss.<platform>.MSS are deprecated and that users should migrate to the new
mss.MSS class which now holds an MSSImplementation instance (the renaming from
MSSBase methods to MSSImplementation methods). Mention that base.py was most
affected and include a short migration note: replace calls to mss.mss() or
mss.<platform>.MSS with creating an instance of mss.MSS instead.
src/tests/test_windows.py (1)

21-23: Don’t bake the default backend into these cache tests.

These cases are really testing the GDI implementation via _impl._dib, but they start from mss.MSS() and then assert that the default implementation is MSSImplWindows. That makes the tests fail as soon as Windows gets another backend, which is exactly the flexibility this strategy refactor is trying to unlock. Pin the concrete implementation under test instead of treating the default _impl choice as part of the contract.

Also applies to: 44-50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tests/test_windows.py` around lines 21 - 23, The tests currently
instantiate the default mss.MSS() and assert that sct._impl is
mss.windows.MSSImplWindows, which couples the tests to the default backend;
instead, import and instantiate the concrete implementation
mss.windows.MSSImplWindows (or otherwise construct the concrete Windows backend
object used in the test) and run the same assertions against that concrete
instance (e.g., check its _dib behavior) so the tests no longer depend on MSS()
choosing a particular default; apply the same change to the other similar block
mentioned (the assertions around lines 44-50) so both tests pin the concrete
Windows implementation rather than asserting MSS()'s default.
src/tests/bench_grab_windows.py (1)

112-123: Pin this benchmark to the GDI backend explicitly.

benchmark_raw_bitblt() is measuring GDI internals (_srcdc/_memdc), but it still starts from mss.MSS() and assumes the default Windows backend is MSSImplWindows. Once backend selection changes, this either breaks or benchmarks the wrong implementation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tests/bench_grab_windows.py` around lines 112 - 123, The
benchmark_raw_bitblt test currently does mss.MSS() and assumes the default
backend; instead instantiate the MSS with the Windows/GDI backend explicitly so
sct._impl is guaranteed to be mss.windows.MSSImplWindows and the measured fields
(_srcdc, _memdc) are valid; update the code that creates sct (replace the
mss.MSS() call) to construct or pass the Windows backend implementation
(MSSImplWindows) so the test always pins to the GDI backend before calling
sct.grab(...) and accessing sct._impl._srcdc/_memdc.
src/mss/linux/xgetimage.py (1)

21-24: Minor: Update docstring cross-reference.

The docstring references mss.linux.base.MSSXCBBase but the class now inherits from MSSImplXCBBase. Consider updating the seealso reference for consistency.

📝 Suggested docstring update
     """XCB backend using XGetImage requests on GNU/Linux.

     .. seealso::
-        :py:class:`mss.linux.base.MSSXCBBase`
+        :py:class:`mss.linux.base.MSSImplXCBBase`
             Lists constructor parameters.
     """
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mss/linux/xgetimage.py` around lines 21 - 24, The docstring's seealso
reference points to mss.linux.base.MSSXCBBase but the class now inherits from
MSSImplXCBBase; update the cross-reference in the docstring to
:py:class:`mss.linux.base.MSSImplXCBBase` (or the correct import path for
MSSImplXCBBase) so the seealso matches the actual base class used by the class
in src/mss/linux/xgetimage.py.
src/tests/test_gnu_linux.py (1)

80-102: Consider narrowing the accepted exception types after investigation.

The TODO comment indicates this needs review before the final PR. Accepting (ImportError, ScreenShotError) for Windows (line 100) and (ScreenShotError, ValueError) for macOS (line 94) is broad and could mask unexpected failure modes.

Once the cross-platform behavior is confirmed, consider documenting the specific exception each platform raises and narrowing these back to single types if possible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tests/test_gnu_linux.py` around lines 80 - 102, Update the
platform-specific tests to assert a single, specific exception per OS rather
than broad tuples: for the macOS branch replace pytest.raises((ScreenShotError,
ValueError)) with the single confirmed exception type raised by mss.MSS on
Darwin (e.g., ScreenShotError or ValueError) and for the Windows branch replace
pytest.raises((ImportError, ScreenShotError)) with the single confirmed
exception (e.g., ImportError or ScreenShotError); locate these in the test where
monkeypatch.setattr(platform, "system", ...) and mss.MSS(backend=backend) are
used, run the cross-platform behavior check to determine the exact exception
type, update the assertions accordingly, and remove or update the TODO comment
once the expected exceptions are narrowed and documented.
src/mss/darwin.py (1)

267-269: Explicit return None would be clearer.

Same as the Windows implementation - using return None explicitly would match the declared return type.

Suggested change
     def cursor(self) -> None:
         """Retrieve all cursor data. Pixels have to be RGB."""
-        return
+        return None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mss/darwin.py` around lines 267 - 269, The cursor() method currently ends
with an implicit return; change it to an explicit return None to match its
declared return type and mirror the Windows implementation—update the cursor
function in class/method named cursor() to end with "return None" so the intent
and type annotation (-> None) are clear.
src/mss/windows.py (1)

413-415: Explicit return None would be clearer.

While return implicitly returns None, using return None explicitly would match the declared return type and improve readability.

Suggested change
     def cursor(self) -> None:
         """Retrieve all cursor data. Pixels have to be RGB."""
-        return
+        return None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mss/windows.py` around lines 413 - 415, The cursor method currently uses
a bare "return" which implicitly returns None; update the cursor() function to
use an explicit "return None" to match its annotated return type and improve
readability (replace the existing "return" statement in def cursor(self) ->
None: with "return None").
docs/source/usage.rst (1)

153-155: Minor inconsistency in CLI help text.

The documented -b, --backend BACKEND line shows only -b but the actual argparse definition at line 63 of __main__.py shows -b, --backend. The multi-line help text format here differs slightly from argparse's default output format. Consider verifying this matches actual --help output.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/source/usage.rst` around lines 153 - 155, The CLI help text in usage.rst
is inconsistent with the argparse definition: update the documented option line
to exactly match the argparse flag names and formatting (i.e., include both "-b,
--backend BACKEND") and ensure the following multi-line description matches
argparse's wrapped help (platform-specific backend to use (Linux:
default/xlib/xgetimage/xshmgetimage; macOS/Windows: default)); locate the
argparse option definition for backend in __main__.py (the parser.add_argument
call for "-b/--backend") and mirror its flag names and help text/line-wrapping
in the usage.rst entry so the docs match the actual --help output.
src/mss/linux/xlib.py (1)

451-454: Redundant condition check.

Since effective_with_cursor is already requested_with_cursor and bool(_XFIXES), the if _XFIXES is not None check inside the conditional load is redundant. The outer if effective_with_cursor already guarantees _XFIXES is truthy.

Simplify the conditional
         if effective_with_cursor:
-            # MyPy doesn't quite realize that won't be called if _XFIXES is None.
             #: :meta private:
-            self.xfixes = cdll.LoadLibrary(_XFIXES) if _XFIXES is not None else None
+            self.xfixes = cdll.LoadLibrary(_XFIXES)  # type: ignore[arg-type]
+        else:
+            self.xfixes = None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mss/linux/xlib.py` around lines 451 - 454, The code redundantly checks
_XFIXES inside the effective_with_cursor branch; update the block in xlib so
when effective_with_cursor is true you directly call cdll.LoadLibrary(_XFIXES)
to set self.xfixes (remove the inner "if _XFIXES is not None" check), keeping
the outer condition that uses effective_with_cursor (which is defined as
requested_with_cursor and bool(_XFIXES)); ensure you still satisfy type-checking
(MyPy) by preserving the same branch so the loader only executes when _XFIXES is
guaranteed truthy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/mss/base.py`:
- Around line 525-533: The max_displays property currently accesses
self._impl.max_displays unconditionally which will raise AttributeError on
non-macOS implementations; modify the property (max_displays) to perform a
runtime guard (e.g. check platform.system() == "Darwin" or
isinstance(self._impl, MSSImplDarwin)) and only return self._impl.max_displays
for macOS, otherwise raise a clear NotImplementedError/PlatformError with a
message like "max_displays is only available on macOS (MSSImplDarwin)"; keep the
docstring but enforce the platform check to avoid AttributeError when self._impl
is MSSImplWindows or MSSImplXlib.

In `@src/mss/linux/xshmgetimage.py`:
- Line 74: The code currently appends healthy-state messages to
self.performance_status (e.g., the self.performance_status.append(full_msg)
call), making the list ambiguous; change the logic so performance_status only
records issues: remove or conditionalize any append of non-error/healthy
messages (including the other occurrences around the block at lines ~201-203)
and only append when a real degraded/error condition is detected (preserve error
message content and use the same identifier full_msg when adding). If you prefer
an audit trail instead, rename performance_status to something like
performance_events and update callers accordingly, but do not leave both healthy
and issue entries in performance_status.

In `@src/tests/test_compat_10_1.py`:
- Around line 160-167: The nested pytest.warns should be made platform-aware:
import platform and compute expected_count = {"Linux":1,"Darwin":2,"Windows":3}.
Replace the two nested pytest.warns blocks around mss.mss(**kwargs) with a
single pytest.warns(DeprecationWarning) as w (or use warnings.catch_warnings)
when calling context = mss.mss(**kwargs), then assert that one
DeprecationWarning matches r"^mss\.mss is deprecated" and that the number of
warnings whose message contains "is only available on" equals
expected_count[platform.system()]; keep the subsequent with context as sct and
isinstance(sct, MSSBase) check unchanged.

---

Outside diff comments:
In `@src/mss/linux/base.py`:
- Around line 427-435: The docstring for _grab_xgetimage(Monitor) is outdated:
the function now returns raw pixel bytes (bytearray) instead of a screenshot
object; update the docstring to state it returns a bytearray of raw pixel data
(include pixel format/stride/order if known) and describe the shape/ordering
(e.g., RGB/RGBA per pixel, row-major, bytes per pixel) so callers using
_grab_xgetimage or the XShmGetImage fallback know how to interpret the returned
bytearray.

---

Nitpick comments:
In `@CHANGES.md`:
- Line 6: Update the changelog text to explicitly list the deprecated
compatibility entry points and the recommended replacement: state that the old
factory function mss.mss() and any platform-specific classes mss.<platform>.MSS
are deprecated and that users should migrate to the new mss.MSS class which now
holds an MSSImplementation instance (the renaming from MSSBase methods to
MSSImplementation methods). Mention that base.py was most affected and include a
short migration note: replace calls to mss.mss() or mss.<platform>.MSS with
creating an instance of mss.MSS instead.

In `@docs/source/examples/fps.py`:
- Line 43: The fps.py example creates the MSS instance without a context manager
(sct = mss.MSS()), so refactor to use the recommended context-manager pattern:
replace the standalone sct = mss.MSS() with a with mss.MSS() as sct: block and
move the existing while loop and any code that uses sct inside that block so the
MSS instance is automatically closed via __exit__; update any related variable
scope accordingly to match other examples.

In `@docs/source/usage.rst`:
- Around line 153-155: The CLI help text in usage.rst is inconsistent with the
argparse definition: update the documented option line to exactly match the
argparse flag names and formatting (i.e., include both "-b, --backend BACKEND")
and ensure the following multi-line description matches argparse's wrapped help
(platform-specific backend to use (Linux: default/xlib/xgetimage/xshmgetimage;
macOS/Windows: default)); locate the argparse option definition for backend in
__main__.py (the parser.add_argument call for "-b/--backend") and mirror its
flag names and help text/line-wrapping in the usage.rst entry so the docs match
the actual --help output.

In `@src/mss/darwin.py`:
- Around line 267-269: The cursor() method currently ends with an implicit
return; change it to an explicit return None to match its declared return type
and mirror the Windows implementation—update the cursor function in class/method
named cursor() to end with "return None" so the intent and type annotation (->
None) are clear.

In `@src/mss/linux/xgetimage.py`:
- Around line 21-24: The docstring's seealso reference points to
mss.linux.base.MSSXCBBase but the class now inherits from MSSImplXCBBase; update
the cross-reference in the docstring to
:py:class:`mss.linux.base.MSSImplXCBBase` (or the correct import path for
MSSImplXCBBase) so the seealso matches the actual base class used by the class
in src/mss/linux/xgetimage.py.

In `@src/mss/linux/xlib.py`:
- Around line 451-454: The code redundantly checks _XFIXES inside the
effective_with_cursor branch; update the block in xlib so when
effective_with_cursor is true you directly call cdll.LoadLibrary(_XFIXES) to set
self.xfixes (remove the inner "if _XFIXES is not None" check), keeping the outer
condition that uses effective_with_cursor (which is defined as
requested_with_cursor and bool(_XFIXES)); ensure you still satisfy type-checking
(MyPy) by preserving the same branch so the loader only executes when _XFIXES is
guaranteed truthy.

In `@src/mss/windows.py`:
- Around line 413-415: The cursor method currently uses a bare "return" which
implicitly returns None; update the cursor() function to use an explicit "return
None" to match its annotated return type and improve readability (replace the
existing "return" statement in def cursor(self) -> None: with "return None").

In `@src/tests/bench_grab_windows.py`:
- Around line 112-123: The benchmark_raw_bitblt test currently does mss.MSS()
and assumes the default backend; instead instantiate the MSS with the
Windows/GDI backend explicitly so sct._impl is guaranteed to be
mss.windows.MSSImplWindows and the measured fields (_srcdc, _memdc) are valid;
update the code that creates sct (replace the mss.MSS() call) to construct or
pass the Windows backend implementation (MSSImplWindows) so the test always pins
to the GDI backend before calling sct.grab(...) and accessing
sct._impl._srcdc/_memdc.

In `@src/tests/test_gnu_linux.py`:
- Around line 80-102: Update the platform-specific tests to assert a single,
specific exception per OS rather than broad tuples: for the macOS branch replace
pytest.raises((ScreenShotError, ValueError)) with the single confirmed exception
type raised by mss.MSS on Darwin (e.g., ScreenShotError or ValueError) and for
the Windows branch replace pytest.raises((ImportError, ScreenShotError)) with
the single confirmed exception (e.g., ImportError or ScreenShotError); locate
these in the test where monkeypatch.setattr(platform, "system", ...) and
mss.MSS(backend=backend) are used, run the cross-platform behavior check to
determine the exact exception type, update the assertions accordingly, and
remove or update the TODO comment once the expected exceptions are narrowed and
documented.

In `@src/tests/test_windows.py`:
- Around line 21-23: The tests currently instantiate the default mss.MSS() and
assert that sct._impl is mss.windows.MSSImplWindows, which couples the tests to
the default backend; instead, import and instantiate the concrete implementation
mss.windows.MSSImplWindows (or otherwise construct the concrete Windows backend
object used in the test) and run the same assertions against that concrete
instance (e.g., check its _dib behavior) so the tests no longer depend on MSS()
choosing a particular default; apply the same change to the other similar block
mentioned (the assertions around lines 44-50) so both tests pin the concrete
Windows implementation rather than asserting MSS()'s default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8349cc9-afd4-4c56-a88f-008b5a968c69

📥 Commits

Reviewing files that changed from the base of the PR and between 4cf1a06 and da9f29d.

📒 Files selected for processing (59)
  • CHANGELOG.md
  • CHANGES.md
  • README.md
  • demos/cat-detector.py
  • demos/tinytv-stream-simple.py
  • demos/tinytv-stream.py
  • demos/video-capture-simple.py
  • demos/video-capture.py
  • docs/source/api.rst
  • docs/source/conf.py
  • docs/source/examples.rst
  • docs/source/examples/callback.py
  • docs/source/examples/custom_cls_image.py
  • docs/source/examples/fps.py
  • docs/source/examples/fps_multiprocessing.py
  • docs/source/examples/from_pil_tuple.py
  • docs/source/examples/linux_display_keyword.py
  • docs/source/examples/linux_xshm_backend.py
  • docs/source/examples/opencv_numpy.py
  • docs/source/examples/part_of_screen.py
  • docs/source/examples/part_of_screen_monitor_2.py
  • docs/source/examples/pil.py
  • docs/source/examples/pil_pixels.py
  • docs/source/index.rst
  • docs/source/usage.rst
  • src/mss/__init__.py
  • src/mss/__main__.py
  • src/mss/base.py
  • src/mss/darwin.py
  • src/mss/factory.py
  • src/mss/linux/__init__.py
  • src/mss/linux/base.py
  • src/mss/linux/xgetimage.py
  • src/mss/linux/xlib.py
  • src/mss/linux/xshmgetimage.py
  • src/mss/windows.py
  • src/tests/bench_bgra2rgb.py
  • src/tests/bench_general.py
  • src/tests/bench_grab_windows.py
  • src/tests/conftest.py
  • src/tests/test_cls_image.py
  • src/tests/test_compat_10_1.py
  • src/tests/test_compat_exports.py
  • src/tests/test_compat_linux_api.py
  • src/tests/test_find_monitors.py
  • src/tests/test_get_pixels.py
  • src/tests/test_gnu_linux.py
  • src/tests/test_implementation.py
  • src/tests/test_issue_220.py
  • src/tests/test_leaks.py
  • src/tests/test_macos.py
  • src/tests/test_primary_monitor.py
  • src/tests/test_save.py
  • src/tests/test_setup.py
  • src/tests/test_tools.py
  • src/tests/test_windows.py
  • src/tests/test_xcb.py
  • src/tests/third_party/test_numpy.py
  • src/tests/third_party/test_pil.py
💤 Files with no reviewable changes (1)
  • docs/source/api.rst

Comment on lines +525 to +533
@property
def max_displays(self) -> int:
"""Maximum number of displays to handle.

Availability: macOS

.. versionadded:: 8.0.0
"""
return self._impl.max_displays # type: ignore[attr-defined]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if max_displays is defined on non-Darwin implementations
ast-grep --pattern 'class MSSImplWindows($$$) {
  $$$
  max_displays
  $$$
}'
rg -n 'max_displays' src/mss/linux/ src/mss/windows.py

Repository: jholveck/python-mss

Length of output: 45


🏁 Script executed:

# First, understand the repository structure
cd src/mss && ls -la

Repository: jholveck/python-mss

Length of output: 963


🏁 Script executed:

# Find all implementation files
find src/mss -name "*.py" -type f | head -20

Repository: jholveck/python-mss

Length of output: 452


🏁 Script executed:

# Search for max_displays in the entire codebase
rg -n 'max_displays' src/mss/

Repository: jholveck/python-mss

Length of output: 1450


🏁 Script executed:

# Find all implementation class definitions
rg -n 'class MSSImpl' src/mss/

Repository: jholveck/python-mss

Length of output: 513


🏁 Script executed:

# Check the base.py code around line 220 to see how max_displays is validated
sed -n '210,230p' src/mss/base.py

Repository: jholveck/python-mss

Length of output: 1080


🏁 Script executed:

# Check if MSSImplWindows has max_displays attribute
sed -n '185,250p' src/mss/windows.py

Repository: jholveck/python-mss

Length of output: 2436


🏁 Script executed:

# Check MSSImplXlib implementation
sed -n '390,450p' src/mss/linux/xlib.py

Repository: jholveck/python-mss

Length of output: 2260


🏁 Script executed:

# Look at the max_displays property and surrounding context in base.py
sed -n '520,540p' src/mss/base.py

Repository: jholveck/python-mss

Length of output: 636


🏁 Script executed:

# Check how _impl is initialized in the MSS class
rg -n '_impl' src/mss/base.py | head -20

Repository: jholveck/python-mss

Length of output: 713


🏁 Script executed:

# Look at the __init__ method of MSS class to see how _impl is set
sed -n '190,250p' src/mss/base.py

Repository: jholveck/python-mss

Length of output: 2731


🏁 Script executed:

# Let's verify what happens at runtime by checking if there's any guard in the property or elsewhere
grep -n 'def max_displays' src/mss/base.py -A 10

Repository: jholveck/python-mss

Length of output: 383


🏁 Script executed:

# Double-check that MSSImplWindows and MSSImplXlib truly don't have max_displays
grep -n 'max_displays' src/mss/windows.py src/mss/linux/xlib.py

Repository: jholveck/python-mss

Length of output: 45


🏁 Script executed:

# Check if there's any platform check in the property getter
sed -n '525,540p' src/mss/base.py

Repository: jholveck/python-mss

Length of output: 544


max_displays property will raise AttributeError on non-Darwin platforms.

The max_displays property accesses self._impl.max_displays unconditionally with a type: ignore[attr-defined] comment, but this attribute only exists in MSSImplDarwin. On Windows and Linux, the implementation classes (MSSImplWindows and MSSImplXlib) do not define this attribute, so accessing the property will raise AttributeError at runtime.

The docstring documents "Availability: macOS", but this limitation is not enforced. Consider adding a runtime guard that checks the platform before accessing the attribute, or raising a more explicit error message.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mss/base.py` around lines 525 - 533, The max_displays property currently
accesses self._impl.max_displays unconditionally which will raise AttributeError
on non-macOS implementations; modify the property (max_displays) to perform a
runtime guard (e.g. check platform.system() == "Darwin" or
isinstance(self._impl, MSSImplDarwin)) and only return self._impl.max_displays
for macOS, otherwise raise a clear NotImplementedError/PlatformError with a
message like "max_displays is only available on macOS (MSSImplDarwin)"; keep the
docstring but enforce the platform check to avoid AttributeError when self._impl
is MSSImplWindows or MSSImplXlib.

if args:
full_msg += " | " + ", ".join(str(arg) for arg in args)
self.shm_fallback_reason = full_msg
self.performance_status.append(full_msg)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep performance_status semantically one-sided.

This list is used for fallback diagnostics here, but the success path also appends a healthy-state message. That makes a non-empty performance_status ambiguous for callers trying to detect degraded capture behavior. Either keep it issue-only, or document/rename it as a general event log.

Also applies to: 201-203

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mss/linux/xshmgetimage.py` at line 74, The code currently appends
healthy-state messages to self.performance_status (e.g., the
self.performance_status.append(full_msg) call), making the list ambiguous;
change the logic so performance_status only records issues: remove or
conditionalize any append of non-error/healthy messages (including the other
occurrences around the block at lines ~201-203) and only append when a real
degraded/error condition is detected (preserve error message content and use the
same identifier full_msg when adding). If you prefer an audit trail instead,
rename performance_status to something like performance_events and update
callers accordingly, but do not leave both healthy and issue entries in
performance_status.

Comment on lines +160 to +167
with (
pytest.warns(DeprecationWarning, match=r"^mss\.mss is deprecated"),
pytest.warns(DeprecationWarning, match=r"is only available on"),
):
context = mss.mss(**kwargs)

with context as sct:
assert isinstance(sct, MSSBase)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if this test is skipped on Linux or has platform-specific handling
rg -n -A5 'test_deprecated_factory_accepts_documented_kwargs' src/tests/

Repository: jholveck/python-mss

Length of output: 551


🏁 Script executed:

cat -n src/tests/test_compat_10_1.py | head -180 | tail -50

Repository: jholveck/python-mss

Length of output: 2178


🏁 Script executed:

# Get the complete test function and check for platform-specific decorators
rg -n -B5 'def test_deprecated_factory_accepts_documented_kwargs' src/tests/test_compat_10_1.py | head -20

Repository: jholveck/python-mss

Length of output: 304


🏁 Script executed:

# Check if there are any skipif decorators on this test
rg -n -B10 'def test_deprecated_factory_accepts_documented_kwargs' src/tests/test_compat_10_1.py | grep -E '(skipif|pytest.mark|sys.platform)'

Repository: jholveck/python-mss

Length of output: 45


🏁 Script executed:

# Find the mss.mss factory function implementation
fd -t f '\.py$' -path '*/mss/*' | grep -v test | head -20

Repository: jholveck/python-mss

Length of output: 231


🏁 Script executed:

# Search for the mss factory function and where warnings are emitted
rg -n "def mss" --type py | grep -v test

Repository: jholveck/python-mss

Length of output: 115


🏁 Script executed:

# Search for where the "is only available on" warning is emitted
rg -n "is only available on" --type py

Repository: jholveck/python-mss

Length of output: 378


🏁 Script executed:

# Check the warning context in base.py
sed -n '215,240p' src/mss/base.py

Repository: jholveck/python-mss

Length of output: 1072


🏁 Script executed:

# Check the factory implementation
cat -n src/mss/factory.py

Repository: jholveck/python-mss

Length of output: 844


🏁 Script executed:

# Check the full test function including any decorators
rg -n -B15 'def test_deprecated_factory_accepts_documented_kwargs' src/tests/test_compat_10_1.py | head -25

Repository: jholveck/python-mss

Length of output: 763


🏁 Script executed:

# Look for conftest or any pytest markers that might skip this test
find src/tests -name "conftest.py" -o -name "*.py" | xargs grep -l "test_deprecated_factory_accepts_documented_kwargs"

Repository: jholveck/python-mss

Length of output: 92


🏁 Script executed:

# Check the entire test file for any module-level pytest.mark decorators or skipif conditions
head -50 src/tests/test_compat_10_1.py

Repository: jholveck/python-mss

Length of output: 1248


🏁 Script executed:

# Verify the platform module behavior to confirm our understanding
rg -n "platform.system()" src/mss/base.py -A2 -B2

Repository: jholveck/python-mss

Length of output: 373


Fix the nested pytest.warns to account for platform-specific warning counts.

The test expects exactly two "is only available on" warnings via the nested pytest.warns context managers. However, the actual number of warnings depends on the platform:

  • Linux: Only 1 warning (max_displays is invalid; with_cursor and display are valid)
  • Darwin: 2 warnings (with_cursor and display are invalid; max_displays is valid)
  • Windows: 3 warnings (all three are invalid)

The test needs to be refactored to either use conditional logic based on platform.system() or consolidate the warnings check to match platform-specific behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tests/test_compat_10_1.py` around lines 160 - 167, The nested
pytest.warns should be made platform-aware: import platform and compute
expected_count = {"Linux":1,"Darwin":2,"Windows":3}. Replace the two nested
pytest.warns blocks around mss.mss(**kwargs) with a single
pytest.warns(DeprecationWarning) as w (or use warnings.catch_warnings) when
calling context = mss.mss(**kwargs), then assert that one DeprecationWarning
matches r"^mss\.mss is deprecated" and that the number of warnings whose message
contains "is only available on" equals expected_count[platform.system()]; keep
the subsequent with context as sct and isinstance(sct, MSSBase) check unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant