diff --git a/effects/screenshot/screenshot.cpp b/effects/screenshot/screenshot.cpp index 38fd5f5da..9eeb21635 100644 --- a/effects/screenshot/screenshot.cpp +++ b/effects/screenshot/screenshot.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include @@ -30,6 +32,8 @@ #include #include "../service_utils.h" +Q_DECLARE_METATYPE(QStringList) + class ComparableQPoint : public QPoint { public: @@ -66,6 +70,8 @@ const static QString s_errorInvalidAreaMsg = QStringLiteral("Invalid area reques const static QString s_errorInvalidScreen = QStringLiteral("org.kde.kwin.Screenshot.Error.InvalidScreen"); const static QString s_errorInvalidScreenMsg = QStringLiteral("Invalid screen requested"); const static QString s_dbusInterfaceName = QStringLiteral("org.kde.kwin.Screenshot"); +const static QString s_errorScreenMissing = QStringLiteral("org.kde.kwin.Screenshot.Error.ScreenMissing"); +const static QString s_errorScreenMissingMsg = QStringLiteral("Screen not found"); bool ScreenShotEffect::supported() { @@ -355,39 +361,43 @@ void ScreenShotEffect::postPaintScreen() m_multipleOutputsRendered = m_multipleOutputsRendered.united(intersection); if (m_multipleOutputsRendered.boundingRect() == m_scheduledGeometry) { - // Recompute coordinates - if (m_nativeSize) { - computeCoordinatesAfterScaling(); + if (m_orderImg.isEmpty()) { + // Recompute coordinates + if (m_nativeSize) { + computeCoordinatesAfterScaling(); + } + + // find the output image size + int width = 0; + int height = 0; + QMap::const_iterator i; + for (i = m_cacheOutputsImages.constBegin(); i != m_cacheOutputsImages.constEnd(); ++i) { + const auto pos = i.key(); + const auto img = i.value(); + + width = qMax(width, pos.x() + img.width()); + height = qMax(height, pos.y() + img.height()); + } + + QImage multipleOutputsImage = QImage(width, height, QImage::Format_ARGB32); + + QPainter p; + p.begin(&multipleOutputsImage); + + // reassemble images together + for (i = m_cacheOutputsImages.constBegin(); i != m_cacheOutputsImages.constEnd(); ++i) { + auto pos = i.key(); + auto img = i.value(); + // disable dpr rendering, we already took care of this + img.setDevicePixelRatio(1.0); + p.drawImage(pos, img); + } + p.end(); + + sendReplyImage(multipleOutputsImage); + } else { + sendReplyImages(); } - - // find the output image size - int width = 0; - int height = 0; - QMap::const_iterator i; - for (i = m_cacheOutputsImages.constBegin(); i != m_cacheOutputsImages.constEnd(); ++i) { - const auto pos = i.key(); - const auto img = i.value(); - - width = qMax(width, pos.x() + img.width()); - height = qMax(height, pos.y() + img.height()); - } - - QImage multipleOutputsImage = QImage(width, height, QImage::Format_ARGB32); - - QPainter p; - p.begin(&multipleOutputsImage); - - // reassemble images together - for (i = m_cacheOutputsImages.constBegin(); i != m_cacheOutputsImages.constEnd(); ++i) { - auto pos = i.key(); - auto img = i.value(); - // disable dpr rendering, we already took care of this - img.setDevicePixelRatio(1.0); - p.drawImage(pos, img); - } - p.end(); - - sendReplyImage(multipleOutputsImage); } } else { @@ -411,10 +421,41 @@ void ScreenShotEffect::sendReplyImage(const QImage &img) close(fd); } }, m_fd, img); - m_fd = -1; } else { QDBusConnection::sessionBus().send(m_replyMessage.createReply(saveTempImage(img))); } + + clearState(); +} + +void ScreenShotEffect::sendReplyImages() +{ + QList outputImages; + for (const QPoint &pos : qAsConst(m_orderImg)) { + auto it = m_cacheOutputsImages.find(pos); + if (it != m_cacheOutputsImages.constEnd()) { + outputImages.append(*it); + } + } + QtConcurrent::run( + [] (int fd, const QList &outputImages) { + QFile file; + if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) { + QDataStream ds(&file); + ds.setVersion(QDataStream::Qt_DefaultCompiledVersion); + ds << outputImages; + file.close(); + } else { + close(fd); + } + }, m_fd, outputImages); + + clearState(); +} + +void ScreenShotEffect::clearState() +{ + m_fd = -1; m_scheduledGeometry = QRect(); m_multipleOutputsRendered = QRegion(); m_captureCursor = false; @@ -422,6 +463,7 @@ void ScreenShotEffect::sendReplyImage(const QImage &img) m_cacheOutputsImages.clear(); m_cachedOutputGeometry = QRect(); m_nativeSize = false; + m_orderImg.clear(); } QString ScreenShotEffect::saveTempImage(const QImage &img) @@ -661,6 +703,50 @@ void ScreenShotEffect::screenshotScreen(QDBusUnixFileDescriptor fd, bool capture ); } +void ScreenShotEffect::screenshotScreens(QDBusUnixFileDescriptor fd, const QStringList &screensNames, bool captureCursor, bool shouldReturnNativeSize) +{ + if (!checkCall()) { + return; + } + m_fd = dup(fd.fileDescriptor()); + if (m_fd == -1) { + sendErrorReply(s_errorFd, s_errorFdMsg); + return; + } + m_captureCursor = captureCursor; + m_nativeSize = shouldReturnNativeSize; + m_orderImg = QList(); + m_scheduledGeometry = QRect(); + + const QList screens = QGuiApplication::screens(); + + QStringList lscreensNames = screensNames; + for (const QScreen *screen : screens) { + const int indexName = lscreensNames.indexOf(screen->name()); + if (indexName != -1) { + lscreensNames.removeAt(indexName); + const auto screenGeom = screen->geometry(); + if (!screenGeom.isValid()) { + close(m_fd); + clearState(); + sendErrorReply(s_errorScreenMissing, s_errorScreenMissingMsg + " : " + screen->name()); + return; + } + m_scheduledGeometry = m_scheduledGeometry.united(screenGeom); + m_orderImg.insert(indexName, screenGeom.topLeft()); + } + } + + if (!lscreensNames.isEmpty()) { + close(m_fd); + clearState(); + sendErrorReply(s_errorScreenMissing, s_errorScreenMissingMsg + " : " + lscreensNames.join(", ")); + return; + } + + effects->addRepaint(m_scheduledGeometry); +} + QString ScreenShotEffect::screenshotArea(int x, int y, int width, int height, bool captureCursor) { if (!checkCall()) { diff --git a/effects/screenshot/screenshot.h b/effects/screenshot/screenshot.h index d714ff8d9..e4943a887 100644 --- a/effects/screenshot/screenshot.h +++ b/effects/screenshot/screenshot.h @@ -22,6 +22,11 @@ class ComparableQPoint; namespace KWin { +/** + * The screenshot effet allows to takes screenshot, by window, area, screen, etc... + * + * A using application must have "org.kde.kwin.Screenshot" in its X-KDE-DBUS-Restricted-Interfaces application service file field. + */ class ScreenShotEffect : public Effect, protected QDBusContext { Q_OBJECT @@ -77,10 +82,7 @@ public Q_SLOTS: */ Q_SCRIPTABLE QString screenshotFullscreen(bool captureCursor = false); /** - * Starts an interactive screenshot session. - * - * The user is asked to confirm that a screenshot is taken by having to actively - * click and giving the possibility to cancel. + * Takes a full screen screenshot in a one file format. * * Once the screenshot is taken it gets saved into the @p fd passed to the * method. It is intended to be used with a pipe, so that the invoking side can just @@ -91,6 +93,16 @@ public Q_SLOTS: * @param shouldReturnNativeSize Whether to return an image according to the virtualGeometry, or according to pixel on screen size */ Q_SCRIPTABLE void screenshotFullscreen(QDBusUnixFileDescriptor fd, bool captureCursor = false, bool shouldReturnNativeSize = false); + /** + * Take a screenshot of the passed screens and return a QList in the fd response, + * an image for each screen in pixel-on-screen size when shouldReturnNativeSize is passed, or converted to using logicale size if not + * + * @param fd + * @param screensNames the names of the screens whose screenshot to return + * @param captureCursor + * @param shouldReturnNativeSize + */ + Q_SCRIPTABLE void screenshotScreens(QDBusUnixFileDescriptor fd, const QStringList &screensNames, bool captureCursor = false, bool shouldReturnNativeSize = false); /** * Saves a screenshot of the screen identified by @p screen into a file and returns the path to the file. * Functionality requires hardware support, if not available a null string is returned. @@ -135,6 +147,8 @@ private: QImage blitScreenshot(const QRect &geometry, const qreal scale = 1.0); QString saveTempImage(const QImage &img); void sendReplyImage(const QImage &img); + void sendReplyImages(); + void clearState(); enum class InfoMessageMode { Window, Screen @@ -152,6 +166,7 @@ private: QRect m_cachedOutputGeometry; QRegion m_multipleOutputsRendered; QMap m_cacheOutputsImages; + QList m_orderImg; bool m_captureCursor = false; bool m_nativeSize = false; enum class WindowMode { diff --git a/service_utils.h b/service_utils.h index d44e1b233..c9b9afb9c 100644 --- a/service_utils.h +++ b/service_utils.h @@ -50,12 +50,12 @@ static QStringList fetchProcessServiceField(const QString &executablePath, const return fieldValues; } -static QStringList fetchRequestedInterfaces(const QString &executablePath) +static inline QStringList fetchRequestedInterfaces(const QString &executablePath) { return fetchProcessServiceField(executablePath, s_waylandInterfaceName); } -static QStringList fetchRestrictedDBusInterfacesFromPid(const uint pid) +static inline QStringList fetchRestrictedDBusInterfacesFromPid(const uint pid) { const auto executablePath = QFileInfo(QStringLiteral("/proc/%1/exe").arg(pid)).symLinkTarget(); return fetchProcessServiceField(executablePath, s_dbusRestrictedInterfaceName);