File indexing completed on 2024-04-28 05:36:49
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 "ksharedconfig.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, [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 : std::as_const(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 QDBusMessage message = m_context->message(); 0080 auto allow = [message]() { 0081 const QVariantMap map = {{QStringLiteral("result"), static_cast<uint>(BackgroundPortal::Allow)}}; 0082 QDBusMessage reply = message.createReply({static_cast<uint>(0), map}); 0083 if (!QDBusConnection::sessionBus().send(reply)) { 0084 qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response"; 0085 } 0086 }; 0087 0088 auto allowOnce = [message]() { 0089 const QVariantMap map = {{QStringLiteral("result"), static_cast<uint>(BackgroundPortal::AllowOnce)}}; 0090 QDBusMessage reply = message.createReply({static_cast<uint>(0), map}); 0091 if (!QDBusConnection::sessionBus().send(reply)) { 0092 qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response"; 0093 } 0094 }; 0095 0096 auto forbid = [message]() { 0097 const QVariantMap map = {{QStringLiteral("result"), static_cast<uint>(BackgroundPortal::Forbid)}}; 0098 QDBusMessage reply = message.createReply({static_cast<uint>(0), map}); 0099 if (!QDBusConnection::sessionBus().send(reply)) { 0100 qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response"; 0101 } 0102 }; 0103 0104 if (KSharedConfig::openConfig()->group("Background").readEntry("NotifyBackgroundApps", true)) { 0105 allowOnce(); 0106 return 0; 0107 } 0108 0109 const KService::Ptr app = KService::serviceByDesktopName(app_id); 0110 0111 QObject *obj = QObject::parent(); 0112 0113 if (!obj) { 0114 qCWarning(XdgDesktopPortalKdeBackground) << "Failed to get dbus context"; 0115 return 2; 0116 } 0117 0118 const QString appName = app ? app->name() : app_id; 0119 if (m_backgroundAppWarned.contains(app_id)) { 0120 const QVariantMap map = { 0121 {QStringLiteral("result"), static_cast<uint>(BackgroundPortal::AllowOnce)}, 0122 }; 0123 QDBusMessage reply = message.createReply({uint(0), map}); 0124 if (!QDBusConnection::sessionBus().send(reply)) { 0125 qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response"; 0126 } 0127 0128 return 0; 0129 } 0130 0131 KNotification *notify = new KNotification(QStringLiteral("notification"), KNotification::Persistent | KNotification::DefaultEvent, this); 0132 notify->setTitle(i18n("Background Activity")); 0133 notify->setText( 0134 i18nc("@info %1 is the name of an application", 0135 "%1 wants to remain running when it has no visible windows. If you forbid this, the application will quit when its last window is closed.", 0136 appName)); 0137 notify->setProperty("activated", false); 0138 0139 message.setDelayedReply(true); 0140 0141 auto allowAction = notify->addAction(i18nc("@action:button Allow the app to keep running with no open windows", "Allow")); 0142 0143 connect(allowAction, &KNotificationAction::activated, this, [allow, notify] { 0144 allow(); 0145 notify->setProperty("activated", true); 0146 }); 0147 0148 auto forbidAction = notify->addAction(i18nc("@action:button Don't allow the app to keep running without any open windows", "Forbid")); 0149 0150 connect(forbidAction, &KNotificationAction::activated, this, [this, appName, allow, forbid, notify] { 0151 const QString title = 0152 i18nc("@title title of a dialog to confirm whether to allow an app to remain running with no visible windows", "Background App Usage"); 0153 const QString text = i18nc("%1 is the name of an application", 0154 "Note that this will force %1 to quit when its last window is closed. This could cause data loss if the application has " 0155 "any unsaved changes when it happens.", 0156 appName); 0157 auto messageBox = new QMessageBox(QMessageBox::Question, title, text); 0158 messageBox->addButton(i18nc("@action:button Allow the app to keep running with no open windows", "Allow"), QMessageBox::AcceptRole); 0159 messageBox->addButton(i18nc("@action:button Don't allow the app to keep running without any open windows", "Forbid Anyway"), QMessageBox::RejectRole); 0160 messageBox->show(); 0161 0162 connect(messageBox, &QMessageBox::accepted, this, [messageBox, allow]() { 0163 allow(); 0164 messageBox->deleteLater(); 0165 }); 0166 connect(messageBox, &QMessageBox::rejected, this, [messageBox, forbid]() { 0167 forbid(); 0168 messageBox->deleteLater(); 0169 }); 0170 0171 notify->setProperty("activated", true); 0172 }); 0173 0174 connect(notify, &KNotification::closed, this, [notify, allowOnce]() { 0175 if (notify->property("activated").toBool()) { 0176 return; 0177 } 0178 0179 allowOnce(); 0180 }); 0181 0182 notify->sendEvent(); 0183 0184 Q_ASSERT(!m_backgroundAppWarned.contains(app_id)); 0185 connect(notify, &KNotification::closed, this, [this, app_id] { 0186 m_backgroundAppWarned.remove(app_id); 0187 }); 0188 m_backgroundAppWarned.insert(app_id); 0189 0190 return 0; 0191 } 0192 0193 bool BackgroundPortal::EnableAutostart(const QString &app_id, bool enable, const QStringList &commandline, uint flags) 0194 { 0195 qCDebug(XdgDesktopPortalKdeBackground) << "EnableAutostart called with parameters:"; 0196 qCDebug(XdgDesktopPortalKdeBackground) << " app_id: " << app_id; 0197 qCDebug(XdgDesktopPortalKdeBackground) << " enable: " << enable; 0198 qCDebug(XdgDesktopPortalKdeBackground) << " commandline: " << commandline; 0199 qCDebug(XdgDesktopPortalKdeBackground) << " flags: " << flags; 0200 0201 const QString fileName = app_id + QStringLiteral(".desktop"); 0202 const QString directory = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/autostart/"); 0203 const QString fullPath = directory + fileName; 0204 const AutostartFlags autostartFlags = static_cast<AutostartFlags>(flags); 0205 0206 if (!enable) { 0207 QFile file(fullPath); 0208 if (!file.remove()) { 0209 qCDebug(XdgDesktopPortalKdeBackground) << "Failed to remove " << fileName << " to disable autostart."; 0210 } 0211 return false; 0212 } 0213 0214 QDir dir(directory); 0215 if (!dir.mkpath(dir.absolutePath())) { 0216 qCDebug(XdgDesktopPortalKdeBackground) << "Failed to create autostart directory."; 0217 return false; 0218 } 0219 0220 KDesktopFile desktopFile(fullPath); 0221 KConfigGroup desktopEntryConfigGroup = desktopFile.desktopGroup(); 0222 desktopEntryConfigGroup.writeEntry(QStringLiteral("Type"), QStringLiteral("Application")); 0223 desktopEntryConfigGroup.writeEntry(QStringLiteral("Name"), app_id); 0224 desktopEntryConfigGroup.writeEntry(QStringLiteral("Exec"), KShell::joinArgs(commandline)); 0225 if (autostartFlags.testFlag(AutostartFlag::Activatable)) { 0226 desktopEntryConfigGroup.writeEntry(QStringLiteral("DBusActivatable"), true); 0227 } 0228 desktopEntryConfigGroup.writeEntry(QStringLiteral("X-Flatpak"), app_id); 0229 0230 return true; 0231 } 0232 0233 void BackgroundPortal::addWindow(KWayland::Client::PlasmaWindow *window) 0234 { 0235 const QString appId = window->appId(); 0236 const bool isActive = window->isActive(); 0237 m_appStates[appId] = QVariant::fromValue<uint>(isActive ? Active : Running); 0238 0239 connect(window, &KWayland::Client::PlasmaWindow::activeChanged, this, [this, window]() { 0240 setActiveWindow(window->appId(), window->isActive()); 0241 }); 0242 connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [this, window]() { 0243 uint windows = 0; 0244 const QString appId = window->appId(); 0245 const auto plasmaWindows = WaylandIntegration::plasmaWindowManagement()->windows(); 0246 for (KWayland::Client::PlasmaWindow *otherWindow : plasmaWindows) { 0247 if (otherWindow->appId() == appId && otherWindow->uuid() != window->uuid()) { 0248 windows++; 0249 } 0250 } 0251 0252 if (!windows) { 0253 m_appStates.remove(appId); 0254 Q_EMIT RunningApplicationsChanged(); 0255 } 0256 }); 0257 0258 Q_EMIT RunningApplicationsChanged(); 0259 } 0260 0261 void BackgroundPortal::setActiveWindow(const QString &appId, bool active) 0262 { 0263 m_appStates[appId] = QVariant::fromValue<uint>(active ? Active : Running); 0264 0265 Q_EMIT RunningApplicationsChanged(); 0266 }