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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ See Git checking messages for full history.
## 10.1.1.dev0 (2025-xx-xx)
- Linux: check the server for Xrandr support version (#417)
- Linux: improve typing and error messages for X libraries (#418)
- Linux: add a new XCB backend for better thread safety, error-checking, and future development (#425)
- Linux: introduce an XCB-powered backend stack with a factory in ``mss.linux`` while keeping the Xlib code as a fallback (#425)
- Linux: add the XShmGetImage backend with automatic XGetImage fallback and explicit status reporting (#431)
- :heart: contributors: @jholveck

## 10.1.0 (2025-08-16)
Expand Down
14 changes: 14 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Technical Changes

## 10.1.1 (2025-xx-xx)

### linux/__init__.py
- Added an ``mss()`` factory to select between the different GNU/Linux backends.

### linux/xlib.py
- Moved the legacy Xlib backend into the ``mss.linux.xlib`` module to be used as a fallback implementation.

### linux/xgetimage.py
- Added an XCB-based backend that mirrors XGetImage semantics.

### linux/xshmgetimage.py
- Added an XCB backend powered by XShmGetImage with ``shm_status`` and ``shm_fallback_reason`` attributes for diagnostics.

## 10.1.0 (2025-08-16)

### darwin.py
Expand Down
79 changes: 79 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,36 @@ GNU/Linux

.. module:: mss.linux

Factory function to return the appropriate backend implementation.

.. function:: mss(backend="default", **kwargs)

:keyword str backend: Backend name ("default", "xlib", "xgetimage", or "xshmgetimage").
:keyword display: Display name (e.g., ":0.0") for the X server. Default is taken from the :envvar:`DISPLAY` environment variable.
:type display: str or None
:param kwargs: Additional arguments passed to the backend MSS class.
:rtype: :class:`mss.base.MSSBase`
:return: Backend-specific MSS instance.

Factory returning a proper MSS class instance for GNU/Linux.
The backend parameter selects the implementation:

- "default" or "xshmgetimage": XCB-based backend using XShmGetImage (default, with automatic fallback to XGetImage)
- "xgetimage": XCB-based backend using XGetImage
- "xlib": Traditional Xlib-based backend retained for environments without working XCB libraries

.. function:: MSS(*args, **kwargs)

Alias for :func:`mss` for backward compatibility.


Xlib Backend
^^^^^^^^^^^^

.. module:: mss.linux.xlib

Legacy Xlib-based backend, kept as a fallback when XCB is unavailable.

.. attribute:: CFUNCTIONS

.. versionadded:: 6.1.0
Expand Down Expand Up @@ -79,6 +109,55 @@ GNU/Linux

.. versionadded:: 8.0.0


XGetImage Backend
^^^^^^^^^^^^^^^^^

.. module:: mss.linux.xgetimage

XCB-based backend using XGetImage protocol.

.. class:: MSS

XCB implementation using XGetImage for screenshot capture.


XShmGetImage Backend
^^^^^^^^^^^^^^^^^^^^

.. module:: mss.linux.xshmgetimage

XCB-based backend using XShmGetImage protocol with shared memory.

.. class:: ShmStatus

Enum describing the availability of the X11 MIT-SHM extension used by the backend.

.. attribute:: UNKNOWN

Initial state before any capture confirms availability or failure.

.. attribute:: AVAILABLE

Shared-memory capture works and will continue to be used.

.. attribute:: UNAVAILABLE

Shared-memory capture failed; MSS will use XGetImage.

.. class:: MSS

XCB implementation using XShmGetImage for screenshot capture.
Falls back to XGetImage if shared memory extension is unavailable.

.. attribute:: shm_status

Current shared-memory availability, using :class:`mss.linux.xshmgetimage.ShmStatus`.

.. attribute:: shm_fallback_reason

Optional string describing why the backend fell back to XGetImage when MIT-SHM is unavailable.

Windows
-------

Expand Down
9 changes: 9 additions & 0 deletions docs/source/developers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,12 @@ To build the documentation, simply type::

$ python -m pip install -e '.[docs]'
$ sphinx-build -d docs docs/source docs_out --color -W -bhtml


XCB Code Generator
==================

The GNU/Linux XCB backends rely on generated ctypes bindings. If you need to
add new XCB requests or types, do **not** edit ``src/mss/linux/xcbgen.py`` by
hand. Instead, follow the workflow described in ``src/xcbproto/README.md``,
which explains how to update ``gen_xcb_to_py.py`` and regenerate the bindings.
9 changes: 9 additions & 0 deletions docs/source/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ You can handle data using a custom class:

.. versionadded:: 3.1.0

GNU/Linux XShm backend
----------------------

Select the XShmGetImage backend explicitly and inspect whether it is active or
falling back to XGetImage:

.. literalinclude:: examples/linux_xshm_backend.py
:lines: 7-

PIL
===

Expand Down
17 changes: 17 additions & 0 deletions docs/source/examples/linux_xshm_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""This is part of the MSS Python's module.
Source: https://github.com/BoboTiG/python-mss.

Select the XShmGetImage backend explicitly and inspect its status.
"""

from mss.linux.xshmgetimage import MSS as mss

with mss() as sct:
screenshot = sct.grab(sct.monitors[1])
print(f"Captured screenshot dimensions: {screenshot.size.width}x{screenshot.size.height}")

print(f"shm_status: {sct.shm_status.name}")
if sct.shm_fallback_reason:
print(f"Falling back to XGetImage because: {sct.shm_fallback_reason}")
else:
print("MIT-SHM capture active.")
64 changes: 55 additions & 9 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Usage
Import
======

So MSS can be used as simply as::
MSS can be used as simply as::

from mss import mss

Expand All @@ -20,6 +20,11 @@ Or import the good one based on your operating system::
# Microsoft Windows
from mss.windows import MSS as mss

On GNU/Linux you can also import a specific backend (see :ref:`backends`)
directly when you need a particular implementation, for example::

from mss.linux.xshmgetimage import MSS as mss


Instance
========
Expand Down Expand Up @@ -49,18 +54,56 @@ This is a much better usage, memory efficient::
Also, it is a good thing to save the MSS instance inside an attribute of your class and calling it when needed.


.. _backends:

Backends
--------

Some platforms have multiple ways to take screenshots. In MSS, these are known as *backends*. The :py:func:`mss` functions will normally autodetect which one is appropriate for your situation, but you can override this if you want. For instance, you may know that your specific situation requires a particular backend.

If you want to choose a particular backend, you can use the :py::`backend` keyword to :py:func:`mss`::

with mss(backend="xgetimage") as sct:
...

Alternatively, you can also directly import the backend you want to use::

from mss.linux.xgetimage import MSS as mss

Currently, only the GNU/Linux implementation has multiple backends. These are described in their own section below.


GNU/Linux
---------

On GNU/Linux, you can specify which display to use (useful for distant screenshots via SSH)::

with mss(display=":0.0") as sct:
# ...
Display
^^^^^^^

A more specific example (only valid on GNU/Linux):
On GNU/Linux, the default display is taken from the :envvar:`DISPLAY` environment variable. You can instead specify which display to use (useful for distant screenshots via SSH) using the ``display`` keyword:

.. literalinclude:: examples/linux_display_keyword.py
:lines: 9-
:lines: 7-


Backends
^^^^^^^^

The GNU/Linux implementation has multiple backends (see :ref:`backends`), or ways it can take screenshots. The :py:func:`mss.mss` and :py:func:`mss.linux.mss` functions will normally autodetect which one is appropriate, but you can override this if you want.

There are three available backends.

:py:mod:`xshmgetimage` (default)
The fastest backend, based on :c:func:`xcb_shm_get_image`. It is roughly three times faster than :py:mod:`xgetimage`
and is used automatically. When the MIT-SHM extension is unavailable (for example on remote SSH displays), it
transparently falls back to :py:mod:`xgetimage` so you can always request it safely.

:py:mod:`xgetimage`
A highly-compatible, but slower, backend based on :c:func:`xcb_get_image`. Use this explicitly only when you know
that :py:mod:`xshmgetimage` cannot operate in your environment.

:py:mod:`xlib`
The legacy backend powered by :c:func:`XGetImage`. It is kept solely for systems where XCB libraries are
unavailable and no new features are being added to it.


Command Line
Expand All @@ -73,8 +116,8 @@ You can use ``mss`` via the CLI::
Or via direct call from Python::

$ python -m mss --help
usage: __main__.py [-h] [-c COORDINATES] [-l {0,1,2,3,4,5,6,7,8,9}]
[-m MONITOR] [-o OUTPUT] [-q] [-v] [--with-cursor]
usage: mss [-h] [-c COORDINATES] [-l {0,1,2,3,4,5,6,7,8,9}] [-m MONITOR]
[-o OUTPUT] [--with-cursor] [-q] [-b BACKEND] [-v]

options:
-h, --help show this help message and exit
Expand All @@ -86,6 +129,9 @@ Or via direct call from Python::
the monitor to screenshot
-o OUTPUT, --output OUTPUT
the output file name
-b, --backend BACKEND
platform-specific backend to use
(Linux: default/xlib/xgetimage/xshmgetimage; macOS/Windows: default)
--with-cursor include the cursor
-q, --quiet do not print created files
-v, --version show program's version number and exit
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ ignore = [
"docs/source/*" = [
"ERA001", # commented code
"INP001", # file `xxx` is part of an implicit namespace package
"N811", # importing constant (MSS) as non-constant (mss)
]
"src/tests/*" = [
"FBT001", # boolean-typed positional argument in function definition
Expand Down
38 changes: 34 additions & 4 deletions src/mss/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,38 @@
"""

import os.path
import platform
import sys
from argparse import ArgumentParser
from argparse import ArgumentError, ArgumentParser

from mss import __version__
from mss.exception import ScreenShotError
from mss.factory import mss
from mss.tools import to_png


def _backend_cli_choices() -> list[str]:
os_name = platform.system().lower()
if os_name == "darwin":
from mss import darwin # noqa: PLC0415

return list(darwin.BACKENDS)
if os_name == "linux":
from mss import linux # noqa: PLC0415

return list(linux.BACKENDS)
if os_name == "windows":
from mss import windows # noqa: PLC0415

return list(windows.BACKENDS)
return ["default"]


def main(*args: str) -> int:
"""Main logic."""
cli_args = ArgumentParser(prog="mss")
backend_choices = _backend_cli_choices()

cli_args = ArgumentParser(prog="mss", exit_on_error=False)
cli_args.add_argument(
"-c",
"--coordinates",
Expand All @@ -40,9 +60,19 @@ def main(*args: str) -> int:
action="store_true",
help="do not print created files",
)
cli_args.add_argument(
"-b", "--backend", default="default", choices=backend_choices, help="platform-specific backend to use"
)
cli_args.add_argument("-v", "--version", action="version", version=__version__)

options = cli_args.parse_args(args or None)
try:
options = cli_args.parse_args(args or None)
except ArgumentError as e:
# By default, parse_args will print and the error and exit. We
# return instead of exiting, to make unit testing easier.
cli_args.print_usage(sys.stderr)
print(f"{cli_args.prog}: error: {e}", file=sys.stderr)
return 2
kwargs = {"mon": options.monitor, "output": options.output}
if options.coordinates:
try:
Expand All @@ -61,7 +91,7 @@ def main(*args: str) -> int:
kwargs["output"] = "sct-{top}x{left}_{width}x{height}.png"

try:
with mss(with_cursor=options.with_cursor) as sct:
with mss(with_cursor=options.with_cursor, backend=options.backend) as sct:
if options.coordinates:
output = kwargs["output"].format(**kwargs["mon"])
sct_img = sct.grab(kwargs["mon"])
Expand Down
11 changes: 10 additions & 1 deletion src/mss/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@

from mss.models import Monitor, Monitors

# Prior to 3.11, Python didn't have the Self type. typing_extensions does, but we don't want to depend on it.
try:
from typing import Self
except ImportError: # pragma: nocover
try:
from typing_extensions import Self
except ImportError: # pragma: nocover
Self = Any # type: ignore[assignment]

try:
from datetime import UTC
except ImportError: # pragma: nocover
Expand Down Expand Up @@ -58,7 +67,7 @@ def __init__(
msg = 'The only valid backend on this platform is "default".'
raise ScreenShotError(msg)

def __enter__(self) -> MSSBase: # noqa:PYI034
def __enter__(self) -> Self:
"""For the cool call `with MSS() as mss:`."""
return self

Expand Down
2 changes: 2 additions & 0 deletions src/mss/darwin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

__all__ = ("MSS",)

BACKENDS = ["default"]

MAC_VERSION_CATALINA = 10.16

kCGWindowImageBoundsIgnoreFraming = 1 << 0 # noqa: N816
Expand Down
3 changes: 2 additions & 1 deletion src/mss/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def mss(**kwargs: Any) -> MSSBase:
if os_ == "linux":
from mss import linux # noqa: PLC0415

return linux.MSS(**kwargs)
# Linux has its own factory to choose the backend.
return linux.mss(**kwargs)

if os_ == "windows":
from mss import windows # noqa: PLC0415
Expand Down
Loading