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 }