diff --git a/data/kde.portal b/data/kde.portal
--- a/data/kde.portal
+++ b/data/kde.portal
@@ -1,4 +1,4 @@
[portal]
DBusName=org.freedesktop.impl.portal.desktop.kde
-Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Email;org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.Inhibit;org.freedesktop.impl.portal.Notification;org.freedesktop.impl.portal.Print;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.RemoteDesktop;org.freedesktop.impl.portal.Settings
+Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Background;org.freedesktop.impl.portal.Email;org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.Inhibit;org.freedesktop.impl.portal.Notification;org.freedesktop.impl.portal.Print;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.RemoteDesktop;org.freedesktop.impl.portal.Settings
UseIn=KDE
diff --git a/data/org.freedesktop.impl.portal.desktop.kde.desktop.in b/data/org.freedesktop.impl.portal.desktop.kde.desktop.in
--- a/data/org.freedesktop.impl.portal.desktop.kde.desktop.in
+++ b/data/org.freedesktop.impl.portal.desktop.kde.desktop.in
@@ -1,6 +1,6 @@
[Desktop Entry]
Type=Application
Exec=@CMAKE_INSTALL_FULL_LIBEXECDIR@/xdg-desktop-portal-kde
-X-KDE-Wayland-Interfaces=org_kde_kwin_fake_input,org_kde_kwin_remote_access_manager
+X-KDE-Wayland-Interfaces=org_kde_kwin_fake_input,org_kde_kwin_remote_access_manager,org_kde_plasma_window_management
NoDisplay=true
Icon=kde
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -8,6 +8,7 @@
accessdialog.cpp
appchooser.cpp
appchooserdialog.cpp
+ background.cpp
desktopportal.cpp
email.cpp
filechooser.cpp
diff --git a/src/background.h b/src/background.h
new file mode 100644
--- /dev/null
+++ b/src/background.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright © 2020 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Authors:
+ * Jan Grulich
+ */
+
+#ifndef XDG_DESKTOP_PORTAL_KDE_BACKGROUND_H
+#define XDG_DESKTOP_PORTAL_KDE_BACKGROUND_H
+
+#include
+#include
+
+namespace KWayland {
+ namespace Client {
+ class PlasmaWindow;
+ }
+}
+
+class BackgroundPortal : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.impl.portal.Background")
+public:
+ explicit BackgroundPortal(QObject *parent);
+ ~BackgroundPortal();
+
+ enum ApplicationState {
+ Background = 0,
+ Running = 1,
+ Active = 2
+ };
+
+ enum AutostartFlag {
+ None = 0x0,
+ Activatable = 0x1
+ };
+ Q_DECLARE_FLAGS(AutostartFlags, AutostartFlag)
+
+ enum NotifyResult {
+ Forbid = 0,
+ Allow = 1,
+ Ignore = 2
+ };
+
+public Q_SLOTS:
+ QVariantMap GetAppState();
+
+ uint NotifyBackground(const QDBusObjectPath &handle,
+ const QString &app_id,
+ const QString &name,
+ QVariantMap &results);
+
+ bool EnableAutostart(const QString &app_id,
+ bool enable,
+ const QStringList &commandline,
+ uint flags);
+Q_SIGNALS:
+ void RunningApplicationsChanged();
+
+private:
+ void addWindow(KWayland::Client::PlasmaWindow *window);
+ void setActiveWindow(const QString &appId, bool active);
+
+ uint m_notificationCounter = 0;
+ QList m_windowList;
+ QVariantMap m_appStates;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS(BackgroundPortal::AutostartFlags)
+
+#endif // XDG_DESKTOP_PORTAL_KDE_BACKGROUND_H
+
+
diff --git a/src/background.cpp b/src/background.cpp
new file mode 100644
--- /dev/null
+++ b/src/background.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright © 2020 Red Hat, Inc
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Authors:
+ * Jan Grulich
+ */
+
+#include "background.h"
+#include "utils.h"
+#include "waylandintegration.h"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+Q_LOGGING_CATEGORY(XdgDesktopPortalKdeBackground, "xdp-kde-background")
+
+BackgroundPortal::BackgroundPortal(QObject *parent)
+ : QDBusAbstractAdaptor(parent)
+{
+ connect(WaylandIntegration::waylandIntegration(), &WaylandIntegration::WaylandIntegration::plasmaWindowManagementInitialized, this, [=] () {
+ connect(WaylandIntegration::plasmaWindowManagement(), &KWayland::Client::PlasmaWindowManagement::windowCreated, this, [=] (KWayland::Client::PlasmaWindow *window) {
+ addWindow(window);
+ });
+
+ m_windowList = WaylandIntegration::plasmaWindowManagement()->windows();
+ for (KWayland::Client::PlasmaWindow *window : m_windowList) {
+ addWindow(window);
+ }
+ });
+}
+
+BackgroundPortal::~BackgroundPortal()
+{
+}
+
+QVariantMap BackgroundPortal::GetAppState()
+{
+ qCDebug(XdgDesktopPortalKdeBackground) << "GetAppState called: no parameters";
+ return m_appStates;
+}
+
+uint BackgroundPortal::NotifyBackground(const QDBusObjectPath &handle,
+ const QString &app_id,
+ const QString &name,
+ QVariantMap &results)
+{
+ Q_UNUSED(results);
+
+ qCDebug(XdgDesktopPortalKdeBackground) << "NotifyBackground called with parameters:";
+ qCDebug(XdgDesktopPortalKdeBackground) << " handle: " << handle.path();
+ qCDebug(XdgDesktopPortalKdeBackground) << " app_id: " << app_id;
+ qCDebug(XdgDesktopPortalKdeBackground) << " name: " << name;
+
+ KNotification *notify = new KNotification(QStringLiteral("notification"), KNotification::Persistent | KNotification::DefaultEvent, this);
+ notify->setTitle(i18n("Background activity"));
+ notify->setText(i18n("%1 is running in the background.", app_id));
+ notify->setActions({i18n("Find out more")});
+
+ QObject *obj = QObject::parent();
+
+ if (!obj) {
+ qCWarning(XdgDesktopPortalKdeBackground) << "Failed to get dbus context";
+ return 2;
+ }
+
+ void *ptr = obj->qt_metacast("QDBusContext");
+ QDBusContext *q_ptr = reinterpret_cast(ptr);
+
+ if (!q_ptr) {
+ qCWarning(XdgDesktopPortalKdeBackground) << "Failed to get dbus context";
+ return 2;
+ }
+
+ QDBusMessage reply;
+ QDBusMessage message = q_ptr->message();
+
+ message.setDelayedReply(true);
+ reply = message.createReply();
+ QDBusConnection::sessionBus().send(reply);
+
+ bool notificationActivated = false;
+ connect(notify, QOverload::of(&KNotification::activated), this, [=, ¬ificationActivated] (uint action) {
+ if (action != 1) {
+ return;
+ }
+ notificationActivated = true;
+
+ const QString title = i18n("%1 is running in the background", app_id);
+ const QString text = i18n("This might be for a legitimate reason, but the application has not provided one."
+ "\n\nNote that forcing an application to quit might cause data loss.");
+ QMessageBox messageBox(QMessageBox::Question, title, text);
+ QPushButton *quitButton = messageBox.addButton(i18n("Force quit"), QMessageBox::RejectRole);
+ QPushButton *allowButton = messageBox.addButton(i18n("Allow"), QMessageBox::AcceptRole);
+ messageBox.exec();
+
+ BackgroundPortal::NotifyResult result = BackgroundPortal::Ignore;
+ if (messageBox.clickedButton() == quitButton) {
+ result = BackgroundPortal::Forbid;
+ } else if (messageBox.clickedButton() == allowButton) {
+ result = BackgroundPortal::Allow;
+ }
+
+ QVariantMap map;
+ map.insert(QStringLiteral("result"), static_cast(result));
+ QDBusMessage reply = message.createReply({0, map});
+ if (!QDBusConnection::sessionBus().send(reply)) {
+ qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response";
+ }
+ });
+ connect(notify, &KNotification::closed, this, [=, ¬ificationActivated] () {
+ if (notificationActivated) {
+ return;
+ }
+
+ QVariantMap map;
+ map.insert(QStringLiteral("result"), static_cast(BackgroundPortal::Ignore));
+ QDBusMessage reply = message.createReply({0, map});
+ if (!QDBusConnection::sessionBus().send(reply)) {
+ qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response";
+ }
+ });
+
+ notify->sendEvent();
+
+ return 0;
+}
+
+bool BackgroundPortal::EnableAutostart(const QString &app_id,
+ bool enable,
+ const QStringList &commandline,
+ uint flags)
+{
+ qCDebug(XdgDesktopPortalKdeBackground) << "EnableAutostart called with parameters:";
+ qCDebug(XdgDesktopPortalKdeBackground) << " app_id: " << app_id;
+ qCDebug(XdgDesktopPortalKdeBackground) << " enable: " << enable;
+ qCDebug(XdgDesktopPortalKdeBackground) << " commandline: " << commandline;
+ qCDebug(XdgDesktopPortalKdeBackground) << " flags: " << flags;
+
+ const QString fileName = app_id + QStringLiteral(".desktop");
+ const QString directory = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/autostart/");
+ const QString fullPath = directory + fileName;
+ const AutostartFlags autostartFlags = static_cast(flags);
+
+ if (!enable) {
+ QFile file(fullPath);
+ if (!file.remove()) {
+ qCDebug(XdgDesktopPortalKdeBackground) << "Failed to remove " << fileName << " to disable autostart.";
+ }
+ return false;
+ }
+
+ QDir dir(directory);
+ if (!dir.mkpath(dir.absolutePath())) {
+ qCDebug(XdgDesktopPortalKdeBackground) << "Failed to create autostart directory.";
+ return false;
+ }
+
+ KDesktopFile desktopFile(fullPath);
+ KConfigGroup desktopEntryConfigGroup = desktopFile.desktopGroup();
+ desktopEntryConfigGroup.writeEntry(QStringLiteral("Type"), QStringLiteral("Application"));
+ desktopEntryConfigGroup.writeEntry(QStringLiteral("Name"), app_id);
+ desktopEntryConfigGroup.writeEntry(QStringLiteral("Exec"), KShell::joinArgs(commandline));
+ if (autostartFlags.testFlag(AutostartFlag::Activatable)) {
+ desktopEntryConfigGroup.writeEntry(QStringLiteral("DBusActivatable"), true);
+ }
+ desktopEntryConfigGroup.writeEntry(QStringLiteral("X-Flatpak"), app_id);
+
+ return true;
+}
+
+void BackgroundPortal::addWindow(KWayland::Client::PlasmaWindow *window)
+{
+ const QString appId = window->appId();
+ const bool isActive = window->isActive();
+ m_appStates[appId] = QVariant::fromValue(isActive ? Active : Running);
+
+ connect(window, &KWayland::Client::PlasmaWindow::activeChanged, this, [=] () {
+ setActiveWindow(window->appId(), window->isActive());
+ });
+ connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [=] () {
+ uint windows = 0;
+ const QString appId = window->appId();
+ for (KWayland::Client::PlasmaWindow *otherWindow : WaylandIntegration::plasmaWindowManagement()->windows()) {
+ if (otherWindow->appId() == appId && otherWindow->internalId() != window->internalId()) {
+ windows++;
+ }
+ }
+
+ if (!windows) {
+ m_appStates.remove(appId);
+ Q_EMIT RunningApplicationsChanged();
+ }
+ });
+
+ Q_EMIT RunningApplicationsChanged();
+}
+
+void BackgroundPortal::setActiveWindow(const QString &appId, bool active)
+{
+ m_appStates[appId] = QVariant::fromValue(active ? Active : Running);
+
+ Q_EMIT RunningApplicationsChanged();
+}
diff --git a/src/desktopportal.h b/src/desktopportal.h
--- a/src/desktopportal.h
+++ b/src/desktopportal.h
@@ -26,18 +26,19 @@
#include "access.h"
#include "appchooser.h"
+#include "background.h"
#include "email.h"
#include "filechooser.h"
#include "inhibit.h"
#include "notification.h"
#include "print.h"
#if SCREENCAST_ENABLED
#include "screencast.h"
#include "remotedesktop.h"
-#include "waylandintegration.h"
#endif
#include "screenshot.h"
#include "settings.h"
+#include "waylandintegration.h"
class DesktopPortal : public QObject, public QDBusContext
{
@@ -49,6 +50,7 @@
private:
AccessPortal *m_access;
AppChooserPortal *m_appChooser;
+ BackgroundPortal *m_background;
EmailPortal *m_email;
FileChooserPortal *m_fileChooser;
InhibitPortal *m_inhibit;
diff --git a/src/desktopportal.cpp b/src/desktopportal.cpp
--- a/src/desktopportal.cpp
+++ b/src/desktopportal.cpp
@@ -37,12 +37,13 @@
{
const QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP").toUpper();
if (xdgCurrentDesktop == "KDE") {
+ m_background = new BackgroundPortal(this);
m_screenshot = new ScreenshotPortal(this);
#if SCREENCAST_ENABLED
m_screenCast = new ScreenCastPortal(this);
m_remoteDesktop = new RemoteDesktopPortal(this);
- WaylandIntegration::init();
#endif
+ WaylandIntegration::init();
}
}
diff --git a/src/waylandintegration.h b/src/waylandintegration.h
--- a/src/waylandintegration.h
+++ b/src/waylandintegration.h
@@ -26,6 +26,11 @@
#include
#include
+namespace KWayland {
+ namespace Client {
+ class PlasmaWindowManagement;
+ }
+}
namespace WaylandIntegration
{
@@ -76,6 +81,7 @@
Q_OBJECT
Q_SIGNALS:
void newBuffer(uint8_t *screenData);
+ void plasmaWindowManagementInitialized();
};
void authenticate();
@@ -94,6 +100,7 @@
void requestPointerMotionAbsolute(const QPointF &pos);
void requestPointerAxisDiscrete(Qt::Orientation axis, qreal delta);
+ KWayland::Client::PlasmaWindowManagement *plasmaWindowManagement();
QMap screens();
QVariant streams();
diff --git a/src/waylandintegration.cpp b/src/waylandintegration.cpp
--- a/src/waylandintegration.cpp
+++ b/src/waylandintegration.cpp
@@ -41,6 +41,7 @@
#include
#include
#include
+#include
// system
#include
@@ -57,7 +58,9 @@
void WaylandIntegration::init()
{
+#if SCREENCAST_ENABLED
globalWaylandIntegration->initDrm();
+#endif
globalWaylandIntegration->initWayland();
}
@@ -111,6 +114,11 @@
globalWaylandIntegration->requestPointerAxisDiscrete(axis, delta);
}
+KWayland::Client::PlasmaWindowManagement * WaylandIntegration::plasmaWindowManagement()
+{
+ return globalWaylandIntegration->plasmaWindowManagement();
+}
+
QMap WaylandIntegration::screens()
{
return globalWaylandIntegration->screens();
@@ -380,6 +388,11 @@
return m_outputMap;
}
+KWayland::Client::PlasmaWindowManagement * WaylandIntegration::WaylandIntegrationPrivate::plasmaWindowManagement()
+{
+ return m_windowManagement;
+}
+
QVariant WaylandIntegration::WaylandIntegrationPrivate::streams()
{
Stream stream;
@@ -636,11 +649,17 @@
m_registry = new KWayland::Client::Registry(this);
+#if SCREENCAST_ENABLED
connect(m_registry, &KWayland::Client::Registry::fakeInputAnnounced, this, [this] (quint32 name, quint32 version) {
m_fakeInput = m_registry->createFakeInput(name, version, this);
});
connect(m_registry, &KWayland::Client::Registry::outputAnnounced, this, &WaylandIntegrationPrivate::addOutput);
connect(m_registry, &KWayland::Client::Registry::outputRemoved, this, &WaylandIntegrationPrivate::removeOutput);
+#endif
+ connect(m_registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, this, [this] (quint32 name, quint32 version) {
+ m_windowManagement = m_registry->createPlasmaWindowManagement(name, version, this);
+ Q_EMIT waylandIntegration()->plasmaWindowManagementInitialized();
+ });
connect(m_registry, &KWayland::Client::Registry::interfacesAnnounced, this, [this] {
m_registryInitialized = true;
diff --git a/src/waylandintegration_p.h b/src/waylandintegration_p.h
--- a/src/waylandintegration_p.h
+++ b/src/waylandintegration_p.h
@@ -42,6 +42,8 @@
class RemoteAccessManager;
class RemoteBuffer;
class Output;
+ class PlasmaWindow;
+ class PlasmaWindowManagement;
}
}
@@ -80,6 +82,7 @@
void requestPointerMotionAbsolute(const QPointF &pos);
void requestPointerAxisDiscrete(Qt::Orientation axis, qreal delta);
+ KWayland::Client::PlasmaWindowManagement *plasmaWindowManagement();
QMap screens();
QVariant streams();
@@ -90,28 +93,29 @@
void setupRegistry();
private:
- bool m_eglInitialized;
- bool m_streamingEnabled;
+ bool m_eglInitialized = false;
+ bool m_streamingEnabled = false;
bool m_streamInput = false;
- bool m_registryInitialized;
- bool m_waylandAuthenticationRequested;
+ bool m_registryInitialized = false;
+ bool m_waylandAuthenticationRequested = false;
quint32 m_output;
QDateTime m_lastFrameTime;
- ScreenCastStream *m_stream;
+ ScreenCastStream *m_stream = nullptr;
- QThread *m_thread;
+ QThread *m_thread = nullptr;
QPoint m_streamedScreenPosition;
QMap m_outputMap;
QList m_bindOutputs;
- KWayland::Client::ConnectionThread *m_connection;
- KWayland::Client::EventQueue *m_queue;
- KWayland::Client::FakeInput *m_fakeInput;
- KWayland::Client::Registry *m_registry;
- KWayland::Client::RemoteAccessManager *m_remoteAccessManager;
+ KWayland::Client::ConnectionThread *m_connection = nullptr;
+ KWayland::Client::EventQueue *m_queue = nullptr;
+ KWayland::Client::FakeInput *m_fakeInput = nullptr;
+ KWayland::Client::Registry *m_registry = nullptr;
+ KWayland::Client::RemoteAccessManager *m_remoteAccessManager = nullptr;
+ KWayland::Client::PlasmaWindowManagement *m_windowManagement = nullptr;
qint32 m_drmFd = 0; // for GBM buffer mmap
gbm_device *m_gbmDevice = nullptr; // for passed GBM buffer retrieval