ImageGrab.py 5.9 KB

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