This commit is contained in:
2024-11-29 18:15:30 +00:00
parent 40aade2d8e
commit bc9415586e
5298 changed files with 1938676 additions and 80 deletions

View File

@ -0,0 +1,5 @@
UNKNOWN_VERSION = "?.?"
class CBackend(object):
is_subprocess = False

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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("-", " "))

View File

@ -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

View File

@ -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("-", " "))

View File

@ -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]

View File

@ -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

View File

@ -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__

View File

@ -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

View File

@ -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)

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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__

View File

@ -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)