codekingpro/portable-devtools
115k
1#
2# The Python Imaging Library
3# $Id$
4#
5# screen grabber
6#
7# History:
8# 2001-04-26 fl created
9# 2001-09-17 fl use builtin driver, if present
10# 2002-11-19 fl added grabclipboard support
11#
12# Copyright (c) 2001-2002 by Secret Labs AB
13# Copyright (c) 2001-2002 by Fredrik Lundh
14#
15# See the README file for information on usage and redistribution.
16#
17from __future__ import annotations
18
19import io
20import os
21import shutil
22import subprocess
23import sys
24import tempfile
25
26from . import Image
27
28TYPE_CHECKING = False
29if TYPE_CHECKING:
30 from . import ImageWin
31
32
33def grab(
34 bbox: tuple[int, int, int, int] | None = None,
35 include_layered_windows: bool = False,
36 all_screens: bool = False,
37 xdisplay: str | None = None,
38 window: int | ImageWin.HWND | None = None,
39) -> Image.Image:
40 im: Image.Image
41 if xdisplay is None:
42 if sys.platform == "darwin":
43 fh, filepath = tempfile.mkstemp(".png")
44 os.close(fh)
45 args = ["screencapture"]
46 if window is not None:
47 args += ["-l", str(window)]
48 elif bbox:
49 left, top, right, bottom = bbox
50 args += ["-R", f"{left},{top},{right-left},{bottom-top}"]
51 args += ["-x", filepath]
52 retcode = subprocess.call(args)
53 if retcode:
54 raise subprocess.CalledProcessError(retcode, args)
55 im = Image.open(filepath)
56 im.load()
57 os.unlink(filepath)
58 if bbox:
59 if window is not None:
60 # Determine if the window was in Retina mode or not
61 # by capturing it without the shadow,
62 # and checking how different the width is
63 fh, filepath = tempfile.mkstemp(".png")
64 os.close(fh)
65 args = ["screencapture", "-l", str(window), "-o", "-x", filepath]
66 retcode = subprocess.call(args)
67 if retcode:
68 raise subprocess.CalledProcessError(retcode, args)
69 with Image.open(filepath) as im_no_shadow:
70 retina = im.width - im_no_shadow.width > 100
71 os.unlink(filepath)
72
73 # Since screencapture's -R does not work with -l,
74 # crop the image manually
75 if retina:
76 left, top, right, bottom = bbox
77 im_cropped = im.resize(
78 (right - left, bottom - top),
79 box=tuple(coord * 2 for coord in bbox),
80 )
81 else:
82 im_cropped = im.crop(bbox)
83 im.close()
84 return im_cropped
85 else:
86 im_resized = im.resize((right - left, bottom - top))
87 im.close()
88 return im_resized
89 return im
90 elif sys.platform == "win32":
91 if window is not None:
92 all_screens = -1
93 offset, size, data = Image.core.grabscreen_win32(
94 include_layered_windows,
95 all_screens,
96 int(window) if window is not None else 0,
97 )
98 im = Image.frombytes(
99 "RGB",
100 size,
101 data,
102 # RGB, 32-bit line padding, origin lower left corner
103 "raw",
104 "BGR",
105 (size[0] * 3 + 3) & -4,
106 -1,
107 )
108 if bbox:
109 x0, y0 = offset
110 left, top, right, bottom = bbox
111 im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
112 return im
113 # Cast to Optional[str] needed for Windows and macOS.
114 display_name: str | None = xdisplay
115 try:
116 if not Image.core.HAVE_XCB:
117 msg = "Pillow was built without XCB support"
118 raise OSError(msg)
119 size, data = Image.core.grabscreen_x11(display_name)
120 except OSError:
121 if display_name is None and sys.platform not in ("darwin", "win32"):
122 if shutil.which("gnome-screenshot"):
123 args = ["gnome-screenshot", "-f"]
124 elif shutil.which("grim"):
125 args = ["grim"]
126 elif shutil.which("spectacle"):
127 args = ["spectacle", "-n", "-b", "-f", "-o"]
128 else:
129 raise
130 fh, filepath = tempfile.mkstemp(".png")
131 os.close(fh)
132 args.append(filepath)
133 retcode = subprocess.call(args)
134 if retcode:
135 raise subprocess.CalledProcessError(retcode, args)
136 im = Image.open(filepath)
137 im.load()
138 os.unlink(filepath)
139 if bbox:
140 im_cropped = im.crop(bbox)
141 im.close()
142 return im_cropped
143 return im
144 else:
145 raise
146 else:
147 im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1)
148 if bbox:
149 im = im.crop(bbox)
150 return im
151
152
153def grabclipboard() -> Image.Image | list[str] | None:
154 if sys.platform == "darwin":
155 p = subprocess.run(
156 ["osascript", "-e", "get the clipboard as «class PNGf»"],
157 capture_output=True,
158 )
159 if p.returncode != 0:
160 return None
161
162 import binascii
163
164 data = io.BytesIO(binascii.unhexlify(p.stdout[11:-3]))
165 return Image.open(data)
166 elif sys.platform == "win32":
167 fmt, data = Image.core.grabclipboard_win32()
168 if fmt == "file": # CF_HDROP
169 import struct
170
171 o = struct.unpack_from("I", data)[0]
172 if data[16] == 0:
173 files = data[o:].decode("mbcs").split("\0")
174 else:
175 files = data[o:].decode("utf-16le").split("\0")
176 return files[: files.index("")]
177 if isinstance(data, bytes):
178 data = io.BytesIO(data)
179 if fmt == "png":
180 from . import PngImagePlugin
181
182 return PngImagePlugin.PngImageFile(data)
183 elif fmt == "DIB":
184 from . import BmpImagePlugin
185
186 return BmpImagePlugin.DibImageFile(data)
187 return None
188 else:
189 if os.getenv("WAYLAND_DISPLAY"):
190 session_type = "wayland"
191 elif os.getenv("DISPLAY"):
192 session_type = "x11"
193 else: # Session type check failed
194 session_type = None
195
196 if shutil.which("wl-paste") and session_type in ("wayland", None):
197 args = ["wl-paste", "-t", "image"]
198 elif shutil.which("xclip") and session_type in ("x11", None):
199 args = ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"]
200 else:
201 msg = "wl-paste or xclip is required for ImageGrab.grabclipboard() on Linux"
202 raise NotImplementedError(msg)
203
204 p = subprocess.run(args, capture_output=True)
205 if p.returncode != 0:
206 err = p.stderr
207 for silent_error in [
208 # wl-paste, when the clipboard is empty
209 b"Nothing is copied",
210 # Ubuntu/Debian wl-paste, when the clipboard is empty
211 b"No selection",
212 # Ubuntu/Debian wl-paste, when an image isn't available
213 b"No suitable type of content copied",
214 # wl-paste or Ubuntu/Debian xclip, when an image isn't available
215 b" not available",
216 # xclip, when an image isn't available
217 b"cannot convert ",
218 # xclip, when the clipboard isn't initialized
219 b"xclip: Error: There is no owner for the ",
220 ]:
221 if silent_error in err:
222 return None
223 msg = f"{args[0]} error"
224 if err:
225 msg += f": {err.strip().decode()}"
226 raise ChildProcessError(msg)
227
228 data = io.BytesIO(p.stdout)
229 im = Image.open(data)
230 im.load()
231 return im
232 