ImageGrab.py 7.6 KB

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