asd
This commit is contained in:
@ -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)
|
Reference in New Issue
Block a user