ImageGrab.py 6.3 KB

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