| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | 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 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:
|
| | 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,
|
| |
|
| | "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
|
| |
|
| | 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":
|
| | 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 = 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 [
|
| |
|
| | b"Nothing is copied",
|
| |
|
| | b"No selection",
|
| |
|
| | b"No suitable type of content copied",
|
| |
|
| | b" not available",
|
| |
|
| | b"cannot convert ",
|
| |
|
| | 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
|
| |
|