| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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: |
| | |
| | |
| | |
| | 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) |
| |
|
| | |
| | |
| | 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, |
| | |
| | "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 |
| |
|