Fri, 05 Apr 2019 19:19:43 +0200
Merged witzh branch 'screenshot' to start new development cycle.
Snapshot/SnapWidget.py | file | annotate | diff | comparison | revisions |
--- a/Globals/__init__.py Fri Apr 05 18:59:36 2019 +0200 +++ b/Globals/__init__.py Fri Apr 05 19:19:43 2019 +0200 @@ -92,9 +92,100 @@ if currDesktop: return currDesktop + currDesktop = os.environ.get("GNOME_DESKTOP_SESSION_ID", "") + if currDesktop: + return currDesktop + + currDesktop = os.environ.get("KDE_FULL_SESSION", "") + if currDesktop: + return currDesktop + + currDesktop = os.environ.get("DESKTOP_SESSION", "") + if currDesktop: + return currDesktop + return "" +def isKdeDesktop(): + """ + Function to check, if the current session is a KDE desktop (Linux only). + + @return flag indicating a KDE desktop + @rtype bool + """ + if not isLinuxPlatform(): + return False + + isKDE = False + + desktop = os.environ.get("XDG_CURRENT_DESKTOP", "").lower() or \ + os.environ.get("XDG_SESSION_DESKTOP", "").lower() or \ + os.environ.get("DESKTOP_SESSION", "").lower() + if desktop: + isKDE = "kde" in desktop or "plasma" in desktop + else: + isKDE = bool(os.environ.get("KDE_FULL_SESSION", "")) + + return isKDE + + +def isGnomeDesktop(): + """ + Function to check, if the current session is a Gnome desktop (Linux only). + + @return flag indicating a Gnome desktop + @rtype bool + """ + if not isLinuxPlatform(): + return False + + isGnome = False + + desktop = os.environ.get("XDG_CURRENT_DESKTOP", "").lower() or \ + os.environ.get("XDG_SESSION_DESKTOP", "").lower() or \ + os.environ.get("GDMSESSION", "").lower() + if desktop: + isGnome = "gnome" in desktop + else: + isGnome = bool(os.environ.get("GNOME_DESKTOP_SESSION_ID", "")) + + return isGnome + + +def sessionType(): + """ + Function to determine the name of the running session (Linux only). + + @return name of the desktop environment + @rtype str + """ + if not isLinuxPlatform(): + return "" + + sessionType = os.environ.get("XDG_SESSION_TYPE").lower() + if "x11" in sessionType: + return "X11" + elif "wayland" in sessionType: + return "Wayland" + + sessionType = os.environ.get("WAYLAND_DISPLAY", "").lower() + if "wayland" in sessionType: + return "Wayland" + + return "" + + +def isWaylandSession(): + """ + Function to check, if the current session is a wayland session. + + @return flag indicating a wayland session + @rtype bool + """ + return sessionType() == "Wayland" + + def checkBlacklistedVersions(): """ Module functions to check for blacklisted versions of the prerequisites.
--- a/Snapshot/SnapWidget.py Fri Apr 05 18:59:36 2019 +0200 +++ b/Snapshot/SnapWidget.py Fri Apr 05 19:19:43 2019 +0200 @@ -16,8 +16,8 @@ import os from PyQt5.QtCore import pyqtSlot, QFile, QFileInfo, QTimer, QPoint, \ - QMimeData, Qt, QEvent, QRegExp, QLocale, PYQT_VERSION_STR -from PyQt5.QtGui import QImageWriter, QPixmap, QCursor, QDrag, QKeySequence + QMimeData, Qt, QRegExp, QLocale, PYQT_VERSION_STR +from PyQt5.QtGui import QImageWriter, QPixmap, QDrag, QKeySequence from PyQt5.QtWidgets import QWidget, QApplication, QShortcut from E5Gui import E5FileDialog, E5MessageBox @@ -27,19 +27,14 @@ import UI.PixmapCache import Preferences import Globals -from Globals import qVersionTuple + +from .SnapshotModes import SnapshotModes class SnapWidget(QWidget, Ui_SnapWidget): """ Class implementing the snapshot widget. """ - ModeFullscreen = 0 - ModeScreen = 1 - ModeRectangle = 2 - ModeFreehand = 3 - ModeEllipse = 4 - def __init__(self, parent=None): """ Constructor @@ -55,31 +50,49 @@ self.copyPreviewButton.setIcon(UI.PixmapCache.getIcon("editCopy.png")) self.setWindowIcon(UI.PixmapCache.getIcon("ericSnap.png")) - self.modeCombo.addItem(self.tr("Fullscreen"), - SnapWidget.ModeFullscreen) - if qVersionTuple() >= (5, 10, 0): - if len(QApplication.screens()) > 1: - self.modeCombo.addItem(self.tr("Select Screen"), - SnapWidget.ModeScreen) + if Globals.isWaylandSession(): + from .SnapshotWaylandGrabber import SnapshotWaylandGrabber + self.__grabber = SnapshotWaylandGrabber(self) else: - if QApplication.desktop().screenCount() > 1: - self.modeCombo.addItem(self.tr("Select Screen"), - SnapWidget.ModeScreen) - self.modeCombo.addItem(self.tr("Rectangular Selection"), - SnapWidget.ModeRectangle) - self.modeCombo.addItem(self.tr("Elliptical Selection"), - SnapWidget.ModeEllipse) - self.modeCombo.addItem(self.tr("Freehand Selection"), - SnapWidget.ModeFreehand) - self.__mode = int(Preferences.Prefs.settings.value("Snapshot/Mode", 0)) - index = self.modeCombo.findData(self.__mode) + from .SnapshotDefaultGrabber import SnapshotDefaultGrabber + self.__grabber = SnapshotDefaultGrabber(self) + self.decorationsCheckBox.hide() + self.mouseCursorCheckBox.hide() + self.__grabber.grabbed.connect(self.__captured) + supportedModes = self.__grabber.supportedModes() + + if SnapshotModes.Fullscreen in supportedModes: + self.modeCombo.addItem(self.tr("Fullscreen"), + SnapshotModes.Fullscreen) + if SnapshotModes.SelectedScreen in supportedModes: + if Globals.qVersionTuple() >= (5, 10, 0): + if len(QApplication.screens()) > 1: + self.modeCombo.addItem(self.tr("Select Screen"), + SnapshotModes.SelectedScreen) + else: + if QApplication.desktop().screenCount() > 1: + self.modeCombo.addItem(self.tr("Select Screen"), + SnapshotModes.SelectedScreen) + if SnapshotModes.SelectedWindow in supportedModes: + self.modeCombo.addItem(self.tr("Select Window"), + SnapshotModes.SelectedWindow) + if SnapshotModes.Rectangle in supportedModes: + self.modeCombo.addItem(self.tr("Rectangular Selection"), + SnapshotModes.Rectangle) + if SnapshotModes.Ellipse in supportedModes: + self.modeCombo.addItem(self.tr("Elliptical Selection"), + SnapshotModes.Ellipse) + if SnapshotModes.Freehand in supportedModes: + self.modeCombo.addItem(self.tr("Freehand Selection"), + SnapshotModes.Freehand) + mode = int(Preferences.Prefs.settings.value("Snapshot/Mode", 0)) + index = self.modeCombo.findData(SnapshotModes(mode)) if index == -1: index = 0 self.modeCombo.setCurrentIndex(index) - self.__delay = int( - Preferences.Prefs.settings.value("Snapshot/Delay", 0)) - self.delaySpin.setValue(self.__delay) + delay = int(Preferences.Prefs.settings.value("Snapshot/Delay", 0)) + self.delaySpin.setValue(delay) if PYQT_VERSION_STR >= "5.0.0": from PyQt5.QtCore import QStandardPaths @@ -94,25 +107,16 @@ os.path.join(picturesLocation, self.tr("snapshot") + "1.png")) - self.__grabber = None self.__snapshot = QPixmap() self.__savedPosition = QPoint() self.__modified = False self.__locale = QLocale() - self.__grabberWidget = QWidget(None, Qt.X11BypassWindowManagerHint) - self.__grabberWidget.move(-10000, -10000) - self.__grabberWidget.installEventFilter(self) - self.__initFileFilters() - self.__initShortcuts() self.preview.startDrag.connect(self.__dragSnapshot) - from .SnapshotTimer import SnapshotTimer - self.__grabTimer = SnapshotTimer() - self.__grabTimer.timeout.connect(self.__grabTimerTimeout) self.__updateTimer = QTimer() self.__updateTimer.setSingleShot(True) self.__updateTimer.timeout.connect(self.__updatePreview) @@ -301,113 +305,15 @@ """ Private slot to take a snapshot. """ - self.__mode = self.modeCombo.itemData(self.modeCombo.currentIndex()) - self.__delay = self.delaySpin.value() - self.__savedPosition = self.pos() self.hide() - if self.__delay: - self.__grabTimer.start(self.__delay) - else: - QTimer.singleShot(200, self.__startUndelayedGrab) - - def __grabTimerTimeout(self): - """ - Private slot to perform a delayed grab operation. - """ - if self.__mode == SnapWidget.ModeRectangle: - self.__grabRectangle() - elif self.__mode == SnapWidget.ModeEllipse: - self.__grabEllipse() - elif self.__mode == SnapWidget.ModeFreehand: - self.__grabFreehand() - else: - self.__performGrab() - - def __startUndelayedGrab(self): - """ - Private slot to perform an undelayed grab operation. - """ - if self.__mode == SnapWidget.ModeRectangle: - self.__grabRectangle() - elif self.__mode == SnapWidget.ModeEllipse: - self.__grabEllipse() - elif self.__mode == SnapWidget.ModeFreehand: - self.__grabFreehand() - else: - if Globals.isMacPlatform(): - self.__performGrab() - else: - self.__grabberWidget.show() - self.__grabberWidget.grabMouse(Qt.CrossCursor) - - def __grabRectangle(self): - """ - Private method to grab a rectangular screen region. - """ - from .SnapshotRegionGrabber import SnapshotRegionGrabber - self.__grabber = SnapshotRegionGrabber( - mode=SnapshotRegionGrabber.Rectangle) - self.__grabber.grabbed.connect(self.__captured) - - def __grabEllipse(self): - """ - Private method to grab an elliptical screen region. - """ - from .SnapshotRegionGrabber import SnapshotRegionGrabber - self.__grabber = SnapshotRegionGrabber( - mode=SnapshotRegionGrabber.Ellipse) - self.__grabber.grabbed.connect(self.__captured) - - def __grabFreehand(self): - """ - Private method to grab a non-rectangular screen region. - """ - from .SnapshotFreehandGrabber import SnapshotFreehandGrabber - self.__grabber = SnapshotFreehandGrabber() - self.__grabber.grabbed.connect(self.__captured) - - def __performGrab(self): - """ - Private method to perform a screen grab other than a selected region. - """ - self.__grabberWidget.releaseMouse() - self.__grabberWidget.hide() - self.__grabTimer.stop() - - if self.__mode == SnapWidget.ModeFullscreen: - desktop = QApplication.desktop() - if qVersionTuple() >= (5, 0, 0): - self.__snapshot = QApplication.screens()[0].grabWindow( - desktop.winId(), desktop.x(), desktop.y(), - desktop.width(), desktop.height()) - else: - self.__snapshot = QPixmap.grabWindow( - desktop.winId(), desktop.x(), desktop.y(), - desktop.width(), desktop.height()) - elif self.__mode == SnapWidget.ModeScreen: - desktop = QApplication.desktop() - if qVersionTuple() >= (5, 10, 0): - screen = QApplication.screenAt(QCursor.pos()) - geom = screen.geometry() - else: - screenId = desktop.screenNumber(QCursor.pos()) - geom = desktop.screenGeometry(screenId) - x = geom.x() - y = geom.y() - if qVersionTuple() >= (5, 0, 0): - self.__snapshot = QApplication.screens()[0].grabWindow( - desktop.winId(), x, y, geom.width(), geom.height()) - else: - self.__snapshot = QPixmap.grabWindow( - desktop.winId(), x, y, geom.width(), geom.height()) - else: - self.__snapshot = QPixmap() - - self.__redisplay() - self.__modified = True - self.__updateCaption() + self.__grabber.grab( + self.modeCombo.itemData(self.modeCombo.currentIndex()), + self.delaySpin.value(), + self.mouseCursorCheckBox.isChecked(), + self.decorationsCheckBox.isChecked(), + ) def __redisplay(self): """ @@ -445,14 +351,10 @@ @param pixmap pixmap of the snapshot (QPixmap) """ - self.__grabber.close() self.__snapshot = QPixmap(pixmap) - self.__grabber.grabbed.disconnect(self.__captured) - self.__grabber = None - self.__redisplay() - self.__modified = True + self.__modified = not pixmap.isNull() self.__updateCaption() def __updatePreview(self): @@ -486,23 +388,6 @@ drag.setPixmap(self.preview.pixmap()) drag.exec_(Qt.CopyAction) - def eventFilter(self, obj, evt): - """ - Public method to handle event for other objects. - - @param obj reference to the object (QObject) - @param evt reference to the event (QEvent) - @return flag indicating that the event should be filtered out (boolean) - """ - if obj == self.__grabberWidget and \ - evt.type() == QEvent.MouseButtonPress: - if QWidget.mouseGrabber() != self.__grabberWidget: - return False - if evt.button() == Qt.LeftButton: - self.__performGrab() - - return False - def closeEvent(self, evt): """ Protected method handling the close event. @@ -527,9 +412,11 @@ Preferences.Prefs.settings.setValue( "Snapshot/Delay", self.delaySpin.value()) - Preferences.Prefs.settings.setValue( - "Snapshot/Mode", - self.modeCombo.itemData(self.modeCombo.currentIndex())) + modeData = self.modeCombo.itemData(self.modeCombo.currentIndex()) + if modeData is not None: + Preferences.Prefs.settings.setValue( + "Snapshot/Mode", + modeData.value) Preferences.Prefs.settings.setValue( "Snapshot/Filename", self.__filename) Preferences.Prefs.settings.sync() @@ -543,3 +430,19 @@ self.tr("eric6 Snapshot"))) self.setWindowModified(self.__modified) self.pathNameEdit.setText(os.path.dirname(self.__filename)) + + @pyqtSlot(int) + def on_modeCombo_currentIndexChanged(self, index): + """ + Private slot handling the selection of a screenshot mode. + + @param index index of the selection + @type int + """ + isWindowMode = False + if index >= 0: + mode = self.modeCombo.itemData(index) + isWindowMode = (mode == SnapshotModes.SelectedWindow) + + self.decorationsCheckBox.setEnabled(isWindowMode) + self.decorationsCheckBox.setChecked(isWindowMode)
--- a/Snapshot/SnapWidget.ui Fri Apr 05 18:59:36 2019 +0200 +++ b/Snapshot/SnapWidget.ui Fri Apr 05 19:19:43 2019 +0200 @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>500</width> - <height>400</height> + <width>550</width> + <height>450</height> </rect> </property> <property name="minimumSize"> @@ -47,7 +47,7 @@ </widget> </item> <item> - <layout class="QFormLayout" name="formLayout"> + <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> <widget class="QLabel" name="label"> <property name="text"> @@ -108,6 +108,30 @@ </item> </layout> </item> + <item row="2" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QCheckBox" name="decorationsCheckBox"> + <property name="toolTip"> + <string>Select to include the window decorations in the screenshot</string> + </property> + <property name="text"> + <string>include Decorations</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="mouseCursorCheckBox"> + <property name="toolTip"> + <string>Select to include the mouse cursor in the screenshot</string> + </property> + <property name="text"> + <string>include Mouse Cursor</string> + </property> + </widget> + </item> + </layout> + </item> </layout> </item> <item> @@ -236,11 +260,13 @@ <tabstops> <tabstop>modeCombo</tabstop> <tabstop>delaySpin</tabstop> + <tabstop>decorationsCheckBox</tabstop> + <tabstop>mouseCursorCheckBox</tabstop> <tabstop>takeButton</tabstop> <tabstop>pathNameEdit</tabstop> <tabstop>saveButton</tabstop> + <tabstop>copyPreviewButton</tabstop> <tabstop>copyButton</tabstop> - <tabstop>copyPreviewButton</tabstop> </tabstops> <resources/> <connections/>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Snapshot/SnapshotDefaultGrabber.py Fri Apr 05 19:19:43 2019 +0200 @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a grabber object for non-Wayland desktops. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import pyqtSignal, Qt, QObject, QTimer, QEvent +from PyQt5.QtGui import QPixmap, QCursor +from PyQt5.QtWidgets import QWidget, QApplication + +from .SnapshotModes import SnapshotModes + +import Globals + + +class SnapshotDefaultGrabber(QObject): + """ + Class implementing a grabber object for non-Wayland desktops. + + @signal grabbed(QPixmap) emitted after the grab operation is finished + """ + grabbed = pyqtSignal(QPixmap) + + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent object + @type QObject + """ + super(SnapshotDefaultGrabber, self).__init__(parent) + + self.__grabber = None + self.__grabberWidget = QWidget(None, Qt.X11BypassWindowManagerHint) + self.__grabberWidget.move(-10000, -10000) + self.__grabberWidget.installEventFilter(self) + + from .SnapshotTimer import SnapshotTimer + self.__grabTimer = SnapshotTimer() + self.__grabTimer.timeout.connect(self.__grabTimerTimeout) + + def supportedModes(self): + """ + Public method to get the supported screenshot modes. + + @return tuple of supported screenshot modes + @rtype tuple of SnapshotModes + """ + return ( + SnapshotModes.Fullscreen, + SnapshotModes.SelectedScreen, + SnapshotModes.Rectangle, + SnapshotModes.Freehand, + SnapshotModes.Ellipse, + ) + + def grab(self, mode, delay=0, captureCursor=False, + captureDecorations=False): + """ + Public method to perform a grab operation potentially after a delay. + + @param mode screenshot mode + @type ScreenshotModes + @param delay delay in seconds + @type int + @param captureCursor flag indicating to include the mouse cursor + (not used) + @type bool + @param captureDecorations flag indicating to include the window + decorations (not used) + @type bool + """ + self.__mode = mode + if delay: + self.__grabTimer.start(delay) + else: + QTimer.singleShot(200, self.__startUndelayedGrab) + + def __grabTimerTimeout(self): + """ + Private slot to perform a delayed grab operation. + """ + if self.__mode == SnapshotModes.Rectangle: + self.__grabRectangle() + elif self.__mode == SnapshotModes.Ellipse: + self.__grabEllipse() + elif self.__mode == SnapshotModes.Freehand: + self.__grabFreehand() + else: + self.__performGrab(self.__mode) + + def __startUndelayedGrab(self): + """ + Private slot to perform an undelayed grab operation. + """ + if self.__mode == SnapshotModes.Rectangle: + self.__grabRectangle() + elif self.__mode == SnapshotModes.Ellipse: + self.__grabEllipse() + elif self.__mode == SnapshotModes.Freehand: + self.__grabFreehand() + else: + if Globals.isMacPlatform(): + self.__performGrab(self.__mode) + else: + self.__grabberWidget.show() + self.__grabberWidget.grabMouse(Qt.CrossCursor) + + def __grabRectangle(self): + """ + Private method to grab a rectangular screen region. + """ + from .SnapshotRegionGrabber import SnapshotRegionGrabber + self.__grabber = SnapshotRegionGrabber( + mode=SnapshotRegionGrabber.Rectangle) + self.__grabber.grabbed.connect(self.__captured) + + def __grabEllipse(self): + """ + Private method to grab an elliptical screen region. + """ + from .SnapshotRegionGrabber import SnapshotRegionGrabber + self.__grabber = SnapshotRegionGrabber( + mode=SnapshotRegionGrabber.Ellipse) + self.__grabber.grabbed.connect(self.__captured) + + def __grabFreehand(self): + """ + Private method to grab a non-rectangular screen region. + """ + from .SnapshotFreehandGrabber import SnapshotFreehandGrabber + self.__grabber = SnapshotFreehandGrabber() + self.__grabber.grabbed.connect(self.__captured) + + def __performGrab(self, mode): + """ + Private method to perform a screen grab other than a selected region. + + @param mode screenshot mode + @type SnapshotModes + """ + self.__grabberWidget.releaseMouse() + self.__grabberWidget.hide() + self.__grabTimer.stop() + + if mode == SnapshotModes.Fullscreen: + desktop = QApplication.desktop() + if Globals.qVersionTuple() >= (5, 0, 0): + snapshot = QApplication.screens()[0].grabWindow( + desktop.winId(), desktop.x(), desktop.y(), + desktop.width(), desktop.height()) + else: + snapshot = QPixmap.grabWindow( + desktop.winId(), desktop.x(), desktop.y(), + desktop.width(), desktop.height()) + elif mode == SnapshotModes.SelectedScreen: + desktop = QApplication.desktop() + if Globals.qVersionTuple() >= (5, 10, 0): + screen = QApplication.screenAt(QCursor.pos()) + geom = screen.geometry() + else: + screenId = desktop.screenNumber(QCursor.pos()) + geom = desktop.screenGeometry(screenId) + x = geom.x() + y = geom.y() + if Globals.qVersionTuple() >= (5, 0, 0): + snapshot = QApplication.screens()[0].grabWindow( + desktop.winId(), x, y, geom.width(), geom.height()) + else: + snapshot = QPixmap.grabWindow( + desktop.winId(), x, y, geom.width(), geom.height()) + else: + snapshot = QPixmap() + + self.grabbed.emit(snapshot) + + def __captured(self, pixmap): + """ + Private slot to show a preview of the snapshot. + + @param pixmap pixmap of the snapshot (QPixmap) + """ + self.__grabber.close() + snapshot = QPixmap(pixmap) + + self.__grabber.grabbed.disconnect(self.__captured) + self.__grabber = None + + self.grabbed.emit(snapshot) + + def eventFilter(self, obj, evt): + """ + Public method to handle event for other objects. + + @param obj reference to the object (QObject) + @param evt reference to the event (QEvent) + @return flag indicating that the event should be filtered out (boolean) + """ + if obj == self.__grabberWidget and \ + evt.type() == QEvent.MouseButtonPress: + if QWidget.mouseGrabber() != self.__grabberWidget: + return False + if evt.button() == Qt.LeftButton: + self.__performGrab(self.__mode) + + return False
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Snapshot/SnapshotModes.py Fri Apr 05 19:19:43 2019 +0200 @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the snapshot mode enumeration. +""" + +from __future__ import unicode_literals + +try: + from enum import Enum +except ImportError: + from ThirdParty.enum import Enum + + +class SnapshotModes(Enum): + """ + Class implementing the snapshot modes. + """ + Fullscreen = 0 + SelectedScreen = 1 + Rectangle = 2 + Freehand = 3 + Ellipse = 4 + SelectedWindow = 5
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Snapshot/SnapshotWaylandGrabber.py Fri Apr 05 19:19:43 2019 +0200 @@ -0,0 +1,399 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a grabber object for non-Wayland desktops. +""" + +from __future__ import unicode_literals + +import os +import uuid + +from PyQt5.QtCore import pyqtSignal, QObject, QTimer +from PyQt5.QtGui import QPixmap, QCursor +from PyQt5.QtWidgets import QApplication + +try: + from PyQt5.QtDBus import QDBusInterface, QDBusMessage + DBusAvailable = True +except ImportError: + DBusAvailable = False + +from E5Gui import E5MessageBox + +from .SnapshotModes import SnapshotModes + +import Globals + + +class SnapshotWaylandGrabber(QObject): + """ + Class implementing a grabber object for non-Wayland desktops. + + @signal grabbed(QPixmap) emitted after the grab operation is finished + """ + grabbed = pyqtSignal(QPixmap) + + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent object + @type QObject + """ + super(SnapshotWaylandGrabber, self).__init__(parent) + + from .SnapshotTimer import SnapshotTimer + self.__grabTimer = SnapshotTimer() + self.__grabTimer.timeout.connect(self.__performGrab) + + def supportedModes(self): + """ + Public method to get the supported screenshot modes. + + @return tuple of supported screenshot modes + @rtype tuple of SnapshotModes + """ + if DBusAvailable and Globals.isKdeDesktop(): + modes = ( + SnapshotModes.Fullscreen, + SnapshotModes.SelectedScreen, + SnapshotModes.SelectedWindow, + ) + elif DBusAvailable and Globals.isGnomeDesktop(): + modes = ( + SnapshotModes.Fullscreen, + SnapshotModes.SelectedScreen, + SnapshotModes.SelectedWindow, + SnapshotModes.Rectangle, + ) + else: + modes = tuple() + + return modes + + def grab(self, mode, delay=0, captureCursor=False, + captureDecorations=False): + """ + Public method to perform a grab operation potentially after a delay. + + @param mode screenshot mode + @type ScreenshotModes + @param delay delay in seconds + @type int + @param captureCursor flag indicating to include the mouse cursor + @type bool + @param captureDecorations flag indicating to include the window + decorations (only used for mode SnapshotModes.SelectedWindow) + @type bool + """ + if not DBusAvailable: + # just to play it safe + self.grabbed.emit(QPixmap()) + return + + self.__mode = mode + self.__captureCursor = captureCursor + self.__captureDecorations = captureDecorations + if delay: + self.__grabTimer.start(delay) + else: + QTimer.singleShot(200, self.__performGrab) + + def __performGrab(self): + """ + Private method to perform the grab operations. + + @exception RuntimeError raised to indicate an unsupported grab mode + """ + if self.__mode == SnapshotModes.Fullscreen: + self.__grabFullscreen() + elif self.__mode == SnapshotModes.SelectedScreen: + self.__grabSelectedScreen() + elif self.__mode == SnapshotModes.SelectedWindow: + self.__grabSelectedWindow() + elif self.__mode == SnapshotModes.Rectangle: + self.__grabRectangle() + else: + raise RuntimeError("unsupported grab mode given") + + def __grabFullscreen(self): + """ + Private method to grab the complete desktop. + """ + snapshot = QPixmap() + + if Globals.isKdeDesktop(): + interface = QDBusInterface( + "org.kde.KWin", + "/Screenshot", + "org.kde.kwin.Screenshot" + ) + reply = interface.call( + "screenshotFullscreen", + self.__captureCursor + ) + if self.__checkReply(reply, 1): + filename = reply.arguments()[0] + if filename: + snapshot = QPixmap(filename) + try: + os.remove(filename) + except OSError: + # just ignore it + pass + elif Globals.isGnomeDesktop(): + path = self.__temporaryFilename() + interface = QDBusInterface( + "org.gnome.Shell", + "/org/gnome/Shell/Screenshot", + "org.gnome.Shell.Screenshot" + ) + reply = interface.call( + "Screenshot", + self.__captureCursor, + False, + path + ) + if self.__checkReply(reply, 2): + filename = reply.arguments()[1] + if filename: + snapshot = QPixmap(filename) + try: + os.remove(filename) + except OSError: + # just ignore it + pass + + self.grabbed.emit(snapshot) + + def __grabSelectedScreen(self): + """ + Private method to grab a selected screen. + """ + snapshot = QPixmap() + + if Globals.isKdeDesktop(): + # Step 1: get the screen number of screen containing the cursor + if Globals.qVersionTuple() >= (5, 10, 0): + screen = QApplication.screenAt(QCursor.pos()) + try: + screenId = QApplication.screens().index(screen) + except ValueError: + # default to screen 0 + screenId = 0 + else: + desktop = QApplication.desktop() + screenId = desktop.screenNumber(QCursor.pos()) + + # Step 2: grab the screen + interface = QDBusInterface( + "org.kde.KWin", + "/Screenshot", + "org.kde.kwin.Screenshot" + ) + reply = interface.call( + "screenshotScreen", + screenId, + self.__captureCursor + ) + if self.__checkReply(reply, 1): + filename = reply.arguments()[0] + if filename: + snapshot = QPixmap(filename) + try: + os.remove(filename) + except OSError: + # just ignore it + pass + elif Globals.isGnomeDesktop(): + # Step 1: grab entire desktop + path = self.__temporaryFilename() + interface = QDBusInterface( + "org.gnome.Shell", + "/org/gnome/Shell/Screenshot", + "org.gnome.Shell.Screenshot" + ) + reply = interface.call( + "ScreenshotWindow", + self.__captureDecorations, + self.__captureCursor, + False, + path + ) + if self.__checkReply(reply, 2): + filename = reply.arguments()[1] + if filename: + snapshot = QPixmap(filename) + try: + os.remove(filename) + except OSError: + # just ignore it + pass + + # Step 2: extract the area of the screen containing + # the cursor + if not snapshot.isNull(): + if Globals.qVersionTuple() >= (5, 10, 0): + screen = QApplication.screenAt(QCursor.pos()) + geom = screen.geometry() + else: + desktop = QApplication.desktop() + screenId = desktop.screenNumber(QCursor.pos()) + geom = desktop.screenGeometry(screenId) + snapshot = snapshot.copy(geom) + + self.grabbed.emit(snapshot) + + def __grabSelectedWindow(self): + """ + Private method to grab a selected window. + """ + snapshot = QPixmap() + + if Globals.isKdeDesktop(): + mask = 0 + if self.__captureDecorations: + mask |= 1 + if self.__captureCursor: + mask |= 2 + interface = QDBusInterface( + "org.kde.KWin", + "/Screenshot", + "org.kde.kwin.Screenshot" + ) + reply = interface.call( + "interactive", + mask + ) + if self.__checkReply(reply, 1): + filename = reply.arguments()[0] + if filename: + snapshot = QPixmap(filename) + try: + os.remove(filename) + except OSError: + # just ignore it + pass + elif Globals.isGnomeDesktop(): + path = self.__temporaryFilename() + interface = QDBusInterface( + "org.gnome.Shell", + "/org/gnome/Shell/Screenshot", + "org.gnome.Shell.Screenshot" + ) + reply = interface.call( + "ScreenshotWindow", + self.__captureDecorations, + self.__captureCursor, + False, + path + ) + if self.__checkReply(reply, 2): + filename = reply.arguments()[1] + if filename: + snapshot = QPixmap(filename) + try: + os.remove(filename) + except OSError: + # just ignore it + pass + + self.grabbed.emit(snapshot) + + def __grabRectangle(self): + """ + Private method to grab a rectangular desktop area. + """ + snapshot = QPixmap() + + if Globals.isGnomeDesktop(): + # Step 1: let the user select the area + interface = QDBusInterface( + "org.gnome.Shell", + "/org/gnome/Shell/Screenshot", + "org.gnome.Shell.Screenshot" + ) + reply = interface.call("SelectArea") + if self.__checkReply(reply, 4): + x, y, width, height = reply.arguments()[:4] + + # Step 2: grab the selected area + path = self.__temporaryFilename() + reply = interface.call( + "ScreenshotArea", + x, y, width, height, + False, + path + ) + if self.__checkReply(reply, 2): + filename = reply.arguments()[1] + if filename: + snapshot = QPixmap(filename) + try: + os.remove(filename) + except OSError: + # just ignore it + pass + + self.grabbed.emit(snapshot) + + def __checkReply(self, reply, argumentsCount): + """ + Private method to check, if a reply is valid. + + @param reply reference to the reply message + @type QDBusMessage + @param argumentsCount number of expected arguments + @type int + @return flag indicating validity + @rtype bool + """ + if reply.type() == QDBusMessage.ReplyMessage: + if len(reply.arguments()) == argumentsCount: + return True + + E5MessageBox.warning( + None, + self.tr("Screenshot Error"), + self.tr("<p>Received an unexpected number of reply arguments." + " Expected {0} but got {1}</p>").format( + argumentsCount, + len(reply.arguments()), + )) + + elif reply.type() == QDBusMessage.ErrorMessage: + E5MessageBox.warning( + None, + self.tr("Screenshot Error"), + self.tr("<p>Received error <b>{0}</b> from DBus while" + " performing screenshot.</p><p>{1}</p>").format( + reply.errorName(), + reply.errorMessage(), + )) + + elif reply.type() == QDBusMessage.InvalidMessage: + E5MessageBox.warning( + None, + self.tr("Screenshot Error"), + self.tr("Received an invalid reply.")) + + else: + E5MessageBox.warning( + None, + self.tr("Screenshot Error"), + self.tr("Received an unexpected reply.")) + + return False + + def __temporaryFilename(self): + """ + Private method to generate a temporary filename. + + @return path name for a unique, temporary file + @rtype str + """ + return "/tmp/eric6-snap-{0}.png".format(uuid.uuid4().hex)
--- a/changelog Fri Apr 05 18:59:36 2019 +0200 +++ b/changelog Fri Apr 05 19:19:43 2019 +0200 @@ -1,5 +1,10 @@ Change Log ---------- +Version 19.05: +- bug fixes +- Snapshot Tool + -- added code to support screenshots on Wayland desktops (KDE and Gnome) + Version 19.04: - bug fixes - Code Style Checker
--- a/eric6.e4p Fri Apr 05 18:59:36 2019 +0200 +++ b/eric6.e4p Fri Apr 05 19:19:43 2019 +0200 @@ -1037,10 +1037,13 @@ <Source>QScintilla/ZoomDialog.py</Source> <Source>QScintilla/__init__.py</Source> <Source>Snapshot/SnapWidget.py</Source> + <Source>Snapshot/SnapshotDefaultGrabber.py</Source> <Source>Snapshot/SnapshotFreehandGrabber.py</Source> + <Source>Snapshot/SnapshotModes.py</Source> <Source>Snapshot/SnapshotPreview.py</Source> <Source>Snapshot/SnapshotRegionGrabber.py</Source> <Source>Snapshot/SnapshotTimer.py</Source> + <Source>Snapshot/SnapshotWaylandGrabber.py</Source> <Source>Snapshot/__init__.py</Source> <Source>SqlBrowser/SqlBrowser.py</Source> <Source>SqlBrowser/SqlBrowserWidget.py</Source> @@ -2259,14 +2262,14 @@ </Resources> <Others> <Other>.hgignore</Other> - <Other>APIs/Python/zope-2.10.7.api</Other> - <Other>APIs/Python/zope-2.11.2.api</Other> - <Other>APIs/Python/zope-3.3.1.api</Other> <Other>APIs/Python3/PyQt4.bas</Other> <Other>APIs/Python3/PyQt5.bas</Other> <Other>APIs/Python3/QScintilla2.bas</Other> <Other>APIs/Python3/eric6.api</Other> <Other>APIs/Python3/eric6.bas</Other> + <Other>APIs/Python/zope-2.10.7.api</Other> + <Other>APIs/Python/zope-2.11.2.api</Other> + <Other>APIs/Python/zope-3.3.1.api</Other> <Other>APIs/QSS/qss.api</Other> <Other>APIs/Ruby/Ruby-1.8.7.api</Other> <Other>APIs/Ruby/Ruby-1.8.7.bas</Other>