diff --git a/src/effects/screenshot/CMakeLists.txt b/src/effects/screenshot/CMakeLists.txt
index 23d826dbfb..bbea5f7b9f 100644
--- a/src/effects/screenshot/CMakeLists.txt
+++ b/src/effects/screenshot/CMakeLists.txt
@@ -6,4 +6,7 @@ set(kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources}
../service_utils.cpp
screenshot/screenshot.cpp
screenshot/screenshotdbusinterface1.cpp
+ screenshot/screenshotdbusinterface2.cpp
)
+
+qt5_add_dbus_adaptor(kwin4_effect_builtins_sources screenshot/org.kde.KWin.ScreenShot2.xml screenshot/screenshotdbusinterface2.h KWin::ScreenShotDBusInterface2)
diff --git a/src/effects/screenshot/org.kde.KWin.ScreenShot2.xml b/src/effects/screenshot/org.kde.KWin.ScreenShot2.xml
new file mode 100644
index 0000000000..34c03b2c1f
--- /dev/null
+++ b/src/effects/screenshot/org.kde.KWin.ScreenShot2.xml
@@ -0,0 +1,184 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/effects/screenshot/screenshot.cpp b/src/effects/screenshot/screenshot.cpp
index 1c715f8917..f292a8556d 100644
--- a/src/effects/screenshot/screenshot.cpp
+++ b/src/effects/screenshot/screenshot.cpp
@@ -10,6 +10,7 @@
*/
#include "screenshot.h"
#include "screenshotdbusinterface1.h"
+#include "screenshotdbusinterface2.h"
#include
#include
@@ -111,6 +112,7 @@ bool ScreenShotEffect::supported()
ScreenShotEffect::ScreenShotEffect()
: m_dbusInterface1(new ScreenShotDBusInterface1(this))
+ , m_dbusInterface2(new ScreenShotDBusInterface2(this))
{
connect(effects, &EffectsHandler::screenAdded, this, &ScreenShotEffect::handleScreenAdded);
connect(effects, &EffectsHandler::screenRemoved, this, &ScreenShotEffect::handleScreenRemoved);
diff --git a/src/effects/screenshot/screenshot.h b/src/effects/screenshot/screenshot.h
index 454efc35de..7ab8169b24 100644
--- a/src/effects/screenshot/screenshot.h
+++ b/src/effects/screenshot/screenshot.h
@@ -31,6 +31,7 @@ enum ScreenShotFlag {
Q_DECLARE_FLAGS(ScreenShotFlags, ScreenShotFlag)
class ScreenShotDBusInterface1;
+class ScreenShotDBusInterface2;
struct ScreenShotWindowData;
struct ScreenShotAreaData;
struct ScreenShotScreenData;
@@ -100,6 +101,7 @@ private:
QVector m_screenScreenShots;
QScopedPointer m_dbusInterface1;
+ QScopedPointer m_dbusInterface2;
EffectScreen *m_paintedScreen = nullptr;
};
diff --git a/src/effects/screenshot/screenshotdbusinterface1.h b/src/effects/screenshot/screenshotdbusinterface1.h
index 77a8742c86..3cff6dbdb7 100644
--- a/src/effects/screenshot/screenshotdbusinterface1.h
+++ b/src/effects/screenshot/screenshotdbusinterface1.h
@@ -25,6 +25,8 @@ class ScreenShotSource1;
*
* An application that requests a screenshot must have "org.kde.kwin.Screenshot" listed in its
* X-KDE-DBUS-Restricted-Interfaces desktop file field.
+ *
+ * @deprecated Use org.kde.KWin.ScreenShot2 interface instead.
*/
class ScreenShotDBusInterface1 : public QObject, protected QDBusContext
{
diff --git a/src/effects/screenshot/screenshotdbusinterface2.cpp b/src/effects/screenshot/screenshotdbusinterface2.cpp
new file mode 100644
index 0000000000..56c35de8d4
--- /dev/null
+++ b/src/effects/screenshot/screenshotdbusinterface2.cpp
@@ -0,0 +1,477 @@
+/*
+ SPDX-FileCopyrightText: 2010 Martin Gräßlin
+ SPDX-FileCopyrightText: 2021 Méven Car
+ SPDX-FileCopyrightText: 2021 Vlad Zahorodnii
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "screenshotdbusinterface2.h"
+#include "../service_utils.h"
+#include "screenshot2adaptor.h"
+
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+namespace KWin
+{
+
+static ScreenShotFlags screenShotFlagsFromOptions(const QVariantMap &options)
+{
+ ScreenShotFlags flags = ScreenShotFlags();
+
+ const QVariant includeDecoration = options.value(QStringLiteral("include-decoration"));
+ if (includeDecoration.toBool()) {
+ flags |= ScreenShotIncludeDecoration;
+ }
+
+ const QVariant includeCursor = options.value(QStringLiteral("include-cursor"));
+ if (includeCursor.toBool()) {
+ flags |= ScreenShotIncludeCursor;
+ }
+
+ const QVariant nativeResolution = options.value(QStringLiteral("native-resolution"));
+ if (nativeResolution.toBool()) {
+ flags |= ScreenShotNativeResolution;
+ }
+
+ return flags;
+}
+
+static void writeBufferToPipe(int fileDescriptor, const QByteArray &buffer)
+{
+ QFile file;
+ if (!file.open(fileDescriptor, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) {
+ close(fileDescriptor);
+ qCWarning(KWINEFFECTS) << Q_FUNC_INFO << "failed to open pipe:" << file.errorString();
+ return;
+ }
+
+ qint64 remainingSize = buffer.size();
+
+ pollfd pfds[1];
+ pfds[0].fd = fileDescriptor;
+ pfds[0].events = POLLOUT;
+
+ while (true) {
+ const int ready = poll(pfds, 1, 60000);
+ if (ready < 0) {
+ if (errno != EINTR) {
+ qCWarning(KWINEFFECTS) << Q_FUNC_INFO << "poll() failed:" << strerror(errno);
+ return;
+ }
+ } else if (ready == 0) {
+ qCWarning(KWINEFFECTS) << Q_FUNC_INFO << "timed out writing to pipe";
+ return;
+ } else if (!(pfds[0].revents & POLLOUT)) {
+ qCWarning(KWINEFFECTS) << Q_FUNC_INFO << "pipe is broken";
+ return;
+ } else {
+ const char *chunk = buffer.constData() + (buffer.size() - remainingSize);
+ const qint64 writtenCount = file.write(chunk, remainingSize);
+
+ if (writtenCount < 0) {
+ qCWarning(KWINEFFECTS) << Q_FUNC_INFO << "write() failed:" << file.errorString();
+ return;
+ }
+
+ remainingSize -= writtenCount;
+ if (writtenCount == 0 || remainingSize == 0) {
+ return;
+ }
+ }
+ }
+}
+
+static const QString s_dbusServiceName = QStringLiteral("org.kde.KWin.ScreenShot2");
+static const QString s_dbusInterface = QStringLiteral("org.kde.KWin.ScreenShot2");
+static const QString s_dbusObjectPath = QStringLiteral("/org/kde/KWin/ScreenShot2");
+
+static const QString s_errorNotAuthorized = QStringLiteral("org.kde.KWin.ScreenShot2.Error.NoAuthorized");
+static const QString s_errorNotAuthorizedMessage = QStringLiteral("The process is not authorized to take a screenshot");
+static const QString s_errorCancelled = QStringLiteral("org.kde.KWin.ScreenShot2.Error.Cancelled");
+static const QString s_errorCancelledMessage = QStringLiteral("Screenshot got cancelled");
+static const QString s_errorInvalidWindow = QStringLiteral("org.kde.KWin.ScreenShot2.Error.InvalidWindow");
+static const QString s_errorInvalidWindowMessage = QStringLiteral("Invalid window requested");
+static const QString s_errorInvalidArea = QStringLiteral("org.kde.KWin.ScreenShot2.Error.InvalidArea");
+static const QString s_errorInvalidAreaMessage = QStringLiteral("Invalid area requested");
+static const QString s_errorInvalidScreen = QStringLiteral("org.kde.KWin.ScreenShot2.Error.InvalidScreen");
+static const QString s_errorInvalidScreenMessage = QStringLiteral("Invalid screen requested");
+static const QString s_errorFileDescriptor = QStringLiteral("org.kde.KWin.ScreenShot2.Error.FileDescriptor");
+static const QString s_errorFileDescriptorMessage = QStringLiteral("No valid file descriptor");
+
+class ScreenShotSource2 : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit ScreenShotSource2(const QFuture &future);
+
+ bool isCancelled() const;
+ bool isCompleted() const;
+ void marshal(ScreenShotSinkPipe2 *sink);
+
+Q_SIGNALS:
+ void cancelled();
+ void completed();
+
+private:
+ QFuture m_future;
+ QFutureWatcher *m_watcher;
+};
+
+class ScreenShotSourceScreen2 : public ScreenShotSource2
+{
+ Q_OBJECT
+
+public:
+ ScreenShotSourceScreen2(ScreenShotEffect *effect, EffectScreen *screen, ScreenShotFlags flags);
+};
+
+class ScreenShotSourceArea2 : public ScreenShotSource2
+{
+ Q_OBJECT
+
+public:
+ ScreenShotSourceArea2(ScreenShotEffect *effect, const QRect &area, ScreenShotFlags flags);
+};
+
+class ScreenShotSourceWindow2 : public ScreenShotSource2
+{
+ Q_OBJECT
+
+public:
+ ScreenShotSourceWindow2(ScreenShotEffect *effect, EffectWindow *window, ScreenShotFlags flags);
+};
+
+class ScreenShotSinkPipe2 : public QObject
+{
+ Q_OBJECT
+
+public:
+ ScreenShotSinkPipe2(int fileDescriptor, QDBusMessage replyMessage);
+ ~ScreenShotSinkPipe2();
+
+ void cancel();
+ void flush(const QImage &image);
+
+private:
+ QDBusMessage m_replyMessage;
+ int m_fileDescriptor;
+};
+
+ScreenShotSource2::ScreenShotSource2(const QFuture &future)
+ : m_future(future)
+{
+ m_watcher = new QFutureWatcher(this);
+ connect(m_watcher, &QFutureWatcher::finished, this, &ScreenShotSource2::completed);
+ connect(m_watcher, &QFutureWatcher::canceled, this, &ScreenShotSource2::cancelled);
+ m_watcher->setFuture(m_future);
+}
+
+bool ScreenShotSource2::isCancelled() const
+{
+ return m_future.isCanceled();
+}
+
+bool ScreenShotSource2::isCompleted() const
+{
+ return m_future.isFinished();
+}
+
+void ScreenShotSource2::marshal(ScreenShotSinkPipe2 *sink)
+{
+ sink->flush(m_future.result());
+}
+
+ScreenShotSourceScreen2::ScreenShotSourceScreen2(ScreenShotEffect *effect,
+ EffectScreen *screen,
+ ScreenShotFlags flags)
+ : ScreenShotSource2(effect->scheduleScreenShot(screen, flags))
+{
+}
+
+ScreenShotSourceArea2::ScreenShotSourceArea2(ScreenShotEffect *effect,
+ const QRect &area,
+ ScreenShotFlags flags)
+ : ScreenShotSource2(effect->scheduleScreenShot(area, flags))
+{
+}
+
+ScreenShotSourceWindow2::ScreenShotSourceWindow2(ScreenShotEffect *effect,
+ EffectWindow *window,
+ ScreenShotFlags flags)
+ : ScreenShotSource2(effect->scheduleScreenShot(window, flags))
+{
+}
+
+ScreenShotSinkPipe2::ScreenShotSinkPipe2(int fileDescriptor, QDBusMessage replyMessage)
+ : m_replyMessage(replyMessage)
+ , m_fileDescriptor(fileDescriptor)
+{
+}
+
+ScreenShotSinkPipe2::~ScreenShotSinkPipe2()
+{
+ if (m_fileDescriptor != -1) {
+ close(m_fileDescriptor);
+ }
+}
+
+void ScreenShotSinkPipe2::cancel()
+{
+ QDBusConnection::sessionBus().send(m_replyMessage.createErrorReply(s_errorCancelled,
+ s_errorCancelledMessage));
+}
+
+void ScreenShotSinkPipe2::flush(const QImage &image)
+{
+ if (m_fileDescriptor == -1) {
+ return;
+ }
+
+ // Note that the type of the data stored in the vardict matters. Be careful.
+ QVariantMap results;
+ results.insert(QStringLiteral("type"), QStringLiteral("raw"));
+ results.insert(QStringLiteral("format"), quint32(image.format()));
+ results.insert(QStringLiteral("width"), quint32(image.width()));
+ results.insert(QStringLiteral("height"), quint32(image.height()));
+ results.insert(QStringLiteral("stride"), quint32(image.bytesPerLine()));
+ QDBusConnection::sessionBus().send(m_replyMessage.createReply(results));
+
+ QtConcurrent::run([](int fileDescriptor, const QImage &image) {
+ const QByteArray buffer(reinterpret_cast(image.constBits()),
+ image.sizeInBytes());
+ writeBufferToPipe(fileDescriptor, buffer);
+ }, m_fileDescriptor, image);
+
+ // The ownership of the pipe file descriptor has been moved to the worker thread.
+ m_fileDescriptor = -1;
+}
+
+ScreenShotDBusInterface2::ScreenShotDBusInterface2(ScreenShotEffect *effect)
+ : QObject(effect)
+ , m_effect(effect)
+{
+ new ScreenShot2Adaptor(this);
+
+ QDBusConnection::sessionBus().registerObject(s_dbusObjectPath, this);
+ QDBusConnection::sessionBus().registerService(s_dbusServiceName);
+}
+
+ScreenShotDBusInterface2::~ScreenShotDBusInterface2()
+{
+ QDBusConnection::sessionBus().unregisterService(s_dbusServiceName);
+ QDBusConnection::sessionBus().unregisterObject(s_dbusObjectPath);
+}
+
+bool ScreenShotDBusInterface2::checkPermissions() const
+{
+ if (!calledFromDBus()) {
+ return false;
+ }
+
+ const QDBusReply reply = connection().interface()->servicePid(message().service());
+ if (reply.isValid()) {
+ const uint pid = reply.value();
+ const auto interfaces = KWin::fetchRestrictedDBusInterfacesFromPid(pid);
+ if (!interfaces.contains(s_dbusInterface)) {
+ sendErrorReply(s_errorNotAuthorized, s_errorNotAuthorizedMessage);
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+QVariantMap ScreenShotDBusInterface2::CaptureWindow(const QString &handle,
+ const QVariantMap &options,
+ QDBusUnixFileDescriptor pipe)
+{
+ if (!checkPermissions()) {
+ return QVariantMap();
+ }
+
+ EffectWindow *window = effects->findWindow(handle);
+ if (!window) {
+ bool ok;
+ const int winId = handle.toInt(&ok);
+ if (ok) {
+ window = effects->findWindow(winId);
+ } else {
+ qCWarning(KWINEFFECTS) << "Invalid handle:" << handle;
+ }
+ }
+ if (!window) {
+ sendErrorReply(s_errorInvalidWindow, s_errorInvalidWindowMessage);
+ return QVariantMap();
+ }
+
+ const int fileDescriptor = dup(pipe.fileDescriptor());
+ if (fileDescriptor == -1) {
+ sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage);
+ return QVariantMap();
+ }
+
+ takeScreenShot(window, screenShotFlagsFromOptions(options),
+ new ScreenShotSinkPipe2(fileDescriptor, message()));
+
+ setDelayedReply(true);
+ return QVariantMap();
+}
+
+QVariantMap ScreenShotDBusInterface2::CaptureArea(int x, int y, int width, int height,
+ const QVariantMap &options,
+ QDBusUnixFileDescriptor pipe)
+{
+ if (!checkPermissions()) {
+ return QVariantMap();
+ }
+
+ const QRect area(x, y, width, height);
+ if (area.isEmpty()) {
+ sendErrorReply(s_errorInvalidArea, s_errorInvalidAreaMessage);
+ return QVariantMap();
+ }
+
+ const int fileDescriptor = dup(pipe.fileDescriptor());
+ if (fileDescriptor == -1) {
+ sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage);
+ return QVariantMap();
+ }
+
+ takeScreenShot(area, screenShotFlagsFromOptions(options),
+ new ScreenShotSinkPipe2(fileDescriptor, message()));
+
+ setDelayedReply(true);
+ return QVariantMap();
+}
+
+QVariantMap ScreenShotDBusInterface2::CaptureScreen(const QString &name,
+ const QVariantMap &options,
+ QDBusUnixFileDescriptor pipe)
+{
+ if (!checkPermissions()) {
+ return QVariantMap();
+ }
+
+ EffectScreen *screen = effects->findScreen(name);
+ if (!screen) {
+ sendErrorReply(s_errorInvalidScreen, s_errorInvalidScreenMessage);
+ return QVariantMap();
+ }
+
+ const int fileDescriptor = dup(pipe.fileDescriptor());
+ if (fileDescriptor == -1) {
+ sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage);
+ return QVariantMap();
+ }
+
+ takeScreenShot(screen, screenShotFlagsFromOptions(options),
+ new ScreenShotSinkPipe2(fileDescriptor, message()));
+
+ setDelayedReply(true);
+ return QVariantMap();
+}
+
+QVariantMap ScreenShotDBusInterface2::CaptureInteractive(uint kind,
+ const QVariantMap &options,
+ QDBusUnixFileDescriptor pipe)
+{
+ const int fileDescriptor = dup(pipe.fileDescriptor());
+ if (fileDescriptor == -1) {
+ sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage);
+ return QVariantMap();
+ }
+
+ const QDBusMessage replyMessage = message();
+
+ if (kind == 0) {
+ effects->startInteractiveWindowSelection([=](EffectWindow *window) {
+ effects->hideOnScreenMessage(EffectsHandler::OnScreenMessageHideFlag::SkipsCloseAnimation);
+
+ if (!window) {
+ close(fileDescriptor);
+
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ bus.send(replyMessage.createErrorReply(s_errorCancelled, s_errorCancelledMessage));
+ } else {
+ takeScreenShot(window, screenShotFlagsFromOptions(options),
+ new ScreenShotSinkPipe2(fileDescriptor, replyMessage));
+ }
+ });
+ effects->showOnScreenMessage(i18n("Select window to screen shot with left click or enter.\n"
+ "Escape or right click to cancel."),
+ QStringLiteral("spectacle"));
+ } else {
+ effects->startInteractivePositionSelection([=](const QPoint &point) {
+ effects->hideOnScreenMessage(EffectsHandler::OnScreenMessageHideFlag::SkipsCloseAnimation);
+
+ if (point == QPoint(-1, -1)) {
+ close(fileDescriptor);
+
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ bus.send(replyMessage.createErrorReply(s_errorCancelled, s_errorCancelledMessage));
+ } else {
+ EffectScreen *screen = effects->screenAt(point);
+ takeScreenShot(screen, screenShotFlagsFromOptions(options),
+ new ScreenShotSinkPipe2(fileDescriptor, replyMessage));
+ }
+ });
+ effects->showOnScreenMessage(i18n("Create screen shot with left click or enter.\n"
+ "Escape or right click to cancel."),
+ QStringLiteral("spectacle"));
+ }
+
+ setDelayedReply(true);
+ return QVariantMap();
+}
+
+void ScreenShotDBusInterface2::bind(ScreenShotSinkPipe2 *sink, ScreenShotSource2 *source)
+{
+ connect(source, &ScreenShotSource2::cancelled, sink, [sink, source]() {
+ sink->cancel();
+
+ sink->deleteLater();
+ source->deleteLater();
+ });
+
+ connect(source, &ScreenShotSource2::completed, sink, [sink, source]() {
+ source->marshal(sink);
+
+ sink->deleteLater();
+ source->deleteLater();
+ });
+}
+
+void ScreenShotDBusInterface2::takeScreenShot(EffectScreen *screen, ScreenShotFlags flags,
+ ScreenShotSinkPipe2 *sink)
+{
+ bind(sink, new ScreenShotSourceScreen2(m_effect, screen, flags));
+}
+
+void ScreenShotDBusInterface2::takeScreenShot(const QRect &area, ScreenShotFlags flags,
+ ScreenShotSinkPipe2 *sink)
+{
+ bind(sink, new ScreenShotSourceArea2(m_effect, area, flags));
+}
+
+void ScreenShotDBusInterface2::takeScreenShot(EffectWindow *window, ScreenShotFlags flags,
+ ScreenShotSinkPipe2 *sink)
+{
+ bind(sink, new ScreenShotSourceWindow2(m_effect, window, flags));
+}
+
+} // namespace KWin
+
+#include "screenshotdbusinterface2.moc"
diff --git a/src/effects/screenshot/screenshotdbusinterface2.h b/src/effects/screenshot/screenshotdbusinterface2.h
new file mode 100644
index 0000000000..d44426fffd
--- /dev/null
+++ b/src/effects/screenshot/screenshotdbusinterface2.h
@@ -0,0 +1,61 @@
+/*
+ SPDX-FileCopyrightText: 2010 Martin Gräßlin
+ SPDX-FileCopyrightText: 2021 Vlad Zahorodnii
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include "screenshot/screenshot.h"
+
+#include
+#include
+#include
+#include
+
+namespace KWin
+{
+
+class ScreenShotEffect;
+class ScreenShotSinkPipe2;
+class ScreenShotSource2;
+
+/**
+ * The ScreenshotDBusInterface2 class provides a d-bus api to take screenshots. This implements
+ * the org.kde.KWin.ScreenShot2 interface.
+ *
+ * An application that requests a screenshot must have "org.kde.KWin.ScreenShot2" listed in its
+ * X-KDE-DBUS-Restricted-Interfaces desktop file field.
+ */
+class ScreenShotDBusInterface2 : public QObject, public QDBusContext
+{
+ Q_OBJECT
+
+public:
+ explicit ScreenShotDBusInterface2(ScreenShotEffect *effect);
+ ~ScreenShotDBusInterface2() override;
+
+public Q_SLOTS:
+ QVariantMap CaptureWindow(const QString &handle, const QVariantMap &options,
+ QDBusUnixFileDescriptor pipe);
+ QVariantMap CaptureArea(int x, int y, int width, int height,
+ const QVariantMap &options,
+ QDBusUnixFileDescriptor pipe);
+ QVariantMap CaptureScreen(const QString &name, const QVariantMap &options,
+ QDBusUnixFileDescriptor pipe);
+ QVariantMap CaptureInteractive(uint kind, const QVariantMap &options,
+ QDBusUnixFileDescriptor pipe);
+
+private:
+ void takeScreenShot(EffectScreen *screen, ScreenShotFlags flags, ScreenShotSinkPipe2 *sink);
+ void takeScreenShot(const QRect &area, ScreenShotFlags flags, ScreenShotSinkPipe2 *sink);
+ void takeScreenShot(EffectWindow *window, ScreenShotFlags flags, ScreenShotSinkPipe2 *sink);
+
+ void bind(ScreenShotSinkPipe2 *sink, ScreenShotSource2 *source);
+ bool checkPermissions() const;
+
+ ScreenShotEffect *m_effect;
+};
+
+} // namespace KWin