Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 0cefb5e1 authored by Jan Grulich's avatar Jan Grulich

Add background portal

Summary:
Implements Background portal, providing APIS related to applications  running in the background.

Implemented according to: https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.impl.portal.Background.xml

Test Plan: Tested with manual invocations over DBus and checking what I return back through dbus-monitor.

Reviewers: #plasma, davidedmundson, apol

Reviewed By: apol

Subscribers: apol, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D28214
parent b8f31759
[portal]
DBusName=org.freedesktop.impl.portal.desktop.kde
Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.Account;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.Account;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
[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
......@@ -9,6 +9,7 @@ set(xdg_desktop_portal_kde_SRCS
account.cpp
appchooser.cpp
appchooserdialog.cpp
background.cpp
desktopportal.cpp
email.cpp
filechooser.cpp
......
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Jan Grulich <jgrulich@redhat.com>
*/
#include "background.h"
#include "utils.h"
#include "waylandintegration.h"
#include <QDBusMetaType>
#include <QDBusContext>
#include <QDBusMessage>
#include <QDBusConnection>
#include <QDir>
#include <QFile>
#include <QLoggingCategory>
#include <QMessageBox>
#include <QPushButton>
#include <QStandardPaths>
#include <QSettings>
#include <KConfigGroup>
#include <KDesktopFile>
#include <KLocalizedString>
#include <KNotification>
#include <KShell>
#include <KWayland/Client/plasmawindowmanagement.h>
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, [this] (KWayland::Client::PlasmaWindow *window) {
addWindow(window);
});
m_windows = WaylandIntegration::plasmaWindowManagement()->windows();
for (KWayland::Client::PlasmaWindow *window : m_windows) {
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")});
notify->setProperty("activated", false);
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<QDBusContext *>(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);
connect(notify, QOverload<uint>::of(&KNotification::activated), this, [=] (uint action) {
if (action != 1) {
return;
}
notify->setProperty("activated", 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;
}
const QVariantMap map = { {QStringLiteral("result"), static_cast<uint>(result)} };
QDBusMessage reply = message.createReply({0, map});
if (!QDBusConnection::sessionBus().send(reply)) {
qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response";
}
});
connect(notify, &KNotification::closed, this, [=] () {
if (notify->property("activated").toBool()) {
return;
}
QVariantMap map;
map.insert(QStringLiteral("result"), static_cast<uint>(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<AutostartFlags>(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<uint>(isActive ? Active : Running);
connect(window, &KWayland::Client::PlasmaWindow::activeChanged, this, [this, window] () {
setActiveWindow(window->appId(), window->isActive());
});
connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [this, window] () {
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<uint>(active ? Active : Running);
Q_EMIT RunningApplicationsChanged();
}
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Jan Grulich <jgrulich@redhat.com>
*/
#ifndef XDG_DESKTOP_PORTAL_KDE_BACKGROUND_H
#define XDG_DESKTOP_PORTAL_KDE_BACKGROUND_H
#include <QDBusAbstractAdaptor>
#include <QDBusObjectPath>
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<KWayland::Client::PlasmaWindow*> m_windows;
QVariantMap m_appStates;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(BackgroundPortal::AutostartFlags)
#endif // XDG_DESKTOP_PORTAL_KDE_BACKGROUND_H
......@@ -38,12 +38,13 @@ DesktopPortal::DesktopPortal(QObject *parent)
{
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();
}
}
......
......@@ -27,6 +27,7 @@
#include "access.h"
#include "account.h"
#include "appchooser.h"
#include "background.h"
#include "email.h"
#include "filechooser.h"
#include "inhibit.h"
......@@ -35,10 +36,10 @@
#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
{
......@@ -51,6 +52,7 @@ private:
AccessPortal *m_access;
AccountPortal *m_account;
AppChooserPortal *m_appChooser;
BackgroundPortal *m_background;
EmailPortal *m_email;
FileChooserPortal *m_fileChooser;
InhibitPortal *m_inhibit;
......
......@@ -41,6 +41,7 @@
#include <KWayland/Client/registry.h>
#include <KWayland/Client/output.h>
#include <KWayland/Client/remote_access.h>
#include <KWayland/Client/plasmawindowmanagement.h>
// system
#include <fcntl.h>
......@@ -57,7 +58,9 @@ void WaylandIntegration::authenticate()
void WaylandIntegration::init()
{
#if SCREENCAST_ENABLED
globalWaylandIntegration->initDrm();
#endif
globalWaylandIntegration->initWayland();
}
......@@ -121,6 +124,11 @@ WaylandIntegration::EGLStruct WaylandIntegration::egl()
return globalWaylandIntegration->egl();
}
KWayland::Client::PlasmaWindowManagement * WaylandIntegration::plasmaWindowManagement()
{
return globalWaylandIntegration->plasmaWindowManagement();
}
QMap<quint32, WaylandIntegration::WaylandOutput> WaylandIntegration::screens()
{
return globalWaylandIntegration->screens();
......@@ -406,6 +414,11 @@ QMap<quint32, WaylandIntegration::WaylandOutput> WaylandIntegration::WaylandInte
return m_outputMap;
}
KWayland::Client::PlasmaWindowManagement * WaylandIntegration::WaylandIntegrationPrivate::plasmaWindowManagement()
{
return m_windowManagement;
}
QVariant WaylandIntegration::WaylandIntegrationPrivate::streams()
{
Stream stream;
......@@ -616,11 +629,17 @@ void WaylandIntegration::WaylandIntegrationPrivate::setupRegistry()
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;
......
......@@ -31,6 +31,12 @@
#include <epoxy/egl.h>
#include <epoxy/gl.h>
namespace KWayland {
namespace Client {
class PlasmaWindowManagement;
}
}
namespace WaylandIntegration
{
......@@ -86,6 +92,7 @@ class WaylandIntegration : public QObject
Q_OBJECT
Q_SIGNALS:
void newBuffer(uint8_t *screenData);
void plasmaWindowManagementInitialized();
};
const char * formatGLError(GLenum err);
......@@ -109,6 +116,7 @@ Q_SIGNALS:
EGLStruct egl();
KWayland::Client::PlasmaWindowManagement *plasmaWindowManagement();
QMap<quint32, WaylandOutput> screens();
QVariant streams();
......
......@@ -37,6 +37,8 @@ namespace KWayland {
class RemoteAccessManager;
class RemoteBuffer;
class Output;
class PlasmaWindow;
class PlasmaWindowManagement;
}
}
......@@ -77,6 +79,7 @@ public:
void requestKeyboardKeycode(int keycode, bool state);
EGLStruct egl();
KWayland::Client::PlasmaWindowManagement *plasmaWindowManagement();
QMap<quint32, WaylandOutput> screens();
QVariant streams();
......@@ -87,28 +90,29 @@ protected Q_SLOTS:
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<quint32, WaylandOutput> m_outputMap;
QList<KWayland::Client::Output*> 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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment