|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|