Skip to content
Back to top

# Source code for PIL.ImageGrab

    #
    # The Python Imaging Library
    # $Id$
    #
    # screen grabber
    #
    # History:
    # 2001-04-26 fl  created
    # 2001-09-17 fl  use builtin driver, if present
    # 2002-11-19 fl  added grabclipboard support
    #
    # Copyright (c) 2001-2002 by Secret Labs AB
    # Copyright (c) 2001-2002 by Fredrik Lundh
    #
    # See the README file for information on usage and redistribution.
    #
    from __future__ import annotations

    import io
    import os
    import shutil
    import subprocess
    import sys
    import tempfile

    from . import Image

    TYPE_CHECKING = False
    if TYPE_CHECKING:
        from . import ImageWin

    def grab(
        bbox: tuple[int, int, int, int] | None = None,
        include_layered_windows: bool = False,
        all_screens: bool = False,
        xdisplay: str | None = None,
        window: int | ImageWin.HWND | None = None,
    ) -> Image.Image:
        im: Image.Image
        if xdisplay is None:
            if sys.platform == "darwin":
                fh, filepath = tempfile.mkstemp(".png")
                os.close(fh)
                args = ["screencapture"]
                if window:
                    args += ["-l", str(window)]
                elif bbox:
                    left, top, right, bottom = bbox
                    args += ["-R", f"{left},{top},{right-left},{bottom-top}"]
                subprocess.call(args + ["-x", filepath])
                im = Image.open(filepath)
                im.load()
                os.unlink(filepath)
                if bbox:
                    if window:
                        # Determine if the window was in Retina mode or not
                        # by capturing it without the shadow,
                        # and checking how different the width is
                        fh, filepath = tempfile.mkstemp(".png")
                        os.close(fh)
                        subprocess.call(
                            ["screencapture", "-l", str(window), "-o", "-x", filepath]
                        )
                        with Image.open(filepath) as im_no_shadow:
                            retina = im.width - im_no_shadow.width > 100
                        os.unlink(filepath)

                        # Since screencapture's -R does not work with -l,
                        # crop the image manually
                        if retina:
                            left, top, right, bottom = bbox
                            im_cropped = im.resize(
                                (right - left, bottom - top),
                                box=tuple(coord * 2 for coord in bbox),
                            )
                        else:
                            im_cropped = im.crop(bbox)
                        im.close()
                        return im_cropped
                    else:
                        im_resized = im.resize((right - left, bottom - top))
                        im.close()
                        return im_resized
                return im
            elif sys.platform == "win32":
                if window is not None:
                    all_screens = -1
                offset, size, data = Image.core.grabscreen_win32(
                    include_layered_windows,
                    all_screens,
                    int(window) if window is not None else 0,
                )
                im = Image.frombytes(
                    "RGB",
                    size,
                    data,
                    # RGB, 32-bit line padding, origin lower left corner
                    "raw",
                    "BGR",
                    (size[0] * 3 + 3) & -4,
                    -1,
                )
                if bbox:
                    x0, y0 = offset
                    left, top, right, bottom = bbox
                    im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
                return im
        # Cast to Optional[str] needed for Windows and macOS.
        display_name: str | None = xdisplay
        try:
            if not Image.core.HAVE_XCB:
                msg = "Pillow was built without XCB support"
                raise OSError(msg)
            size, data = Image.core.grabscreen_x11(display_name)
        except OSError:
            if display_name is None and sys.platform not in ("darwin", "win32"):
                if shutil.which("gnome-screenshot"):
                    args = ["gnome-screenshot", "-f"]
                elif shutil.which("grim"):
                    args = ["grim"]
                elif shutil.which("spectacle"):
                    args = ["spectacle", "-n", "-b", "-f", "-o"]
                else:
                    raise
                fh, filepath = tempfile.mkstemp(".png")
                os.close(fh)
                subprocess.call(args + [filepath])
                im = Image.open(filepath)
                im.load()
                os.unlink(filepath)
                if bbox:
                    im_cropped = im.crop(bbox)
                    im.close()
                    return im_cropped
                return im
            else:
                raise
        else:
            im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1)
            if bbox:
                im = im.crop(bbox)
            return im

    def grabclipboard() -> Image.Image | list[str] | None:
        if sys.platform == "darwin":
            p = subprocess.run(
                ["osascript", "-e", "get the clipboard as «class PNGf»"],
                capture_output=True,
            )
            if p.returncode != 0:
                return None

            import binascii

            data = io.BytesIO(binascii.unhexlify(p.stdout[11:-3]))
            return Image.open(data)
        elif sys.platform == "win32":
            fmt, data = Image.core.grabclipboard_win32()
            if fmt == "file":  # CF_HDROP
                import struct

                o = struct.unpack_from("I", data)[0]
                if data[16] == 0:
                    files = data[o:].decode("mbcs").split("\0")
                else:
                    files = data[o:].decode("utf-16le").split("\0")
                return files[: files.index("")]
            if isinstance(data, bytes):
                data = io.BytesIO(data)
                if fmt == "png":
                    from . import PngImagePlugin

                    return PngImagePlugin.PngImageFile(data)
                elif fmt == "DIB":
                    from . import BmpImagePlugin

                    return BmpImagePlugin.DibImageFile(data)
            return None
        else:
            if os.getenv("WAYLAND_DISPLAY"):
                session_type = "wayland"
            elif os.getenv("DISPLAY"):
                session_type = "x11"
            else:  # Session type check failed
                session_type = None

            if shutil.which("wl-paste") and session_type in ("wayland", None):
                args = ["wl-paste", "-t", "image"]
            elif shutil.which("xclip") and session_type in ("x11", None):
                args = ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"]
            else:
                msg = "wl-paste or xclip is required for ImageGrab.grabclipboard() on Linux"
                raise NotImplementedError(msg)

            p = subprocess.run(args, capture_output=True)
            if p.returncode != 0:
                err = p.stderr
                for silent_error in [
                    # wl-paste, when the clipboard is empty
                    b"Nothing is copied",
                    # Ubuntu/Debian wl-paste, when the clipboard is empty
                    b"No selection",
                    # Ubuntu/Debian wl-paste, when an image isn't available
                    b"No suitable type of content copied",
                    # wl-paste or Ubuntu/Debian xclip, when an image isn't available
                    b" not available",
                    # xclip, when an image isn't available
                    b"cannot convert ",
                    # xclip, when the clipboard isn't initialized
                    b"xclip: Error: There is no owner for the ",
                ]:
                    if silent_error in err:
                        return None
                msg = f"{args[0]} error"
                if err:
                    msg += f": {err.strip().decode()}"
                raise ChildProcessError(msg)

            data = io.BytesIO(p.stdout)
            im = Image.open(data)
            im.load()
            return im