From c7457c1c16765c2e5c9a0293ef0d1ee705048762 Mon Sep 17 00:00:00 2001 From: Joel Ray Holveck Date: Sat, 20 Dec 2025 21:54:00 -0800 Subject: [PATCH 1/3] Add a test for tuples that have negative coordinates. Specifically, a negative width or height. Particularly, this seems like an easy case for a user to hit if somebody forgets that the PIL tuple style uses top, left, RIGHT, BOTTOM and not top, left, WIDTH, HEIGHT. Ask how I know. Test that we do handle that gracefully. --- src/tests/test_implementation.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/tests/test_implementation.py b/src/tests/test_implementation.py index 9ee55ad..7d229dc 100644 --- a/src/tests/test_implementation.py +++ b/src/tests/test_implementation.py @@ -251,6 +251,31 @@ def test_grab_with_tuple(mss_impl: Callable[..., MSSBase]) -> None: assert im.rgb == im2.rgb +def test_grab_with_invalid_tuple(mss_impl: Callable[..., MSSBase]) -> None: + with mss_impl() as sct: + # Remember that rect tuples are PIL-style: (left, top, right, bottom) + + # Negative top + negative_box = (100, -100, 500, 500) + with pytest.raises(ScreenShotError): + sct.grab(negative_box) + + # Negative left + negative_box = (-100, 100, 500, 500) + with pytest.raises(ScreenShotError): + sct.grab(negative_box) + + # Negative width (but right > 0) + negative_box = (100, 100, 50, 500) + with pytest.raises(ScreenShotError): + sct.grab(negative_box) + + # Negative height (but bottom > 0) + negative_box = (100, 100, 500, 50) + with pytest.raises(ScreenShotError): + sct.grab(negative_box) + + def test_grab_with_tuple_percents(mss_impl: Callable[..., MSSBase]) -> None: with mss_impl() as sct: monitor = sct.monitors[1] From 9e679a74b83fa9d252dc89961b4565dc3c0dede1 Mon Sep 17 00:00:00 2001 From: Joel Holveck Date: Sun, 21 Dec 2025 09:09:01 +0000 Subject: [PATCH 2/3] Ensure that grab regions are valid --- src/mss/base.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mss/base.py b/src/mss/base.py index 2abfcbd..f4b3dfe 100644 --- a/src/mss/base.py +++ b/src/mss/base.py @@ -112,6 +112,13 @@ def grab(self, monitor: Monitor | tuple[int, int, int, int], /) -> ScreenShot: "height": monitor[3] - monitor[1], } + if monitor["left"] < 0 or monitor["top"] < 0: + msg = f"Region has negative coordinates: {monitor!r}" + raise ScreenShotError(msg) + if monitor["width"] <= 0 or monitor["height"] <= 0: + msg = f"Region has zero or negative size: {monitor!r}" + raise ScreenShotError(msg) + with lock: screenshot = self._grab_impl(monitor) if self.with_cursor and (cursor := self._cursor_impl()): From b78919bcb71e64f3f8133f5b7272610fe214cc73 Mon Sep 17 00:00:00 2001 From: Joel Holveck Date: Sun, 21 Dec 2025 09:12:06 +0000 Subject: [PATCH 3/3] Remove a now-obsolete test I thought I'd seen a test for for invalid regions somewhere before... --- src/tests/test_gnu_linux.py | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/src/tests/test_gnu_linux.py b/src/tests/test_gnu_linux.py index 6bb118a..dcb1d99 100644 --- a/src/tests/test_gnu_linux.py +++ b/src/tests/test_gnu_linux.py @@ -170,39 +170,6 @@ def test_unsupported_depth(backend: str) -> None: sct.grab(sct.monitors[1]) -def test_region_out_of_monitor_bounds(display: str, backend: str) -> None: - monitor = {"left": -30, "top": 0, "width": WIDTH, "height": HEIGHT} - - with mss.mss(display=display, backend=backend, with_cursor=True) as sct: - # At one point, I had accidentally been reporting the resource ID as a CData object instead of the contained - # int. This is to make sure I don't repeat that mistake. That said, change this error regex if needed to keep - # up with formatting changes. - expected_err_re = ( - r"(?is)" - r"Error of failed request:\s+(8|BadMatch)\b" - r".*Major opcode of failed request:\s+73\b" - r".*Resource id in failed request:\s+[0-9]" - r".*Serial number of failed request:\s+[0-9]" - ) - - with pytest.raises(ScreenShotError, match=expected_err_re) as exc: - sct.grab(monitor) - - details = exc.value.details - assert details - assert isinstance(details, dict) - if backend in {"xgetimage", "xshmgetimage"} and mss.linux.xcb.LIB.errors is None: - pytest.xfail("Error strings in XCB backends are only available with the xcb-util-errors library.") - assert isinstance(details["error"], str) - - errstr = str(exc.value) - assert "Match" in errstr # Xlib: "BadMatch"; XCB: "Match" - assert "GetImage" in errstr # Xlib: "X_GetImage"; XCB: "GetImage" - - if backend == "xlib": - assert not mss.linux.xlib._ERROR - - def test__is_extension_enabled_unknown_name(display: str) -> None: with mss.mss(display=display, backend="xlib") as sct: assert isinstance(sct, mss.linux.xlib.MSS) # For Mypy