asd
This commit is contained in:
54
venv/lib/python3.12/site-packages/pyscreenshot/__init__.py
Normal file
54
venv/lib/python3.12/site-packages/pyscreenshot/__init__.py
Normal file
@ -0,0 +1,54 @@
|
||||
import logging
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from pyscreenshot.about import __version__
|
||||
from pyscreenshot.childproc import childprocess_backend_version
|
||||
from pyscreenshot.loader import FailedBackendError, backend_dict, backend_grab
|
||||
|
||||
ADDITIONAL_IMPORTS = [FailedBackendError]
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.debug("version=%s", __version__)
|
||||
|
||||
|
||||
def grab(
|
||||
bbox: Optional[Tuple[int, int, int, int]] = None,
|
||||
childprocess: bool = True,
|
||||
backend: Optional[str] = None,
|
||||
) -> "Image":
|
||||
"""Copy the contents of the screen to PIL image memory.
|
||||
|
||||
:param bbox: optional bounding box (x1,y1,x2,y2)
|
||||
:param childprocess: run back-end in new process using popen. (bool)
|
||||
This isolates back-ends from each other and from main process.
|
||||
Leave it as it is (True) to have a safe setting.
|
||||
Set it False to improve performance, but then conflicts are possible.
|
||||
:param backend: back-end can be forced if set (examples:scrot, wx,..),
|
||||
otherwise back-end is automatic
|
||||
"""
|
||||
if bbox:
|
||||
x1, y1, x2, y2 = bbox
|
||||
if x2 <= x1:
|
||||
raise ValueError("bbox x2<=x1")
|
||||
if y2 <= y1:
|
||||
raise ValueError("bbox y2<=y1")
|
||||
return backend_grab(backend, bbox, childprocess)
|
||||
|
||||
|
||||
def backends() -> List[str]:
|
||||
"""Back-end names as a list.
|
||||
|
||||
:return: back-ends as string list
|
||||
"""
|
||||
return list(backend_dict.keys())
|
||||
|
||||
|
||||
def backend_version(backend: str) -> str:
|
||||
"""Back-end version.
|
||||
|
||||
:param backend: back-end (examples:scrot, wx,..)
|
||||
:return: version as string
|
||||
"""
|
||||
return childprocess_backend_version(backend)
|
1
venv/lib/python3.12/site-packages/pyscreenshot/about.py
Normal file
1
venv/lib/python3.12/site-packages/pyscreenshot/about.py
Normal file
@ -0,0 +1 @@
|
||||
__version__ = "3.1"
|
@ -0,0 +1,25 @@
|
||||
import time
|
||||
|
||||
from entrypoint2 import entrypoint
|
||||
|
||||
import pyscreenshot
|
||||
from pyscreenshot import backends
|
||||
|
||||
|
||||
@entrypoint
|
||||
def show():
|
||||
im = []
|
||||
blist = []
|
||||
|
||||
for x in backends():
|
||||
try:
|
||||
print("--> grabbing by " + x)
|
||||
im.append(pyscreenshot.grab(bbox=(500, 400, 800, 600), backend=x))
|
||||
blist.append(x)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(im)
|
||||
print(blist)
|
||||
for x in im:
|
||||
x.show()
|
||||
time.sleep(0.5)
|
@ -0,0 +1,119 @@
|
||||
import sys
|
||||
import time
|
||||
|
||||
from entrypoint2 import entrypoint
|
||||
|
||||
import pyscreenshot
|
||||
from pyscreenshot.plugins.freedesktop_dbus import FreedesktopDBusWrapper
|
||||
from pyscreenshot.plugins.gnome_dbus import GnomeDBusWrapper
|
||||
from pyscreenshot.plugins.gnome_screenshot import GnomeScreenshotWrapper
|
||||
from pyscreenshot.plugins.kwin_dbus import KwinDBusWrapper
|
||||
from pyscreenshot.util import run_mod_as_subproc
|
||||
|
||||
|
||||
def run(force_backend, n, childprocess, bbox=None):
|
||||
sys.stdout.write("%-20s\t" % force_backend)
|
||||
sys.stdout.flush() # before any crash
|
||||
# if force_backend == "freedesktop_dbus":
|
||||
# return
|
||||
if force_backend == "default":
|
||||
force_backend = None
|
||||
try:
|
||||
start = time.time()
|
||||
for _ in range(n):
|
||||
pyscreenshot.grab(
|
||||
backend=force_backend, childprocess=childprocess, bbox=bbox
|
||||
)
|
||||
end = time.time()
|
||||
dt = end - start
|
||||
s = "%-4.2g sec\t" % dt
|
||||
s += "(%5d ms per call)" % (1000.0 * dt / n)
|
||||
sys.stdout.write(s)
|
||||
finally:
|
||||
print("")
|
||||
|
||||
|
||||
novirt = [
|
||||
GnomeDBusWrapper.name,
|
||||
KwinDBusWrapper.name,
|
||||
GnomeScreenshotWrapper.name,
|
||||
FreedesktopDBusWrapper.name,
|
||||
]
|
||||
|
||||
|
||||
def run_all(n, childprocess_param, virtual_only=True, bbox=None):
|
||||
debug = True
|
||||
print("")
|
||||
print("n=%s" % n)
|
||||
print("------------------------------------------------------")
|
||||
|
||||
if bbox:
|
||||
x1, y1, x2, y2 = map(str, bbox)
|
||||
bbox = ":".join(map(str, (x1, y1, x2, y2)))
|
||||
bboxpar = ["--bbox", bbox]
|
||||
else:
|
||||
bboxpar = []
|
||||
if debug:
|
||||
debugpar = ["--debug"]
|
||||
else:
|
||||
debugpar = []
|
||||
for x in ["default"] + pyscreenshot.backends():
|
||||
if x == "freedesktop_dbus":
|
||||
continue
|
||||
backendpar = ["--backend", x]
|
||||
# skip non X backends
|
||||
if virtual_only and x in novirt:
|
||||
continue
|
||||
p = run_mod_as_subproc(
|
||||
"pyscreenshot.check.speedtest",
|
||||
["--childprocess", childprocess_param, "--number", str(n)]
|
||||
+ bboxpar
|
||||
+ debugpar
|
||||
+ backendpar,
|
||||
)
|
||||
print(p.stdout)
|
||||
|
||||
|
||||
@entrypoint
|
||||
def speedtest(virtual_display=False, backend="", childprocess="", bbox="", number=10):
|
||||
"""Performance test of all back-ends.
|
||||
|
||||
:param virtual_display: run with Xvfb
|
||||
:param bbox: bounding box coordinates x1:y1:x2:y2
|
||||
:param backend: back-end can be forced if set (example:default, scrot, wx,..),
|
||||
otherwise all back-ends are tested
|
||||
:param childprocess: pyscreenshot parameter childprocess (0/1)
|
||||
:param number: number of screenshots for each backend (default:10)
|
||||
"""
|
||||
childprocess_param = childprocess
|
||||
if childprocess == "":
|
||||
childprocess = True # default
|
||||
elif childprocess == "0":
|
||||
childprocess = False
|
||||
elif childprocess == "1":
|
||||
childprocess = True
|
||||
else:
|
||||
raise ValueError("invalid childprocess value")
|
||||
|
||||
if bbox:
|
||||
x1, y1, x2, y2 = map(int, bbox.split(":"))
|
||||
bbox = x1, y1, x2, y2
|
||||
else:
|
||||
bbox = None
|
||||
|
||||
def f(virtual_only):
|
||||
if backend:
|
||||
try:
|
||||
run(backend, number, childprocess, bbox=bbox)
|
||||
except pyscreenshot.FailedBackendError:
|
||||
pass
|
||||
else:
|
||||
run_all(number, childprocess_param, virtual_only=virtual_only, bbox=bbox)
|
||||
|
||||
if virtual_display:
|
||||
from pyvirtualdisplay import Display
|
||||
|
||||
with Display(visible=0):
|
||||
f(virtual_only=True)
|
||||
else:
|
||||
f(virtual_only=False)
|
@ -0,0 +1,24 @@
|
||||
import platform
|
||||
|
||||
import pyscreenshot
|
||||
from pyscreenshot import backend_version
|
||||
|
||||
|
||||
def print_name_version(name, version):
|
||||
s = "{:<20} {}".format(name, version)
|
||||
print(s)
|
||||
|
||||
|
||||
def print_versions():
|
||||
print_name_version("python", platform.python_version())
|
||||
print_name_version("pyscreenshot", pyscreenshot.__version__)
|
||||
|
||||
for name in pyscreenshot.backends():
|
||||
v = backend_version(name)
|
||||
if not v:
|
||||
v = ""
|
||||
print_name_version(name, v)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print_versions()
|
41
venv/lib/python3.12/site-packages/pyscreenshot/childproc.py
Normal file
41
venv/lib/python3.12/site-packages/pyscreenshot/childproc.py
Normal file
@ -0,0 +1,41 @@
|
||||
import logging
|
||||
import os
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from pyscreenshot.err import FailedBackendError
|
||||
from pyscreenshot.imcodec import codec
|
||||
from pyscreenshot.util import run_mod_as_subproc
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def childprocess_backend_version(backend):
|
||||
p = run_mod_as_subproc("pyscreenshot.cli.print_backend_version", [backend])
|
||||
if p.return_code != 0:
|
||||
log.warning(p)
|
||||
raise FailedBackendError(p)
|
||||
|
||||
return p.stdout
|
||||
|
||||
|
||||
def childprocess_grab(backend, bbox):
|
||||
with TemporaryDirectory(prefix="pyscreenshot") as tmpdirname:
|
||||
filename = os.path.join(tmpdirname, "screenshot.png")
|
||||
cmd = ["--filename", filename]
|
||||
if bbox:
|
||||
x1, y1, x2, y2 = map(str, bbox)
|
||||
bbox = ":".join(map(str, (x1, y1, x2, y2)))
|
||||
cmd += ["--bbox", bbox]
|
||||
if backend:
|
||||
cmd += ["--backend", backend]
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
cmd += ["--debug"]
|
||||
|
||||
p = run_mod_as_subproc("pyscreenshot.cli.grab", cmd)
|
||||
if p.return_code != 0:
|
||||
# log.debug(p)
|
||||
raise FailedBackendError(p)
|
||||
|
||||
data = open(filename, "rb").read()
|
||||
data = codec[1](data)
|
||||
return data
|
30
venv/lib/python3.12/site-packages/pyscreenshot/cli/grab.py
Normal file
30
venv/lib/python3.12/site-packages/pyscreenshot/cli/grab.py
Normal file
@ -0,0 +1,30 @@
|
||||
from entrypoint2 import entrypoint
|
||||
|
||||
import pyscreenshot
|
||||
from pyscreenshot.imcodec import codec
|
||||
|
||||
|
||||
@entrypoint
|
||||
def main(filename="", bbox="", backend="", show=False):
|
||||
"""Copy the contents of the screen to file.
|
||||
|
||||
:param filename: output file
|
||||
:param show: show image
|
||||
:param bbox: bounding box coordinates x1:y1:x2:y2
|
||||
:param backend: back-end can be forced if set (example:scrot, wx,..),
|
||||
otherwise back-end is automatic
|
||||
"""
|
||||
backend = backend if backend else None
|
||||
if bbox:
|
||||
x1, y1, x2, y2 = map(int, bbox.split(":"))
|
||||
bbox = x1, y1, x2, y2
|
||||
else:
|
||||
bbox = None
|
||||
|
||||
im = pyscreenshot.grab(bbox=bbox, childprocess=False, backend=backend)
|
||||
if filename:
|
||||
b = codec[0](im)
|
||||
with open(filename, "wb") as f:
|
||||
f.write(b)
|
||||
if show:
|
||||
im.show()
|
@ -0,0 +1,23 @@
|
||||
import logging
|
||||
|
||||
from entrypoint2 import entrypoint
|
||||
|
||||
from pyscreenshot.loader import backend_version2
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@entrypoint
|
||||
def main(backend):
|
||||
"""Print pyscreenshot back-end version.
|
||||
|
||||
:param backend: back-end (example:scrot, wx,..)
|
||||
"""
|
||||
backend = backend if backend else None
|
||||
|
||||
try:
|
||||
v = backend_version2(backend)
|
||||
except Exception as e:
|
||||
log.warning(e)
|
||||
v = ""
|
||||
print(v)
|
2
venv/lib/python3.12/site-packages/pyscreenshot/err.py
Normal file
2
venv/lib/python3.12/site-packages/pyscreenshot/err.py
Normal file
@ -0,0 +1,2 @@
|
||||
class FailedBackendError(Exception):
|
||||
pass
|
@ -0,0 +1,8 @@
|
||||
"Grab the part of the screen"
|
||||
import pyscreenshot as ImageGrab
|
||||
|
||||
# part of the screen
|
||||
im = ImageGrab.grab(bbox=(10, 10, 510, 510)) # X1,Y1,X2,Y2
|
||||
|
||||
# save image file
|
||||
im.save("box.png")
|
@ -0,0 +1,8 @@
|
||||
"Grab the whole screen"
|
||||
import pyscreenshot as ImageGrab
|
||||
|
||||
# grab fullscreen
|
||||
im = ImageGrab.grab()
|
||||
|
||||
# save image file
|
||||
im.save("fullscreen.png")
|
@ -0,0 +1,14 @@
|
||||
"Create screenshot of xmessage with Xvfb"
|
||||
from time import sleep
|
||||
|
||||
from easyprocess import EasyProcess
|
||||
from pyvirtualdisplay import Display
|
||||
|
||||
import pyscreenshot as ImageGrab
|
||||
|
||||
with Display(size=(100, 60)) as disp: # start Xvfb display
|
||||
# display is available
|
||||
with EasyProcess(["xmessage", "hello"]): # start xmessage
|
||||
sleep(1) # wait for displaying window
|
||||
img = ImageGrab.grab()
|
||||
img.save("xmessage.png")
|
36
venv/lib/python3.12/site-packages/pyscreenshot/imcodec.py
Normal file
36
venv/lib/python3.12/site-packages/pyscreenshot/imcodec.py
Normal file
@ -0,0 +1,36 @@
|
||||
import io
|
||||
|
||||
from PIL import Image
|
||||
|
||||
# def _coder(im):
|
||||
# if im:
|
||||
# data = {
|
||||
# 'pixels': im.tobytes(),
|
||||
# 'size': im.size,
|
||||
# 'mode': im.mode,
|
||||
# }
|
||||
# return data
|
||||
#
|
||||
#
|
||||
# def _decoder(data):
|
||||
# if data:
|
||||
# im = Image.frombytes(data['mode'], data['size'], data['pixels'])
|
||||
# return im
|
||||
|
||||
|
||||
def _coder(im):
|
||||
if im:
|
||||
b = io.BytesIO()
|
||||
im.save(b, format="png")
|
||||
data = b.getvalue()
|
||||
return data
|
||||
|
||||
|
||||
def _decoder(data):
|
||||
if data:
|
||||
b = io.BytesIO(data)
|
||||
im = Image.open(b)
|
||||
return im
|
||||
|
||||
|
||||
codec = (_coder, _decoder)
|
178
venv/lib/python3.12/site-packages/pyscreenshot/loader.py
Normal file
178
venv/lib/python3.12/site-packages/pyscreenshot/loader.py
Normal file
@ -0,0 +1,178 @@
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from pyscreenshot.childproc import childprocess_grab
|
||||
from pyscreenshot.err import FailedBackendError
|
||||
from pyscreenshot.plugins.freedesktop_dbus import FreedesktopDBusWrapper
|
||||
from pyscreenshot.plugins.gdk3pixbuf import Gdk3PixbufWrapper
|
||||
from pyscreenshot.plugins.gnome_dbus import GnomeDBusWrapper
|
||||
from pyscreenshot.plugins.gnome_screenshot import GnomeScreenshotWrapper
|
||||
from pyscreenshot.plugins.grim import GrimWrapper
|
||||
from pyscreenshot.plugins.imagemagick import ImagemagickWrapper
|
||||
|
||||
# from pyscreenshot.plugins.kwin_dbus import KwinDBusWrapper
|
||||
from pyscreenshot.plugins.mac_quartz import MacQuartzWrapper
|
||||
from pyscreenshot.plugins.mac_screencapture import ScreencaptureWrapper
|
||||
from pyscreenshot.plugins.maim import MaimWrapper
|
||||
from pyscreenshot.plugins.msswrap import MssWrapper
|
||||
from pyscreenshot.plugins.pilwrap import PilWrapper
|
||||
from pyscreenshot.plugins.pyside2_grabwindow import PySide2GrabWindow
|
||||
|
||||
# from pyscreenshot.plugins.pyside_grabwindow import PySideGrabWindow
|
||||
# from pyscreenshot.plugins.qt4grabwindow import Qt4GrabWindow
|
||||
from pyscreenshot.plugins.qt5grabwindow import Qt5GrabWindow
|
||||
from pyscreenshot.plugins.scrot import ScrotWrapper
|
||||
from pyscreenshot.plugins.wxscreen import WxScreen
|
||||
|
||||
# from pyscreenshot.plugins.ksnip import KsnipWrapper
|
||||
from pyscreenshot.util import (
|
||||
platform_is_linux,
|
||||
platform_is_osx,
|
||||
platform_is_win,
|
||||
use_x_display,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
backend_dict = {
|
||||
PilWrapper.name: PilWrapper,
|
||||
MssWrapper.name: MssWrapper,
|
||||
ScrotWrapper.name: ScrotWrapper,
|
||||
GrimWrapper.name: GrimWrapper,
|
||||
MaimWrapper.name: MaimWrapper,
|
||||
ImagemagickWrapper.name: ImagemagickWrapper,
|
||||
Qt5GrabWindow.name: Qt5GrabWindow,
|
||||
# Qt4GrabWindow.name: Qt4GrabWindow,
|
||||
PySide2GrabWindow.name: PySide2GrabWindow,
|
||||
# PySideGrabWindow.name: PySideGrabWindow,
|
||||
WxScreen.name: WxScreen,
|
||||
Gdk3PixbufWrapper.name: Gdk3PixbufWrapper,
|
||||
ScreencaptureWrapper.name: ScreencaptureWrapper,
|
||||
MacQuartzWrapper.name: MacQuartzWrapper,
|
||||
FreedesktopDBusWrapper.name: FreedesktopDBusWrapper,
|
||||
GnomeDBusWrapper.name: GnomeDBusWrapper,
|
||||
GnomeScreenshotWrapper.name: GnomeScreenshotWrapper,
|
||||
# KwinDBusWrapper.name: KwinDBusWrapper,
|
||||
# XwdWrapper.name: XwdWrapper,
|
||||
# KsnipWrapper.name: KsnipWrapper,
|
||||
}
|
||||
|
||||
|
||||
def qt():
|
||||
yield Qt5GrabWindow
|
||||
# yield Qt4GrabWindow
|
||||
yield PySide2GrabWindow
|
||||
# yield PySideGrabWindow
|
||||
|
||||
|
||||
def backends(childprocess):
|
||||
# the order is based on performance
|
||||
if platform_is_linux():
|
||||
if use_x_display():
|
||||
if childprocess:
|
||||
yield ScrotWrapper
|
||||
yield PilWrapper
|
||||
yield MssWrapper
|
||||
else:
|
||||
yield PilWrapper
|
||||
yield MssWrapper
|
||||
yield ScrotWrapper
|
||||
yield MaimWrapper
|
||||
yield ImagemagickWrapper
|
||||
yield Gdk3PixbufWrapper
|
||||
yield WxScreen
|
||||
for x in qt():
|
||||
yield x
|
||||
|
||||
yield GnomeDBusWrapper
|
||||
|
||||
# on screen notification
|
||||
# "The process is not authorized to take a screenshot"
|
||||
# yield KwinDBusWrapper
|
||||
|
||||
# flash effect
|
||||
yield GnomeScreenshotWrapper
|
||||
|
||||
yield GrimWrapper
|
||||
# yield KsnipWrapper
|
||||
|
||||
# confirmation dialog
|
||||
yield FreedesktopDBusWrapper
|
||||
|
||||
elif platform_is_osx():
|
||||
yield PilWrapper
|
||||
yield MssWrapper
|
||||
|
||||
# alternatives for older pillow versions
|
||||
yield ScreencaptureWrapper
|
||||
yield MacQuartzWrapper
|
||||
|
||||
# qt has some color difference
|
||||
|
||||
# does not work: Gdk3, wx, Imagemagick
|
||||
|
||||
elif platform_is_win():
|
||||
yield PilWrapper
|
||||
yield MssWrapper
|
||||
else:
|
||||
yield PilWrapper
|
||||
yield MssWrapper
|
||||
for x in backend_dict.values():
|
||||
yield x
|
||||
|
||||
|
||||
def select_childprocess(childprocess, backend_class):
|
||||
if backend_class.is_subprocess:
|
||||
# backend is always a subprocess -> nothing to do
|
||||
return False
|
||||
|
||||
return childprocess
|
||||
|
||||
|
||||
def auto(bbox, childprocess):
|
||||
im = None
|
||||
for backend_class in backends(childprocess):
|
||||
backend_name = backend_class.name
|
||||
try:
|
||||
if select_childprocess(childprocess, backend_class):
|
||||
log.debug('running "%s" in child process', backend_name)
|
||||
im = childprocess_grab(backend_name, bbox)
|
||||
else:
|
||||
obj = backend_class()
|
||||
|
||||
im = obj.grab(bbox)
|
||||
break
|
||||
except Exception:
|
||||
msg = traceback.format_exc()
|
||||
log.debug(msg)
|
||||
if not im:
|
||||
msg = "All backends failed!"
|
||||
raise FailedBackendError(msg)
|
||||
|
||||
return im
|
||||
|
||||
|
||||
def force(backend_name, bbox, childprocess):
|
||||
backend_class = backend_dict[backend_name]
|
||||
if select_childprocess(childprocess, backend_class):
|
||||
log.debug('running "%s" in child process', backend_name)
|
||||
return childprocess_grab(backend_name, bbox)
|
||||
else:
|
||||
obj = backend_class()
|
||||
im = obj.grab(bbox)
|
||||
return im
|
||||
|
||||
|
||||
def backend_grab(backend, bbox, childprocess):
|
||||
if backend:
|
||||
return force(backend, bbox, childprocess)
|
||||
else:
|
||||
return auto(bbox, childprocess)
|
||||
|
||||
|
||||
def backend_version2(backend_name):
|
||||
backend_class = backend_dict[backend_name]
|
||||
obj = backend_class()
|
||||
v = obj.backend_version()
|
||||
return v
|
@ -0,0 +1,5 @@
|
||||
UNKNOWN_VERSION = "?.?"
|
||||
|
||||
|
||||
class CBackend(object):
|
||||
is_subprocess = False
|
@ -0,0 +1,76 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from pyscreenshot.plugins.backend import UNKNOWN_VERSION, CBackend
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FreedesktopDBusError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FreedesktopDBusWrapper(CBackend):
|
||||
name = "freedesktop_dbus"
|
||||
is_subprocess = True
|
||||
|
||||
def grab(self, bbox=None):
|
||||
has_jeepney = False
|
||||
try:
|
||||
from jeepney import DBusAddress, new_method_call
|
||||
from jeepney.bus_messages import MatchRule, message_bus
|
||||
from jeepney.io.blocking import Proxy, open_dbus_connection
|
||||
|
||||
has_jeepney = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if not has_jeepney:
|
||||
raise FreedesktopDBusError("jeepney library is missing")
|
||||
|
||||
portal = DBusAddress(
|
||||
object_path="/org/freedesktop/portal/desktop",
|
||||
bus_name="org.freedesktop.portal.Desktop",
|
||||
)
|
||||
screenshot = portal.with_interface("org.freedesktop.portal.Screenshot")
|
||||
|
||||
conn = open_dbus_connection()
|
||||
|
||||
token = "pyscreenshot"
|
||||
sender_name = conn.unique_name[1:].replace(".", "_")
|
||||
handle = f"/org/freedesktop/portal/desktop/request/{sender_name}/{token}"
|
||||
|
||||
response_rule = MatchRule(
|
||||
type="signal", interface="org.freedesktop.portal.Request", path=handle
|
||||
)
|
||||
Proxy(message_bus, conn).AddMatch(response_rule)
|
||||
|
||||
with conn.filter(response_rule) as responses:
|
||||
req = new_method_call(
|
||||
screenshot,
|
||||
"Screenshot",
|
||||
"sa{sv}",
|
||||
("", {"handle_token": ("s", token), "interactive": ("b", False)}),
|
||||
)
|
||||
conn.send_and_get_reply(req)
|
||||
response_msg = conn.recv_until_filtered(responses)
|
||||
|
||||
response, results = response_msg.body
|
||||
|
||||
im = False
|
||||
if response == 0:
|
||||
filename = results["uri"][1].split("file://", 1)[-1]
|
||||
if os.path.isfile(filename):
|
||||
im = Image.open(filename)
|
||||
os.remove(filename)
|
||||
|
||||
conn.close()
|
||||
|
||||
if bbox and im:
|
||||
im = im.crop(bbox)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
return UNKNOWN_VERSION
|
@ -0,0 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Gdk3-based screenshotting.
|
||||
|
||||
Adapted from https://stackoverflow.com/a/37768950/81636, but uses
|
||||
buffers directly instead of saving intermediate files (which is slow).
|
||||
"""
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
from pyscreenshot.util import platform_is_osx
|
||||
|
||||
|
||||
class Gdk3BackendError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Gdk3PixbufWrapper(CBackend):
|
||||
name = "pygdk3"
|
||||
|
||||
def grab(self, bbox=None):
|
||||
"""Grabs an image directly to a buffer.
|
||||
|
||||
:param bbox: Optional tuple or list containing (x1, y1, x2, y2) coordinates
|
||||
of sub-region to capture.
|
||||
:return: PIL RGB image
|
||||
:raises: ValueError, if image data does not have 3 channels (RGB), each with 8
|
||||
bits.
|
||||
:rtype: Image
|
||||
"""
|
||||
if platform_is_osx():
|
||||
raise Gdk3BackendError("osx not supported")
|
||||
import gi # type: ignore
|
||||
|
||||
gi.require_version("Gdk", "3.0")
|
||||
# gi.require_version('GdkPixbuf', '2.0')
|
||||
from gi.repository import Gdk, GdkPixbuf # type: ignore
|
||||
|
||||
# read_pixel_bytes: New in version 2.32.
|
||||
if GdkPixbuf.PIXBUF_MAJOR == 2:
|
||||
if GdkPixbuf.PIXBUF_MINOR < 32:
|
||||
raise ValueError(
|
||||
"GdkPixbuf min supported version: 2.32 current:"
|
||||
+ GdkPixbuf.PIXBUF_VERSION
|
||||
)
|
||||
|
||||
w = Gdk.get_default_root_window()
|
||||
if bbox is not None:
|
||||
g = [bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]]
|
||||
else:
|
||||
g = w.get_geometry()
|
||||
pb = Gdk.pixbuf_get_from_window(w, *g)
|
||||
if not pb:
|
||||
raise Gdk3BackendError("empty buffer")
|
||||
|
||||
if pb.get_bits_per_sample() != 8:
|
||||
raise Gdk3BackendError("Expected 8 bits per pixel.")
|
||||
elif pb.get_n_channels() != 3:
|
||||
raise Gdk3BackendError("Expected RGB image.")
|
||||
|
||||
# Read the entire buffer into a python bytes object.
|
||||
# read_pixel_bytes: New in version 2.32.
|
||||
pixel_bytes = pb.read_pixel_bytes().get_data() # type: bytes
|
||||
width, height = g[2], g[3]
|
||||
|
||||
# Probably for SSE alignment reasons, the pixbuf has extra data in each line.
|
||||
# The args after "raw" help handle this; see
|
||||
# http://effbot.org/imagingbook/decoder.htm#the-raw-decoder
|
||||
return Image.frombytes(
|
||||
"RGB", (width, height), pixel_bytes, "raw", "RGB", pb.get_rowstride(), 1
|
||||
)
|
||||
|
||||
def backend_version(self):
|
||||
import gi
|
||||
|
||||
return ".".join(map(str, gi.version_info))
|
@ -0,0 +1,79 @@
|
||||
import logging
|
||||
|
||||
from pyscreenshot.plugins.backend import UNKNOWN_VERSION, CBackend
|
||||
from pyscreenshot.tempexport import read_func_img
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GnomeDBusError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GnomeDBusWrapper(CBackend):
|
||||
name = "gnome_dbus"
|
||||
is_subprocess = True
|
||||
|
||||
def grab(self, bbox=None):
|
||||
im = read_func_img(self._grab_to_file, bbox)
|
||||
return im
|
||||
|
||||
def _grab_to_file(self, filename, bbox=None):
|
||||
has_jeepney = False
|
||||
try:
|
||||
# from jeepney import new_method_call
|
||||
from jeepney.io.blocking import open_dbus_connection # type: ignore
|
||||
from jeepney.wrappers import MessageGenerator # type: ignore
|
||||
from jeepney.wrappers import new_method_call
|
||||
|
||||
has_jeepney = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if not has_jeepney:
|
||||
raise GnomeDBusError("jeepney library is missing")
|
||||
|
||||
class Screenshot(MessageGenerator):
|
||||
interface = "org.gnome.Shell.Screenshot"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
object_path="/org/gnome/Shell/Screenshot",
|
||||
bus_name="org.gnome.Shell.Screenshot",
|
||||
):
|
||||
super().__init__(object_path=object_path, bus_name=bus_name)
|
||||
|
||||
def Screenshot(self, include_cursor, flash, filename):
|
||||
return new_method_call(
|
||||
self, "Screenshot", "bbs", (include_cursor, flash, filename)
|
||||
)
|
||||
|
||||
def ScreenshotArea(self, x, y, width, height, flash, filename):
|
||||
return new_method_call(
|
||||
self,
|
||||
"ScreenshotArea",
|
||||
"iiiibs",
|
||||
(x, y, width, height, flash, filename),
|
||||
)
|
||||
|
||||
# https://jeepney.readthedocs.io/en/latest/integrate.html
|
||||
connection = open_dbus_connection(bus="SESSION")
|
||||
dbscr = Screenshot()
|
||||
if bbox:
|
||||
msg = dbscr.ScreenshotArea(
|
||||
bbox[0],
|
||||
bbox[1],
|
||||
bbox[2] - bbox[0],
|
||||
bbox[3] - bbox[1],
|
||||
False,
|
||||
filename,
|
||||
)
|
||||
else:
|
||||
msg = dbscr.Screenshot(False, False, filename)
|
||||
reply = connection.send_and_get_reply(msg)
|
||||
result = reply.body[0]
|
||||
if not result:
|
||||
raise GnomeDBusError("dbus error: %s %s" % (msg, result))
|
||||
|
||||
def backend_version(self):
|
||||
return UNKNOWN_VERSION
|
@ -0,0 +1,37 @@
|
||||
from easyprocess import EasyProcess
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
from pyscreenshot.tempexport import read_prog_img
|
||||
from pyscreenshot.util import extract_version
|
||||
|
||||
PROGRAM = "gnome-screenshot"
|
||||
|
||||
# https://gitlab.gnome.org/GNOME/gnome-screenshot/blob/master/src/screenshot-utils.c
|
||||
# DBus is used for screenshot.
|
||||
# If it doesn't succeed or $GNOME_SCREENSHOT_FORCE_FALLBACK is set then X DISPLAY is used.
|
||||
# Flash effect! https://bugzilla.gnome.org/show_bug.cgi?id=672759
|
||||
|
||||
|
||||
class GnomeScreenshotWrapper(CBackend):
|
||||
"""Plugin for ``pyscreenshot`` that uses ``gnome-screenshot``
|
||||
https://git.gnome.org/browse/gnome-screenshot/
|
||||
|
||||
This plugin can take screenshot when system is running Wayland.
|
||||
Info: https://bugs.freedesktop.org/show_bug.cgi?id=98672#c1
|
||||
"""
|
||||
|
||||
name = "gnome-screenshot"
|
||||
is_subprocess = True
|
||||
|
||||
def grab(self, bbox=None):
|
||||
im = read_prog_img([PROGRAM, "-f"])
|
||||
if bbox:
|
||||
im = im.crop(bbox)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
p = EasyProcess([PROGRAM, "--version"])
|
||||
p.enable_stdout_log = False
|
||||
p.enable_stderr_log = False
|
||||
p.call()
|
||||
return extract_version(p.stdout.replace("-", " "))
|
@ -0,0 +1,47 @@
|
||||
"""
|
||||
Backend for grim (https://github.com/emersion/grim), a Wayland screen tool for
|
||||
environments other than Gnome and KDE, such as Sway.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from easyprocess import EasyProcess
|
||||
|
||||
from pyscreenshot.plugins.backend import UNKNOWN_VERSION, CBackend
|
||||
from pyscreenshot.tempexport import read_prog_img
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PROGRAM = "grim"
|
||||
|
||||
|
||||
class GrimWrapper(CBackend):
|
||||
name = "grim"
|
||||
is_subprocess = True
|
||||
|
||||
def _bbox_to_grim_region(self, bbox):
|
||||
"""
|
||||
Translate pyscreenshot's bbox tuple convention of (x1, y1, x2, y2) to
|
||||
grim's bbox convention, which is a string of the following format:
|
||||
<x>,<y> <width>x<height>
|
||||
"""
|
||||
x1, y1, x2, y2 = bbox
|
||||
width = x2 - x1
|
||||
height = y2 - y1
|
||||
return "{},{} {}x{}".format(x1, y1, width, height)
|
||||
|
||||
def grab(self, bbox=None):
|
||||
if bbox:
|
||||
# using grim's built-in cropping feature
|
||||
region = self._bbox_to_grim_region(bbox)
|
||||
return read_prog_img([PROGRAM, "-g", region])
|
||||
return read_prog_img([PROGRAM])
|
||||
|
||||
def backend_version(self):
|
||||
# grim doesn't have a version flag for some reason
|
||||
p = EasyProcess([PROGRAM, "-help"])
|
||||
p.enable_stdout_log = False
|
||||
p.enable_stderr_log = False
|
||||
p.call()
|
||||
if p.return_code == 0:
|
||||
return UNKNOWN_VERSION
|
@ -0,0 +1,35 @@
|
||||
from easyprocess import EasyProcess
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
from pyscreenshot.tempexport import read_prog_img
|
||||
from pyscreenshot.util import extract_version, platform_is_osx
|
||||
|
||||
PROGRAM = "import"
|
||||
# http://www.imagemagick.org/
|
||||
|
||||
|
||||
class ImagemagickBackendError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ImagemagickWrapper(CBackend):
|
||||
name = "imagemagick"
|
||||
is_subprocess = True
|
||||
|
||||
def grab(self, bbox=None):
|
||||
if platform_is_osx():
|
||||
raise ImagemagickBackendError("osx not supported")
|
||||
|
||||
command = [PROGRAM, "-silent", "-window", "root"]
|
||||
if bbox:
|
||||
pbox = "{}x{}+{}+{}".format(
|
||||
bbox[2] - bbox[0], bbox[3] - bbox[1], bbox[0], bbox[1]
|
||||
)
|
||||
command += ["-crop", pbox]
|
||||
im = read_prog_img(command)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
stdout = EasyProcess([PROGRAM, "-version"]).call().stdout
|
||||
s = stdout.splitlines()[0]
|
||||
return extract_version(s.replace("-", " "))
|
@ -0,0 +1,41 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from easyprocess import EasyProcess
|
||||
from PIL import Image
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
from pyscreenshot.tempexport import RunProgError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PROGRAM = "ksnip"
|
||||
|
||||
|
||||
class KsnipWrapper(CBackend):
|
||||
name = "ksnip"
|
||||
is_subprocess = True
|
||||
|
||||
def grab(self, bbox=None):
|
||||
cmd = [PROGRAM, "--fullscreen", "--save"]
|
||||
p = EasyProcess(cmd)
|
||||
p.call()
|
||||
if p.return_code != 0:
|
||||
raise RunProgError(p.stderr)
|
||||
lastline = p.stdout.splitlines()[-1]
|
||||
if "Image Saved" not in lastline:
|
||||
raise RunProgError(p.stderr)
|
||||
filename = lastline.split()[-1]
|
||||
im = Image.open(filename)
|
||||
os.remove(filename)
|
||||
|
||||
# TODO: bbox param
|
||||
if bbox:
|
||||
im = im.crop(bbox)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
for line in EasyProcess([PROGRAM, "--version"]).call().stderr.splitlines():
|
||||
if "version" in line.lower():
|
||||
return line.split()[-1]
|
@ -0,0 +1,77 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from pyscreenshot.plugins.backend import UNKNOWN_VERSION, CBackend
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class KdeDBusError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# https://gitlab.gnome.org/GNOME/gimp/-/issues/6626
|
||||
# The org.kde.kwin.Screenshot interface is deprecated in KDE Plasma 5.22.
|
||||
|
||||
# "The process is not authorized to take a screenshot"
|
||||
|
||||
|
||||
class KwinDBusWrapper(CBackend):
|
||||
name = "kwin_dbus"
|
||||
is_subprocess = True
|
||||
|
||||
def grab(self, bbox=None):
|
||||
has_jeepney = False
|
||||
try:
|
||||
# from jeepney import new_method_call
|
||||
from jeepney.io.blocking import open_dbus_connection # type: ignore
|
||||
from jeepney.wrappers import MessageGenerator # type: ignore
|
||||
from jeepney.wrappers import new_method_call
|
||||
|
||||
has_jeepney = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if not has_jeepney:
|
||||
raise KdeDBusError("jeepney library is missing")
|
||||
|
||||
class Screenshot(MessageGenerator):
|
||||
interface = "org.kde.kwin.Screenshot"
|
||||
|
||||
def __init__(self, object_path="/Screenshot", bus_name="org.kde.KWin"):
|
||||
super().__init__(object_path=object_path, bus_name=bus_name)
|
||||
|
||||
def screenshotFullscreen(self, captureCursor):
|
||||
return new_method_call(
|
||||
self, "screenshotFullscreen", "b", (captureCursor,)
|
||||
)
|
||||
|
||||
def screenshotArea(self, x, y, width, height, captureCursor):
|
||||
return new_method_call(
|
||||
self,
|
||||
"screenshotArea",
|
||||
"iiiib",
|
||||
(x, y, width, height, captureCursor),
|
||||
)
|
||||
|
||||
# https://jeepney.readthedocs.io/en/latest/integrate.html
|
||||
connection = open_dbus_connection(bus="SESSION")
|
||||
dbscr = Screenshot()
|
||||
# bbox not working:
|
||||
# if bbox: msg = dbscr.screenshotArea(bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1], False)
|
||||
msg = dbscr.screenshotFullscreen(False)
|
||||
reply = connection.send_and_get_reply(msg)
|
||||
filename = reply.body[0]
|
||||
if not filename:
|
||||
raise KdeDBusError()
|
||||
|
||||
im = Image.open(filename)
|
||||
os.remove(filename)
|
||||
if bbox:
|
||||
im = im.crop(bbox)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
return UNKNOWN_VERSION
|
@ -0,0 +1,67 @@
|
||||
# Javier Escalada Gomez
|
||||
#
|
||||
# from:
|
||||
# https://stackoverflow.com/questions/4524723/take-screenshot-in-python-on-mac-os-x
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
from pyscreenshot.tempexport import read_func_img
|
||||
|
||||
|
||||
class MacQuartzWrapper(CBackend):
|
||||
name = "mac_quartz"
|
||||
|
||||
def grab(self, bbox=None):
|
||||
im = read_func_img(self._grab_to_file, bbox)
|
||||
return im
|
||||
|
||||
def _grab_to_file(self, filename, bbox=None, dpi=72):
|
||||
# Should query dpi from somewhere, e.g for retina displays?
|
||||
import LaunchServices # type: ignore
|
||||
import Quartz # type: ignore
|
||||
import Quartz.CoreGraphics as CG # type: ignore
|
||||
from Cocoa import NSURL # type: ignore
|
||||
|
||||
if bbox:
|
||||
width = bbox[2] - bbox[0]
|
||||
height = bbox[3] - bbox[1]
|
||||
region = CG.CGRectMake(bbox[0], bbox[1], width, height)
|
||||
else:
|
||||
region = CG.CGRectInfinite
|
||||
|
||||
# Create screenshot as CGImage
|
||||
image = CG.CGWindowListCreateImage(
|
||||
region,
|
||||
CG.kCGWindowListOptionOnScreenOnly,
|
||||
CG.kCGNullWindowID,
|
||||
CG.kCGWindowImageDefault,
|
||||
)
|
||||
|
||||
file_type = LaunchServices.kUTTypePNG
|
||||
|
||||
url = NSURL.fileURLWithPath_(filename)
|
||||
|
||||
dest = Quartz.CGImageDestinationCreateWithURL(
|
||||
url,
|
||||
file_type,
|
||||
# 1 image in file
|
||||
1,
|
||||
None,
|
||||
)
|
||||
|
||||
properties = {
|
||||
Quartz.kCGImagePropertyDPIWidth: dpi,
|
||||
Quartz.kCGImagePropertyDPIHeight: dpi,
|
||||
}
|
||||
|
||||
# Add the image to the destination, characterizing the image with
|
||||
# the properties dictionary.
|
||||
Quartz.CGImageDestinationAddImage(dest, image, properties)
|
||||
|
||||
# When all the images (only 1 in this example) are added to the destination,
|
||||
# finalize the CGImageDestination object.
|
||||
Quartz.CGImageDestinationFinalize(dest)
|
||||
|
||||
def backend_version(self):
|
||||
import objc # type: ignore
|
||||
|
||||
return objc.__version__
|
@ -0,0 +1,38 @@
|
||||
from easyprocess import EasyProcess
|
||||
|
||||
from pyscreenshot.plugins.backend import UNKNOWN_VERSION, CBackend
|
||||
from pyscreenshot.tempexport import read_prog_img
|
||||
from pyscreenshot.util import platform_is_osx
|
||||
|
||||
PROGRAM = "screencapture"
|
||||
# https://ss64.com/osx/screencapture.html
|
||||
# By default screenshots are saved as .png files,
|
||||
|
||||
|
||||
class ScreencaptureError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ScreencaptureWrapper(CBackend):
|
||||
name = "mac_screencapture"
|
||||
is_subprocess = True
|
||||
|
||||
def grab(self, bbox=None):
|
||||
if not platform_is_osx():
|
||||
raise ScreencaptureError("This backend runs only on Darwin")
|
||||
|
||||
command = [PROGRAM, "-x"]
|
||||
if bbox:
|
||||
width = bbox[2] - bbox[0]
|
||||
height = bbox[3] - bbox[1]
|
||||
command += ["-R{},{},{},{}".format(bbox[0], bbox[1], width, height)]
|
||||
im = read_prog_img(command)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
p = EasyProcess([PROGRAM, "-help"])
|
||||
p.enable_stdout_log = False
|
||||
p.enable_stderr_log = False
|
||||
p.call()
|
||||
if p.return_code == 0:
|
||||
return UNKNOWN_VERSION
|
@ -0,0 +1,30 @@
|
||||
import logging
|
||||
|
||||
from easyprocess import EasyProcess
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
from pyscreenshot.tempexport import read_prog_img
|
||||
from pyscreenshot.util import extract_version
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PROGRAM = "maim"
|
||||
|
||||
|
||||
class MaimWrapper(CBackend):
|
||||
name = "maim"
|
||||
is_subprocess = True
|
||||
|
||||
def grab(self, bbox=None):
|
||||
cmd = [PROGRAM, "--hidecursor"]
|
||||
if bbox:
|
||||
width = bbox[2] - bbox[0]
|
||||
height = bbox[3] - bbox[1]
|
||||
# https://github.com/naelstrof/maim/issues/119
|
||||
cmd += ["-g", "{}x{}+{}+{}".format(width, height, bbox[0], bbox[1])]
|
||||
im = read_prog_img(cmd)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
return extract_version(EasyProcess([PROGRAM, "--version"]).call().stdout)
|
@ -0,0 +1,50 @@
|
||||
# import atexit
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
from pyscreenshot.util import py_minor
|
||||
|
||||
# https://python-mss.readthedocs.io/examples.html
|
||||
|
||||
|
||||
class MssError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
sct = None
|
||||
|
||||
# not working without xrandr extension
|
||||
# only bits_per_pixel == 32 is supported
|
||||
|
||||
|
||||
class MssWrapper(CBackend):
|
||||
name = "mss"
|
||||
|
||||
def grab(self, bbox=None):
|
||||
if py_minor() < 5:
|
||||
raise MssError()
|
||||
import mss # type: ignore
|
||||
|
||||
# atexit.register(sct.close())
|
||||
|
||||
global sct
|
||||
if not sct:
|
||||
sct = mss.mss()
|
||||
|
||||
# with self.mss.mss() as sct:
|
||||
if bbox:
|
||||
monitor = bbox
|
||||
else:
|
||||
monitor = sct.monitors[0]
|
||||
sct_img = sct.grab(monitor)
|
||||
|
||||
im = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
|
||||
# The same, but less efficient:
|
||||
# im = Image.frombytes('RGB', sct_img.size, sct_img.rgb)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
import mss
|
||||
|
||||
return mss.__version__
|
@ -0,0 +1,22 @@
|
||||
from PIL import __version__
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
from pyscreenshot.util import use_x_display
|
||||
|
||||
|
||||
class PilWrapper(CBackend):
|
||||
name = "pil"
|
||||
|
||||
def grab(self, bbox=None):
|
||||
from PIL import ImageGrab
|
||||
|
||||
# https://pillow.readthedocs.io/en/stable/reference/ImageGrab.html
|
||||
# On Linux, if xdisplay is None then gnome-screenshot will be used if it is installed.
|
||||
# To capture the default X11 display instead, pass xdisplay=""
|
||||
xdisplay = None
|
||||
if use_x_display():
|
||||
xdisplay = ""
|
||||
return ImageGrab.grab(bbox, xdisplay=xdisplay)
|
||||
|
||||
def backend_version(self):
|
||||
return __version__
|
@ -0,0 +1,54 @@
|
||||
import io
|
||||
import logging
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PySide2BugError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
app = None
|
||||
|
||||
|
||||
class PySide2GrabWindow(CBackend):
|
||||
name = "pyside2"
|
||||
|
||||
def grab_to_buffer(self, buff, file_type="png"):
|
||||
from PySide2 import QtCore, QtGui, QtWidgets # type: ignore
|
||||
|
||||
QApplication = QtWidgets.QApplication
|
||||
QBuffer = QtCore.QBuffer
|
||||
QIODevice = QtCore.QIODevice
|
||||
QScreen = QtGui.QScreen
|
||||
# QPixmap = self.PySide2.QtGui.QPixmap
|
||||
|
||||
global app
|
||||
if not app:
|
||||
app = QApplication([])
|
||||
qbuffer = QBuffer()
|
||||
qbuffer.open(QIODevice.ReadWrite)
|
||||
QScreen.grabWindow(
|
||||
QApplication.primaryScreen(), QApplication.desktop().winId()
|
||||
).save(qbuffer, file_type)
|
||||
# https://stackoverflow.com/questions/52291585/pyside2-typeerror-bytes-object-cannot-be-interpreted-as-an-integer
|
||||
buff.write(qbuffer.data().data())
|
||||
qbuffer.close()
|
||||
|
||||
def grab(self, bbox=None):
|
||||
strio = io.BytesIO()
|
||||
self.grab_to_buffer(strio)
|
||||
strio.seek(0)
|
||||
im = Image.open(strio)
|
||||
if bbox:
|
||||
im = im.crop(bbox)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
import PySide2
|
||||
|
||||
return PySide2.__version__
|
@ -0,0 +1,48 @@
|
||||
import io
|
||||
import logging
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# based on qt4 backend
|
||||
|
||||
app = None
|
||||
|
||||
|
||||
class PySideGrabWindow(CBackend):
|
||||
name = "pyside"
|
||||
|
||||
def grab_to_buffer(self, buff, file_type="png"):
|
||||
from PySide import QtCore, QtGui # type: ignore
|
||||
|
||||
QApplication = QtGui.QApplication
|
||||
QBuffer = QtCore.QBuffer
|
||||
QIODevice = QtCore.QIODevice
|
||||
QPixmap = QtGui.QPixmap
|
||||
|
||||
global app
|
||||
if not app:
|
||||
app = QApplication([])
|
||||
qbuffer = QBuffer()
|
||||
qbuffer.open(QIODevice.ReadWrite)
|
||||
QPixmap.grabWindow(QApplication.desktop().winId()).save(qbuffer, file_type)
|
||||
# https://stackoverflow.com/questions/52291585/pyside2-typeerror-bytes-object-cannot-be-interpreted-as-an-integer
|
||||
buff.write(qbuffer.data().data())
|
||||
qbuffer.close()
|
||||
|
||||
def grab(self, bbox=None):
|
||||
strio = io.BytesIO()
|
||||
self.grab_to_buffer(strio)
|
||||
strio.seek(0)
|
||||
im = Image.open(strio)
|
||||
if bbox:
|
||||
im = im.crop(bbox)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
import PySide
|
||||
|
||||
return PySide.__version__
|
@ -0,0 +1,49 @@
|
||||
import io
|
||||
import logging
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# based on:
|
||||
# http://stackoverflow.com/questions/69645/take-a-screenshot-via-a-python-script-linux
|
||||
|
||||
app = None
|
||||
|
||||
|
||||
class Qt4GrabWindow(CBackend):
|
||||
name = "pyqt"
|
||||
|
||||
def grab_to_buffer(self, buff, file_type="png"):
|
||||
from PyQt4 import Qt, QtGui # type: ignore
|
||||
|
||||
QApplication = QtGui.QApplication
|
||||
QBuffer = Qt.QBuffer
|
||||
QIODevice = Qt.QIODevice
|
||||
QPixmap = QtGui.QPixmap
|
||||
|
||||
global app
|
||||
if not app:
|
||||
app = QApplication([])
|
||||
qbuffer = QBuffer()
|
||||
qbuffer.open(QIODevice.ReadWrite)
|
||||
QPixmap.grabWindow(QApplication.desktop().winId()).save(qbuffer, file_type)
|
||||
buff.write(qbuffer.data())
|
||||
qbuffer.close()
|
||||
|
||||
def grab(self, bbox=None):
|
||||
strio = io.BytesIO()
|
||||
self.grab_to_buffer(strio)
|
||||
strio.seek(0)
|
||||
im = Image.open(strio)
|
||||
if bbox:
|
||||
im = im.crop(bbox)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
from PyQt4 import Qt
|
||||
|
||||
return Qt.PYQT_VERSION_STR
|
@ -0,0 +1,51 @@
|
||||
import io
|
||||
import logging
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# based on qt4 backend
|
||||
|
||||
app = None
|
||||
|
||||
|
||||
class Qt5GrabWindow(CBackend):
|
||||
name = "pyqt5"
|
||||
|
||||
# qt backends have conflict with each other in the same process.
|
||||
|
||||
def grab_to_buffer(self, buff, file_type="png"):
|
||||
from PyQt5 import Qt, QtGui, QtWidgets # type: ignore
|
||||
|
||||
QApplication = QtWidgets.QApplication
|
||||
QBuffer = Qt.QBuffer
|
||||
QIODevice = Qt.QIODevice
|
||||
QScreen = QtGui.QScreen
|
||||
|
||||
global app
|
||||
if not app:
|
||||
app = QApplication([])
|
||||
qbuffer = QBuffer()
|
||||
qbuffer.open(QIODevice.ReadWrite)
|
||||
QScreen.grabWindow(
|
||||
QApplication.primaryScreen(), QApplication.desktop().winId()
|
||||
).save(qbuffer, file_type)
|
||||
buff.write(qbuffer.data())
|
||||
qbuffer.close()
|
||||
|
||||
def grab(self, bbox=None):
|
||||
strio = io.BytesIO()
|
||||
self.grab_to_buffer(strio)
|
||||
strio.seek(0)
|
||||
im = Image.open(strio)
|
||||
if bbox:
|
||||
im = im.crop(bbox)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
from PyQt5 import Qt
|
||||
|
||||
return Qt.PYQT_VERSION_STR
|
@ -0,0 +1,26 @@
|
||||
import logging
|
||||
|
||||
from easyprocess import EasyProcess
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
from pyscreenshot.tempexport import read_prog_img
|
||||
from pyscreenshot.util import extract_version
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PROGRAM = "scrot"
|
||||
|
||||
|
||||
class ScrotWrapper(CBackend):
|
||||
name = "scrot"
|
||||
is_subprocess = True
|
||||
|
||||
def grab(self, bbox=None):
|
||||
im = read_prog_img([PROGRAM, "--silent"])
|
||||
if bbox:
|
||||
im = im.crop(bbox)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
return extract_version(EasyProcess([PROGRAM, "-version"]).call().stdout)
|
@ -0,0 +1,62 @@
|
||||
import logging
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
from pyscreenshot.util import platform_is_osx
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# based on:
|
||||
# http://stackoverflow.com/questions/69645/take-a-screenshot-via-a-python-script-linux
|
||||
|
||||
|
||||
class WxBackendError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
app = None
|
||||
|
||||
|
||||
class WxScreen(CBackend):
|
||||
name = "wx"
|
||||
# conflict with pygdk3
|
||||
# wx is never installed by default
|
||||
# pygdk3 is default on Gnome
|
||||
|
||||
def grab(self, bbox=None):
|
||||
if platform_is_osx():
|
||||
raise WxBackendError("osx not supported")
|
||||
import wx # type: ignore
|
||||
|
||||
global app
|
||||
if not app:
|
||||
app = wx.App()
|
||||
screen = wx.ScreenDC()
|
||||
size = screen.GetSize()
|
||||
if wx.__version__ >= "4":
|
||||
bmp = wx.Bitmap(size[0], size[1])
|
||||
else:
|
||||
bmp = wx.EmptyBitmap(size[0], size[1])
|
||||
mem = wx.MemoryDC(bmp)
|
||||
mem.Blit(0, 0, size[0], size[1], screen, 0, 0)
|
||||
del mem
|
||||
if hasattr(bmp, "ConvertToImage"):
|
||||
myWxImage = bmp.ConvertToImage()
|
||||
else:
|
||||
myWxImage = wx.ImageFromBitmap(bmp)
|
||||
im = Image.new("RGB", (myWxImage.GetWidth(), myWxImage.GetHeight()))
|
||||
if hasattr(Image, "frombytes"):
|
||||
# for Pillow
|
||||
im.frombytes(bytes(myWxImage.GetData()))
|
||||
else:
|
||||
# for PIL
|
||||
im.fromstring(myWxImage.GetData())
|
||||
if bbox:
|
||||
im = im.crop(bbox)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
import wx
|
||||
|
||||
return wx.__version__
|
@ -0,0 +1,49 @@
|
||||
import logging
|
||||
|
||||
from easyprocess import EasyProcess
|
||||
|
||||
from pyscreenshot.plugins.backend import CBackend
|
||||
from pyscreenshot.tempexport import RunProgError, read_func_img
|
||||
from pyscreenshot.util import extract_version
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# wikipedia: https://en.wikipedia.org/wiki/Xwd
|
||||
# xwd | xwdtopnm | pnmtopng > Screenshot.png
|
||||
# xwdtopnm is buggy: https://bugs.launchpad.net/ubuntu/+source/netpbm-free/+bug/1379480
|
||||
# solution : imagemagick convert
|
||||
# xwd -root -display :0 | convert xwd:- file.png
|
||||
# TODO: xwd sometimes grabs the wrong window so this backend will be not added now
|
||||
PROGRAM = "xwd"
|
||||
|
||||
|
||||
def read_xwd_img():
|
||||
def run_prog(fpng, bbox=None):
|
||||
fxwd = fpng + ".xwd"
|
||||
pxwd = EasyProcess([PROGRAM, "-root", "-out", fxwd])
|
||||
pxwd.call()
|
||||
if pxwd.return_code != 0:
|
||||
raise RunProgError(pxwd.stderr)
|
||||
|
||||
pconvert = EasyProcess(["convert", "xwd:" + fxwd, fpng])
|
||||
pconvert.call()
|
||||
if pconvert.return_code != 0:
|
||||
raise RunProgError(pconvert.stderr)
|
||||
|
||||
im = read_func_img(run_prog)
|
||||
return im
|
||||
|
||||
|
||||
class XwdWrapper(CBackend):
|
||||
name = "xwd"
|
||||
is_subprocess = True
|
||||
|
||||
def grab(self, bbox=None):
|
||||
im = read_xwd_img()
|
||||
if bbox:
|
||||
im = im.crop(bbox)
|
||||
return im
|
||||
|
||||
def backend_version(self):
|
||||
return extract_version(EasyProcess([PROGRAM, "-version"]).call().stdout)
|
28
venv/lib/python3.12/site-packages/pyscreenshot/tempexport.py
Normal file
28
venv/lib/python3.12/site-packages/pyscreenshot/tempexport.py
Normal file
@ -0,0 +1,28 @@
|
||||
import os.path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from easyprocess import EasyProcess
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class RunProgError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def read_func_img(func, bbox=None):
|
||||
with TemporaryDirectory(prefix="pyscreenshot") as tmpdirname:
|
||||
filename = os.path.join(tmpdirname, "screenshot.png")
|
||||
func(filename, bbox)
|
||||
im = Image.open(filename)
|
||||
return im
|
||||
|
||||
|
||||
def read_prog_img(cmd):
|
||||
def run_prog(filename, bbox=None):
|
||||
p = EasyProcess(cmd + [filename])
|
||||
p.call()
|
||||
if p.return_code != 0:
|
||||
raise RunProgError(p.stderr)
|
||||
|
||||
im = read_func_img(run_prog)
|
||||
return im
|
53
venv/lib/python3.12/site-packages/pyscreenshot/util.py
Normal file
53
venv/lib/python3.12/site-packages/pyscreenshot/util.py
Normal file
@ -0,0 +1,53 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from easyprocess import EasyProcess
|
||||
|
||||
|
||||
def py_minor():
|
||||
return sys.version_info[1]
|
||||
|
||||
|
||||
def platform_is_osx():
|
||||
return sys.platform == "darwin"
|
||||
|
||||
|
||||
def platform_is_win():
|
||||
return sys.platform == "win32"
|
||||
|
||||
|
||||
def platform_is_linux():
|
||||
return sys.platform.startswith("linux")
|
||||
|
||||
|
||||
def use_x_display():
|
||||
if platform_is_win():
|
||||
return False
|
||||
if platform_is_osx():
|
||||
return False
|
||||
DISPLAY = os.environ.get("DISPLAY")
|
||||
XDG_SESSION_TYPE = os.environ.get("XDG_SESSION_TYPE")
|
||||
# Xwayland can not be used for screenshot
|
||||
return DISPLAY and XDG_SESSION_TYPE != "wayland"
|
||||
|
||||
|
||||
def extract_version(txt):
|
||||
"""This function tries to extract the version from the help text of any
|
||||
program."""
|
||||
words = txt.replace(",", " ").split()
|
||||
version = None
|
||||
for x in reversed(words):
|
||||
if len(x) > 2:
|
||||
if x[0].lower() == "v":
|
||||
x = x[1:]
|
||||
if "." in x and x[0].isdigit():
|
||||
version = x
|
||||
break
|
||||
return version
|
||||
|
||||
|
||||
def run_mod_as_subproc(name, params=[]):
|
||||
python = sys.executable
|
||||
cmd = [python, "-m", name] + params
|
||||
p = EasyProcess(cmd).call()
|
||||
return p
|
Reference in New Issue
Block a user