File indexing completed on 2024-04-28 16:55:44

0001 /*
0002  * SPDX-FileCopyrightText: 2020 Red Hat Inc
0003  * SPDX-License-Identifier: LGPL-2.0-or-later
0004  *
0005  * SPDX-FileCopyrightText: 2020 Jan Grulich <jgrulich@redhat.com>
0006  */
0007 
0008 #include "background.h"
0009 #include "background_debug.h"
0010 #include "utils.h"
0011 #include "waylandintegration.h"
0012 
0013 #include <QDBusConnection>
0014 #include <QDBusContext>
0015 #include <QDBusMessage>
0016 #include <QDBusMetaType>
0017 
0018 #include <QDir>
0019 #include <QFile>
0020 #include <QMessageBox>
0021 #include <QPushButton>
0022 #include <QSettings>
0023 #include <QStandardPaths>
0024 
0025 #include <KConfigGroup>
0026 #include <KDesktopFile>
0027 #include <KLocalizedString>
0028 #include <KNotification>
0029 #include <KService>
0030 #include <KShell>
0031 
0032 #include <KWayland/Client/plasmawindowmanagement.h>
0033 
0034 BackgroundPortal::BackgroundPortal(QObject *parent, QDBusContext *context)
0035     : QDBusAbstractAdaptor(parent)
0036     , m_context(context)
0037 {
0038     connect(WaylandIntegration::waylandIntegration(), &WaylandIntegration::WaylandIntegration::plasmaWindowManagementInitialized, this, [=]() {
0039         connect(WaylandIntegration::plasmaWindowManagement(),
0040                 &KWayland::Client::PlasmaWindowManagement::windowCreated,
0041                 this,
0042                 [this](KWayland::Client::PlasmaWindow *window) {
0043                     addWindow(window);
0044                 });
0045 
0046         m_windows = WaylandIntegration::plasmaWindowManagement()->windows();
0047         for (KWayland::Client::PlasmaWindow *window : qAsConst(m_windows)) {
0048             addWindow(window);
0049         }
0050     });
0051 }
0052 
0053 BackgroundPortal::~BackgroundPortal()
0054 {
0055 }
0056 
0057 QVariantMap BackgroundPortal::GetAppState()
0058 {
0059     qCDebug(XdgDesktopPortalKdeBackground) << "GetAppState called: no parameters";
0060     return m_appStates;
0061 }
0062 
0063 uint BackgroundPortal::NotifyBackground(const QDBusObjectPath &handle, const QString &app_id, const QString &name, QVariantMap &results)
0064 {
0065     Q_UNUSED(results);
0066 
0067     qCDebug(XdgDesktopPortalKdeBackground) << "NotifyBackground called with parameters:";
0068     qCDebug(XdgDesktopPortalKdeBackground) << "    handle: " << handle.path();
0069     qCDebug(XdgDesktopPortalKdeBackground) << "    app_id: " << app_id;
0070     qCDebug(XdgDesktopPortalKdeBackground) << "    name: " << name;
0071 
0072     // If KWayland::Client::PlasmaWindowManagement hasn't been created, we would be notified about every
0073     // application, which is not what we want. This will be mostly happening on X11 session.
0074     if (!WaylandIntegration::plasmaWindowManagement()) {
0075         results.insert(QStringLiteral("result"), static_cast<uint>(BackgroundPortal::AllowOnce));
0076         return 0;
0077     }
0078 
0079     const KService::Ptr app = KService::serviceByDesktopName(app_id);
0080 
0081     QObject *obj = QObject::parent();
0082 
0083     if (!obj) {
0084         qCWarning(XdgDesktopPortalKdeBackground) << "Failed to get dbus context";
0085         return 2;
0086     }
0087 
0088     QDBusMessage message = m_context->message();
0089 
0090     const QString appName = app ? app->name() : app_id;
0091     if (m_backgroundAppWarned.contains(app_id)) {
0092         const QVariantMap map = {
0093             {QStringLiteral("result"), static_cast<uint>(BackgroundPortal::AllowOnce)},
0094         };
0095         QDBusMessage reply = message.createReply({uint(0), map});
0096         if (!QDBusConnection::sessionBus().send(reply)) {
0097             qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response";
0098         }
0099 
0100         return 0;
0101     }
0102 
0103     KNotification *notify = new KNotification(QStringLiteral("notification"), KNotification::Persistent | KNotification::DefaultEvent, this);
0104     notify->setTitle(i18n("Background activity"));
0105     notify->setText(i18n("%1 is running in the background.", appName));
0106     notify->setActions({i18n("Find out more")});
0107     notify->setProperty("activated", false);
0108 
0109     message.setDelayedReply(true);
0110 
0111     connect(notify, QOverload<uint>::of(&KNotification::activated), this, [=](uint action) {
0112         if (action != 1) {
0113             return;
0114         }
0115         notify->setProperty("activated", true);
0116 
0117         const QString title = i18n("%1 is running in the background", appName);
0118         const QString text = i18n(
0119             "This might be for a legitimate reason, but the application has not provided one."
0120             "\n\nNote that forcing an application to quit might cause data loss.");
0121         QMessageBox *messageBox = new QMessageBox(QMessageBox::Question, title, text);
0122         messageBox->addButton(i18n("Force quit"), QMessageBox::RejectRole);
0123         messageBox->addButton(i18n("Allow"), QMessageBox::AcceptRole);
0124 
0125         messageBox->show();
0126 
0127         connect(messageBox, &QMessageBox::accepted, this, [message, messageBox]() {
0128             const QVariantMap map = {{QStringLiteral("result"), static_cast<uint>(BackgroundPortal::Allow)}};
0129             QDBusMessage reply = message.createReply({static_cast<uint>(0), map});
0130             if (!QDBusConnection::sessionBus().send(reply)) {
0131                 qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response";
0132             }
0133             messageBox->deleteLater();
0134         });
0135         connect(messageBox, &QMessageBox::rejected, this, [message, messageBox]() {
0136             const QVariantMap map = {{QStringLiteral("result"), static_cast<uint>(BackgroundPortal::Forbid)}};
0137             QDBusMessage reply = message.createReply({static_cast<uint>(0), map});
0138             if (!QDBusConnection::sessionBus().send(reply)) {
0139                 qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response";
0140             }
0141             messageBox->deleteLater();
0142         });
0143     });
0144     connect(notify, &KNotification::closed, this, [=]() {
0145         if (notify->property("activated").toBool()) {
0146             return;
0147         }
0148 
0149         QVariantMap map;
0150         map.insert(QStringLiteral("result"), static_cast<uint>(BackgroundPortal::AllowOnce));
0151         QDBusMessage reply = message.createReply({static_cast<uint>(0), map});
0152         if (!QDBusConnection::sessionBus().send(reply)) {
0153             qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response";
0154         }
0155     });
0156 
0157     notify->sendEvent();
0158 
0159     Q_ASSERT(!m_backgroundAppWarned.contains(app_id));
0160     connect(notify, &KNotification::closed, this, [this, app_id] {
0161         m_backgroundAppWarned.remove(app_id);
0162     });
0163     m_backgroundAppWarned.insert(app_id);
0164 
0165     return 0;
0166 }
0167 
0168 bool BackgroundPortal::EnableAutostart(const QString &app_id, bool enable, const QStringList &commandline, uint flags)
0169 {
0170     qCDebug(XdgDesktopPortalKdeBackground) << "EnableAutostart called with parameters:";
0171     qCDebug(XdgDesktopPortalKdeBackground) << "    app_id: " << app_id;
0172     qCDebug(XdgDesktopPortalKdeBackground) << "    enable: " << enable;
0173     qCDebug(XdgDesktopPortalKdeBackground) << "    commandline: " << commandline;
0174     qCDebug(XdgDesktopPortalKdeBackground) << "    flags: " << flags;
0175 
0176     const QString fileName = app_id + QStringLiteral(".desktop");
0177     const QString directory = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/autostart/");
0178     const QString fullPath = directory + fileName;
0179     const AutostartFlags autostartFlags = static_cast<AutostartFlags>(flags);
0180 
0181     if (!enable) {
0182         QFile file(fullPath);
0183         if (!file.remove()) {
0184             qCDebug(XdgDesktopPortalKdeBackground) << "Failed to remove " << fileName << " to disable autostart.";
0185         }
0186         return false;
0187     }
0188 
0189     QDir dir(directory);
0190     if (!dir.mkpath(dir.absolutePath())) {
0191         qCDebug(XdgDesktopPortalKdeBackground) << "Failed to create autostart directory.";
0192         return false;
0193     }
0194 
0195     KDesktopFile desktopFile(fullPath);
0196     KConfigGroup desktopEntryConfigGroup = desktopFile.desktopGroup();
0197     desktopEntryConfigGroup.writeEntry(QStringLiteral("Type"), QStringLiteral("Application"));
0198     desktopEntryConfigGroup.writeEntry(QStringLiteral("Name"), app_id);
0199     desktopEntryConfigGroup.writeEntry(QStringLiteral("Exec"), KShell::joinArgs(commandline));
0200     if (autostartFlags.testFlag(AutostartFlag::Activatable)) {
0201         desktopEntryConfigGroup.writeEntry(QStringLiteral("DBusActivatable"), true);
0202     }
0203     desktopEntryConfigGroup.writeEntry(QStringLiteral("X-Flatpak"), app_id);
0204 
0205     return true;
0206 }
0207 
0208 void BackgroundPortal::addWindow(KWayland::Client::PlasmaWindow *window)
0209 {
0210     const QString appId = window->appId();
0211     const bool isActive = window->isActive();
0212     m_appStates[appId] = QVariant::fromValue<uint>(isActive ? Active : Running);
0213 
0214     connect(window, &KWayland::Client::PlasmaWindow::activeChanged, this, [this, window]() {
0215         setActiveWindow(window->appId(), window->isActive());
0216     });
0217     connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [this, window]() {
0218         uint windows = 0;
0219         const QString appId = window->appId();
0220         const auto plasmaWindows = WaylandIntegration::plasmaWindowManagement()->windows();
0221         for (KWayland::Client::PlasmaWindow *otherWindow : plasmaWindows) {
0222             if (otherWindow->appId() == appId && otherWindow->uuid() != window->uuid()) {
0223                 windows++;
0224             }
0225         }
0226 
0227         if (!windows) {
0228             m_appStates.remove(appId);
0229             Q_EMIT RunningApplicationsChanged();
0230         }
0231     });
0232 
0233     Q_EMIT RunningApplicationsChanged();
0234 }
0235 
0236 void BackgroundPortal::setActiveWindow(const QString &appId, bool active)
0237 {
0238     m_appStates[appId] = QVariant::fromValue<uint>(active ? Active : Running);
0239 
0240     Q_EMIT RunningApplicationsChanged();
0241 }