Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/mss/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()):
Expand Down
33 changes: 0 additions & 33 deletions src/tests/test_gnu_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions src/tests/test_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down