Changeset View
Standalone View
src/background.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | * Copyright © 2020 Red Hat, Inc | ||||
3 | * This program is free software; you can redistribute it and/or | ||||
4 | * modify it under the terms of the GNU Lesser General Public | ||||
5 | * License as published by the Free Software Foundation; either | ||||
6 | * version 2 of the License, or (at your option) any later version. | ||||
7 | * | ||||
8 | * This library is distributed in the hope that it will be useful, | ||||
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
11 | * Lesser General Public License for more details. | ||||
12 | * | ||||
13 | * You should have received a copy of the GNU Lesser General Public | ||||
14 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||||
15 | * | ||||
16 | * Authors: | ||||
17 | * Jan Grulich <jgrulich@redhat.com> | ||||
18 | */ | ||||
19 | | ||||
20 | #include "background.h" | ||||
21 | #include "utils.h" | ||||
22 | #include "waylandintegration.h" | ||||
23 | | ||||
24 | #include <QDBusMetaType> | ||||
25 | #include <QDBusContext> | ||||
26 | #include <QDBusMessage> | ||||
27 | #include <QDBusConnection> | ||||
28 | | ||||
29 | #include <QDir> | ||||
30 | #include <QFile> | ||||
31 | #include <QLoggingCategory> | ||||
32 | #include <QMessageBox> | ||||
33 | #include <QPushButton> | ||||
34 | #include <QStandardPaths> | ||||
35 | #include <QSettings> | ||||
36 | | ||||
37 | #include <KConfigGroup> | ||||
38 | #include <KDesktopFile> | ||||
39 | #include <KLocalizedString> | ||||
40 | #include <KNotification> | ||||
41 | #include <KShell> | ||||
42 | | ||||
43 | #include <KWayland/Client/plasmawindowmanagement.h> | ||||
44 | | ||||
45 | Q_LOGGING_CATEGORY(XdgDesktopPortalKdeBackground, "xdp-kde-background") | ||||
46 | | ||||
47 | BackgroundPortal::BackgroundPortal(QObject *parent) | ||||
48 | : QDBusAbstractAdaptor(parent) | ||||
49 | { | ||||
50 | connect(WaylandIntegration::waylandIntegration(), &WaylandIntegration::WaylandIntegration::plasmaWindowManagementInitialized, this, [=] () { | ||||
51 | connect(WaylandIntegration::plasmaWindowManagement(), &KWayland::Client::PlasmaWindowManagement::windowCreated, this, [this] (KWayland::Client::PlasmaWindow *window) { | ||||
52 | addWindow(window); | ||||
53 | }); | ||||
54 | | ||||
55 | m_windows = WaylandIntegration::plasmaWindowManagement()->windows(); | ||||
56 | for (KWayland::Client::PlasmaWindow *window : m_windows) { | ||||
57 | addWindow(window); | ||||
58 | } | ||||
59 | }); | ||||
60 | } | ||||
61 | | ||||
62 | BackgroundPortal::~BackgroundPortal() | ||||
63 | { | ||||
64 | } | ||||
65 | | ||||
66 | QVariantMap BackgroundPortal::GetAppState() | ||||
67 | { | ||||
68 | qCDebug(XdgDesktopPortalKdeBackground) << "GetAppState called: no parameters"; | ||||
69 | return m_appStates; | ||||
70 | } | ||||
apol: I don't really understand why we're exposing this. Is it for contained apps? | |||||
This is actually not API exposed to the clients, this is purely for xdg-desktop-portal needs, while the client interface (xdg-desktop-portal) has limited stuff available, see https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.Background.xml. jgrulich: This is actually not API exposed to the clients, this is purely for xdg-desktop-portal needs… | |||||
71 | | ||||
72 | uint BackgroundPortal::NotifyBackground(const QDBusObjectPath &handle, | ||||
73 | const QString &app_id, | ||||
74 | const QString &name, | ||||
75 | QVariantMap &results) | ||||
76 | { | ||||
77 | Q_UNUSED(results); | ||||
78 | | ||||
79 | qCDebug(XdgDesktopPortalKdeBackground) << "NotifyBackground called with parameters:"; | ||||
80 | qCDebug(XdgDesktopPortalKdeBackground) << " handle: " << handle.path(); | ||||
81 | qCDebug(XdgDesktopPortalKdeBackground) << " app_id: " << app_id; | ||||
82 | qCDebug(XdgDesktopPortalKdeBackground) << " name: " << name; | ||||
83 | | ||||
84 | KNotification *notify = new KNotification(QStringLiteral("notification"), KNotification::Persistent | KNotification::DefaultEvent, this); | ||||
85 | notify->setTitle(i18n("Background activity")); | ||||
86 | notify->setText(i18n("%1 is running in the background.", app_id)); | ||||
87 | notify->setActions({i18n("Find out more")}); | ||||
88 | notify->setProperty("activated", false); | ||||
89 | | ||||
90 | QObject *obj = QObject::parent(); | ||||
91 | | ||||
92 | if (!obj) { | ||||
93 | qCWarning(XdgDesktopPortalKdeBackground) << "Failed to get dbus context"; | ||||
94 | return 2; | ||||
95 | } | ||||
96 | | ||||
97 | void *ptr = obj->qt_metacast("QDBusContext"); | ||||
98 | QDBusContext *q_ptr = reinterpret_cast<QDBusContext *>(ptr); | ||||
99 | | ||||
100 | if (!q_ptr) { | ||||
101 | qCWarning(XdgDesktopPortalKdeBackground) << "Failed to get dbus context"; | ||||
102 | return 2; | ||||
103 | } | ||||
104 | | ||||
105 | QDBusMessage reply; | ||||
106 | QDBusMessage message = q_ptr->message(); | ||||
107 | | ||||
108 | message.setDelayedReply(true); | ||||
109 | reply = message.createReply(); | ||||
110 | QDBusConnection::sessionBus().send(reply); | ||||
111 | | ||||
112 | connect(notify, QOverload<uint>::of(&KNotification::activated), this, [=] (uint action) { | ||||
113 | if (action != 1) { | ||||
114 | return; | ||||
115 | } | ||||
116 | notify->setProperty("activated", true); | ||||
117 | | ||||
118 | const QString title = i18n("%1 is running in the background", app_id); | ||||
119 | const QString text = i18n("This might be for a legitimate reason, but the application has not provided one." | ||||
120 | "\n\nNote that forcing an application to quit might cause data loss."); | ||||
121 | QMessageBox messageBox(QMessageBox::Question, title, text); | ||||
122 | QPushButton *quitButton = messageBox.addButton(i18n("Force quit"), QMessageBox::RejectRole); | ||||
123 | QPushButton *allowButton = messageBox.addButton(i18n("Allow"), QMessageBox::AcceptRole); | ||||
124 | messageBox.exec(); | ||||
125 | | ||||
126 | BackgroundPortal::NotifyResult result = BackgroundPortal::Ignore; | ||||
127 | if (messageBox.clickedButton() == quitButton) { | ||||
128 | result = BackgroundPortal::Forbid; | ||||
129 | } else if (messageBox.clickedButton() == allowButton) { | ||||
130 | result = BackgroundPortal::Allow; | ||||
131 | } | ||||
132 | | ||||
133 | const QVariantMap map = { {QStringLiteral("result"), static_cast<uint>(result)} }; | ||||
134 | QDBusMessage reply = message.createReply({0, map}); | ||||
const QVariantMap map = { {QStringLiteral("result"), static_cast<uint>(result)} }; apol: const QVariantMap map = { {QStringLiteral("result"), static_cast<uint>(result)} }; | |||||
135 | if (!QDBusConnection::sessionBus().send(reply)) { | ||||
136 | qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response"; | ||||
137 | } | ||||
138 | }); | ||||
139 | connect(notify, &KNotification::closed, this, [=] () { | ||||
140 | if (notify->property("activated").toBool()) { | ||||
141 | return; | ||||
142 | } | ||||
143 | | ||||
144 | QVariantMap map; | ||||
145 | map.insert(QStringLiteral("result"), static_cast<uint>(BackgroundPortal::Ignore)); | ||||
146 | QDBusMessage reply = message.createReply({0, map}); | ||||
147 | if (!QDBusConnection::sessionBus().send(reply)) { | ||||
148 | qCWarning(XdgDesktopPortalKdeBackground) << "Failed to send response"; | ||||
149 | } | ||||
150 | }); | ||||
151 | | ||||
152 | notify->sendEvent(); | ||||
153 | | ||||
154 | return 0; | ||||
155 | } | ||||
156 | | ||||
157 | bool BackgroundPortal::EnableAutostart(const QString &app_id, | ||||
158 | bool enable, | ||||
159 | const QStringList &commandline, | ||||
160 | uint flags) | ||||
161 | { | ||||
162 | qCDebug(XdgDesktopPortalKdeBackground) << "EnableAutostart called with parameters:"; | ||||
163 | qCDebug(XdgDesktopPortalKdeBackground) << " app_id: " << app_id; | ||||
164 | qCDebug(XdgDesktopPortalKdeBackground) << " enable: " << enable; | ||||
165 | qCDebug(XdgDesktopPortalKdeBackground) << " commandline: " << commandline; | ||||
From the docs Commandline to use add when autostarting at login. If this is not specified, the Exec line from the desktop file will be used. I can't see us doing that last part davidedmundson: From the docs
Commandline to use add when autostarting at login.
If… | |||||
The commandline is actually the Exec line, it's just named that way from the portal specification. You can see below: desktopEntryConfigGroup.writeEntry(QStringLiteral("Exec"), KShell::joinArgs(commandline)); jgrulich: The commandline is actually the Exec line, it's just named that way from the portal… | |||||
166 | qCDebug(XdgDesktopPortalKdeBackground) << " flags: " << flags; | ||||
167 | | ||||
168 | const QString fileName = app_id + QStringLiteral(".desktop"); | ||||
169 | const QString directory = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/autostart/"); | ||||
170 | const QString fullPath = directory + fileName; | ||||
171 | const AutostartFlags autostartFlags = static_cast<AutostartFlags>(flags); | ||||
172 | | ||||
173 | if (!enable) { | ||||
174 | QFile file(fullPath); | ||||
175 | if (!file.remove()) { | ||||
176 | qCDebug(XdgDesktopPortalKdeBackground) << "Failed to remove " << fileName << " to disable autostart."; | ||||
177 | } | ||||
178 | return false; | ||||
179 | } | ||||
180 | | ||||
181 | QDir dir(directory); | ||||
182 | if (!dir.mkpath(dir.absolutePath())) { | ||||
183 | qCDebug(XdgDesktopPortalKdeBackground) << "Failed to create autostart directory."; | ||||
184 | return false; | ||||
185 | } | ||||
186 | | ||||
187 | KDesktopFile desktopFile(fullPath); | ||||
188 | KConfigGroup desktopEntryConfigGroup = desktopFile.desktopGroup(); | ||||
189 | desktopEntryConfigGroup.writeEntry(QStringLiteral("Type"), QStringLiteral("Application")); | ||||
190 | desktopEntryConfigGroup.writeEntry(QStringLiteral("Name"), app_id); | ||||
191 | desktopEntryConfigGroup.writeEntry(QStringLiteral("Exec"), KShell::joinArgs(commandline)); | ||||
192 | if (autostartFlags.testFlag(AutostartFlag::Activatable)) { | ||||
193 | desktopEntryConfigGroup.writeEntry(QStringLiteral("DBusActivatable"), true); | ||||
194 | } | ||||
195 | desktopEntryConfigGroup.writeEntry(QStringLiteral("X-Flatpak"), app_id); | ||||
apol: Will dbus look in autostart though? | |||||
Doesn't need to? I think it just specifies that the desktop file should ignore the exec line and start it through DBus (assuming correct dbus service file is installed etc.). Or am I wrong? jgrulich: Doesn't need to? I think it just specifies that the desktop file should ignore the exec line… | |||||
apol: Ah, alright. | |||||
You are right. We don't support it though. davidedmundson: >Doesn't need to? I think it just specifies that the desktop file should ignore the exec line… | |||||
196 | | ||||
197 | return true; | ||||
198 | } | ||||
199 | | ||||
200 | void BackgroundPortal::addWindow(KWayland::Client::PlasmaWindow *window) | ||||
201 | { | ||||
202 | const QString appId = window->appId(); | ||||
203 | const bool isActive = window->isActive(); | ||||
204 | m_appStates[appId] = QVariant::fromValue<uint>(isActive ? Active : Running); | ||||
205 | | ||||
206 | connect(window, &KWayland::Client::PlasmaWindow::activeChanged, this, [this, window] () { | ||||
207 | setActiveWindow(window->appId(), window->isActive()); | ||||
208 | }); | ||||
apol: context should be [this] | |||||
209 | connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [this, window] () { | ||||
210 | uint windows = 0; | ||||
211 | const QString appId = window->appId(); | ||||
apol: [this] | |||||
212 | for (KWayland::Client::PlasmaWindow *otherWindow : WaylandIntegration::plasmaWindowManagement()->windows()) { | ||||
213 | if (otherWindow->appId() == appId && otherWindow->internalId() != window->internalId()) { | ||||
214 | windows++; | ||||
215 | } | ||||
216 | } | ||||
217 | | ||||
218 | if (!windows) { | ||||
219 | m_appStates.remove(appId); | ||||
220 | Q_EMIT RunningApplicationsChanged(); | ||||
221 | } | ||||
222 | }); | ||||
223 | | ||||
224 | Q_EMIT RunningApplicationsChanged(); | ||||
225 | } | ||||
226 | | ||||
227 | void BackgroundPortal::setActiveWindow(const QString &appId, bool active) | ||||
228 | { | ||||
229 | m_appStates[appId] = QVariant::fromValue<uint>(active ? Active : Running); | ||||
230 | | ||||
231 | Q_EMIT RunningApplicationsChanged(); | ||||
232 | } |
I don't really understand why we're exposing this. Is it for contained apps?