This commit is contained in:
Iliyan Angelov
2025-12-01 06:50:10 +02:00
parent 91f51bc6fe
commit 62c1fe5951
4682 changed files with 544807 additions and 31208 deletions

View File

@@ -14,6 +14,7 @@
#
# See the README file for information on usage and redistribution.
#
from __future__ import annotations
import io
import os
@@ -24,8 +25,19 @@ import tempfile
from . import Image
TYPE_CHECKING = False
if TYPE_CHECKING:
from . import ImageWin
def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None):
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")
@@ -44,8 +56,12 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
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
include_layered_windows,
all_screens,
int(window) if window is not None else 0,
)
im = Image.frombytes(
"RGB",
@@ -62,20 +78,26 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
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(xdisplay)
size, data = Image.core.grabscreen_x11(display_name)
except OSError:
if (
xdisplay is None
and sys.platform not in ("darwin", "win32")
and shutil.which("gnome-screenshot")
):
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(["gnome-screenshot", "-f", filepath])
subprocess.call(args + [filepath])
im = Image.open(filepath)
im.load()
os.unlink(filepath)
@@ -93,40 +115,29 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
return im
def grabclipboard():
def grabclipboard() -> Image.Image | list[str] | None:
if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp(".png")
os.close(fh)
commands = [
'set theFile to (open for access POSIX file "'
+ filepath
+ '" with write permission)',
"try",
" write (the clipboard as «class PNGf») to theFile",
"end try",
"close access theFile",
]
script = ["osascript"]
for command in commands:
script += ["-e", command]
subprocess.call(script)
p = subprocess.run(
["osascript", "-e", "get the clipboard as «class PNGf»"],
capture_output=True,
)
if p.returncode != 0:
return None
im = None
if os.stat(filepath).st_size != 0:
im = Image.open(filepath)
im.load()
os.unlink(filepath)
return im
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("utf-16le").split("\0")
else:
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)
@@ -148,18 +159,7 @@ def grabclipboard():
session_type = None
if shutil.which("wl-paste") and session_type in ("wayland", None):
output = subprocess.check_output(["wl-paste", "-l"]).decode()
mimetypes = output.splitlines()
if "image/png" in mimetypes:
mimetype = "image/png"
elif mimetypes:
mimetype = mimetypes[0]
else:
mimetype = None
args = ["wl-paste"]
if mimetype:
args.extend(["-t", mimetype])
args = ["wl-paste", "-t", "image"]
elif shutil.which("xclip") and session_type in ("x11", None):
args = ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"]
else:
@@ -167,10 +167,29 @@ def grabclipboard():
raise NotImplementedError(msg)
p = subprocess.run(args, capture_output=True)
err = p.stderr
if err:
msg = f"{args[0]} error: {err.strip().decode()}"
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()