Changeset View
Changeset View
Standalone View
Standalone View
applets/taskmanager/plugin/smartlaunchers/smartlauncherbackend.cpp
1 | /*************************************************************************** | 1 | /*************************************************************************** | ||
---|---|---|---|---|---|
2 | * Copyright (C) 2016 Kai Uwe Broulik <kde@privat.broulik.de> * | 2 | * Copyright (C) 2016, 2019 Kai Uwe Broulik <kde@privat.broulik.de> * | ||
3 | * * | 3 | * * | ||
4 | * This program is free software; you can redistribute it and/or modify * | 4 | * This program is free software; you can redistribute it and/or modify * | ||
5 | * it under the terms of the GNU General Public License as published by * | 5 | * it under the terms of the GNU General Public License as published by * | ||
6 | * the Free Software Foundation; either version 2 of the License, or * | 6 | * the Free Software Foundation; either version 2 of the License, or * | ||
7 | * (at your option) any later version. * | 7 | * (at your option) any later version. * | ||
8 | * * | 8 | * * | ||
9 | * This program is distributed in the hope that it will be useful, * | 9 | * This program is distributed in the hope that it will be useful, * | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | ||
12 | * GNU General Public License for more details. * | 12 | * GNU General Public License for more details. * | ||
13 | * * | 13 | * * | ||
14 | * You should have received a copy of the GNU General Public License * | 14 | * You should have received a copy of the GNU General Public License * | ||
15 | * along with this program; if not, write to the * | 15 | * along with this program; if not, write to the * | ||
16 | * Free Software Foundation, Inc., * | 16 | * Free Software Foundation, Inc., * | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * | 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * | ||
18 | ***************************************************************************/ | 18 | ***************************************************************************/ | ||
19 | 19 | | |||
20 | #include "smartlauncherbackend.h" | 20 | #include "smartlauncherbackend.h" | ||
21 | 21 | | |||
22 | #include <QDBusConnection> | 22 | #include <QDBusConnection> | ||
23 | #include <QDBusMessage> | 23 | #include <QDBusMessage> | ||
24 | #include <QDBusServiceWatcher> | 24 | #include <QDBusServiceWatcher> | ||
25 | #include <QDebug> | 25 | #include <QDebug> | ||
26 | 26 | | |||
27 | #include <Plasma/DataEngineConsumer> | 27 | #include <KConfigGroup> | ||
28 | #include <Plasma/DataEngine> | | |||
29 | | ||||
30 | #include <KSharedConfig> | 28 | #include <KSharedConfig> | ||
31 | #include <KService> | 29 | #include <KService> | ||
32 | 30 | | |||
31 | #include <algorithm> | ||||
32 | | ||||
33 | #include <notificationmanager/jobsmodel.h> | ||||
34 | #include <notificationmanager/settings.h> | ||||
35 | | ||||
33 | using namespace SmartLauncher; | 36 | using namespace SmartLauncher; | ||
37 | using namespace NotificationManager; | ||||
34 | 38 | | |||
35 | Backend::Backend(QObject *parent) | 39 | Backend::Backend(QObject *parent) | ||
36 | : QObject(parent) | 40 | : QObject(parent) | ||
37 | , m_watcher(new QDBusServiceWatcher(this)) | 41 | , m_watcher(new QDBusServiceWatcher(this)) | ||
38 | , m_dataEngineConsumer(new Plasma::DataEngineConsumer) | 42 | , m_settings(new Settings(this)) | ||
39 | , m_dataEngine(m_dataEngineConsumer->dataEngine(QStringLiteral("applicationjobs"))) | | |||
40 | { | 43 | { | ||
41 | m_available = setupUnity(); | 44 | setupUnity(); | ||
42 | m_available = setupApplicationJobs() || m_available; | 45 | | ||
46 | reload(); | ||||
47 | connect(m_settings, &Settings::settingsChanged, this, &Backend::reload); | ||||
43 | } | 48 | } | ||
44 | 49 | | |||
45 | Backend::~Backend() | 50 | Backend::~Backend() = default; | ||
51 | | ||||
52 | void Backend::reload() | ||||
46 | { | 53 | { | ||
47 | delete m_dataEngineConsumer; | 54 | m_badgeBlacklist = m_settings->badgeBlacklistedApplications(); | ||
55 | | ||||
56 | // Unity Launcher API operates on storage IDs ("foo.desktop"), whereas settings return desktop entries "foo" | ||||
57 | std::transform(m_badgeBlacklist.begin(), m_badgeBlacklist.end(), m_badgeBlacklist.begin(), [](const QString &desktopEntry) { | ||||
58 | return desktopEntry + QStringLiteral(".desktop"); | ||||
59 | }); | ||||
60 | | ||||
61 | setupApplicationJobs(); | ||||
62 | | ||||
63 | emit reloadRequested(QString() /*all*/); | ||||
64 | } | ||||
65 | | ||||
66 | bool Backend::doNotDisturbMode() const | ||||
67 | { | ||||
68 | return m_settings->notificationsInhibitedByApplication() | ||||
69 | || (m_settings->notificationsInhibitedUntil().isValid() && m_settings->notificationsInhibitedUntil() > QDateTime::currentDateTimeUtc()); | ||||
48 | } | 70 | } | ||
49 | 71 | | |||
50 | bool Backend::setupUnity() | 72 | void Backend::setupUnity() | ||
51 | { | 73 | { | ||
52 | auto sessionBus = QDBusConnection::sessionBus(); | 74 | auto sessionBus = QDBusConnection::sessionBus(); | ||
53 | 75 | | |||
54 | if (!sessionBus.connect({}, {}, QStringLiteral("com.canonical.Unity.LauncherEntry"), | 76 | if (!sessionBus.connect({}, {}, QStringLiteral("com.canonical.Unity.LauncherEntry"), | ||
55 | QStringLiteral("Update"), this, SLOT(update(QString,QMap<QString,QVariant>)))) { | 77 | QStringLiteral("Update"), this, SLOT(update(QString,QMap<QString,QVariant>)))) { | ||
56 | qWarning() << "failed to register Update signal"; | 78 | qWarning() << "failed to register Update signal"; | ||
57 | return false; | 79 | return; | ||
58 | } | 80 | } | ||
59 | 81 | | |||
60 | if (!sessionBus.registerObject(QStringLiteral("/Unity"), this)) { | 82 | if (!sessionBus.registerObject(QStringLiteral("/Unity"), this)) { | ||
61 | qWarning() << "Failed to register unity object"; | 83 | qWarning() << "Failed to register unity object"; | ||
62 | return false; | 84 | return; | ||
63 | } | 85 | } | ||
64 | 86 | | |||
65 | if (!sessionBus.registerService(QStringLiteral("com.canonical.Unity"))) { | 87 | if (!sessionBus.registerService(QStringLiteral("com.canonical.Unity"))) { | ||
66 | qWarning() << "Failed to register unity service"; | 88 | qWarning() << "Failed to register unity service"; | ||
67 | return false; | 89 | // In case an external process uses this (e.g. Latte Dock), let it just listen. | ||
68 | } | 90 | } | ||
69 | 91 | | |||
70 | KConfigGroup grp(KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")), QStringLiteral("Unity Launcher Mapping")); | 92 | KConfigGroup grp(KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")), QStringLiteral("Unity Launcher Mapping")); | ||
71 | 93 | | |||
72 | foreach (const QString &key, grp.keyList()) { | 94 | foreach (const QString &key, grp.keyList()) { | ||
73 | const QString &value = grp.readEntry(key, QString()); | 95 | const QString &value = grp.readEntry(key, QString()); | ||
74 | if (value.isEmpty()) { | 96 | if (value.isEmpty()) { | ||
75 | continue; | 97 | continue; | ||
76 | } | 98 | } | ||
77 | 99 | | |||
78 | m_unityMappingRules.insert(key, value); | 100 | m_unityMappingRules.insert(key, value); | ||
79 | } | 101 | } | ||
80 | | ||||
81 | return true; | | |||
82 | } | 102 | } | ||
83 | 103 | | |||
84 | bool Backend::setupApplicationJobs() | 104 | void Backend::setupApplicationJobs() | ||
85 | { | 105 | { | ||
86 | if (!m_dataEngine->isValid()) { | 106 | if (m_settings->jobsInTaskManager() && !m_jobsModel) { | ||
87 | qWarning() << "Failed to setup application jobs, data engine is not valid"; | 107 | m_jobsModel = JobsModel::createJobsModel(); | ||
88 | return false; | 108 | m_jobsModel->init(); | ||
109 | } else if (!m_settings->jobsInTaskManager() && m_jobsModel) { | ||||
110 | m_jobsModel = nullptr; | ||||
89 | } | 111 | } | ||
90 | | ||||
91 | const QStringList &sources = m_dataEngine->sources(); | | |||
92 | for (const QString &source : sources) { | | |||
93 | onApplicationJobAdded(source); | | |||
94 | } | | |||
95 | | ||||
96 | connect(m_dataEngine, &Plasma::DataEngine::sourceAdded, this, &Backend::onApplicationJobAdded); | | |||
97 | connect(m_dataEngine, &Plasma::DataEngine::sourceRemoved, this, &Backend::onApplicationJobRemoved); | | |||
98 | | ||||
99 | return true; | | |||
100 | } | | |||
101 | | ||||
102 | bool Backend::available() const | | |||
103 | { | | |||
104 | return m_available; | | |||
105 | } | 112 | } | ||
106 | 113 | | |||
107 | bool Backend::hasLauncher(const QString &storageId) const | 114 | bool Backend::hasLauncher(const QString &storageId) const | ||
108 | { | 115 | { | ||
109 | return m_launchers.contains(storageId); | 116 | return m_launchers.contains(storageId); | ||
110 | } | 117 | } | ||
111 | 118 | | |||
112 | int Backend::count(const QString &uri) const | 119 | int Backend::count(const QString &uri) const | ||
113 | { | 120 | { | ||
121 | if (!m_settings->badgesInTaskManager() | ||||
122 | || doNotDisturbMode() | ||||
123 | || m_badgeBlacklist.contains(uri)) { | ||||
124 | return 0; | ||||
125 | } | ||||
114 | return m_launchers.value(uri).count; | 126 | return m_launchers.value(uri).count; | ||
115 | } | 127 | } | ||
116 | 128 | | |||
117 | bool Backend::countVisible(const QString &uri) const | 129 | bool Backend::countVisible(const QString &uri) const | ||
118 | { | 130 | { | ||
131 | if (!m_settings->badgesInTaskManager() | ||||
132 | || doNotDisturbMode() | ||||
133 | || m_badgeBlacklist.contains(uri)) { | ||||
134 | return false; | ||||
135 | } | ||||
119 | return m_launchers.value(uri).countVisible; | 136 | return m_launchers.value(uri).countVisible; | ||
120 | } | 137 | } | ||
121 | 138 | | |||
122 | int Backend::progress(const QString &uri) const | 139 | int Backend::progress(const QString &uri) const | ||
123 | { | 140 | { | ||
141 | if (!m_settings->jobsInTaskManager()) { | ||||
142 | return 0; | ||||
143 | } | ||||
124 | return m_launchers.value(uri).progress; | 144 | return m_launchers.value(uri).progress; | ||
125 | } | 145 | } | ||
126 | 146 | | |||
127 | bool Backend::progressVisible(const QString &uri) const | 147 | bool Backend::progressVisible(const QString &uri) const | ||
128 | { | 148 | { | ||
149 | if (!m_settings->jobsInTaskManager()) { | ||||
150 | return false; | ||||
151 | } | ||||
129 | return m_launchers.value(uri).progressVisible; | 152 | return m_launchers.value(uri).progressVisible; | ||
130 | } | 153 | } | ||
131 | 154 | | |||
132 | bool Backend::urgent(const QString &uri) const | 155 | bool Backend::urgent(const QString &uri) const | ||
133 | { | 156 | { | ||
134 | return m_launchers.value(uri).urgent; | 157 | return m_launchers.value(uri).urgent; | ||
135 | } | 158 | } | ||
136 | 159 | | |||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Line(s) | 209 | if (newCount < std::numeric_limits<int>::max()) { | |||
187 | int saneCount = static_cast<int>(newCount); | 210 | int saneCount = static_cast<int>(newCount); | ||
188 | if (saneCount != foundEntry->count) { | 211 | if (saneCount != foundEntry->count) { | ||
189 | foundEntry->count = saneCount; | 212 | foundEntry->count = saneCount; | ||
190 | emit countChanged(storageId, saneCount); | 213 | emit countChanged(storageId, saneCount); | ||
191 | } | 214 | } | ||
192 | } | 215 | } | ||
193 | } | 216 | } | ||
194 | 217 | | |||
195 | updateLauncherProperty(storageId, properties, QStringLiteral("count"), &foundEntry->count, &Backend::countChanged); | 218 | updateLauncherProperty(storageId, properties, QStringLiteral("count"), &foundEntry->count, &Backend::count, &Backend::countChanged); | ||
196 | updateLauncherProperty(storageId, properties, QStringLiteral("count-visible"), &foundEntry->countVisible, &Backend::countVisibleChanged); | 219 | updateLauncherProperty(storageId, properties, QStringLiteral("count-visible"), &foundEntry->countVisible, &Backend::countVisible, &Backend::countVisibleChanged); | ||
197 | 220 | | |||
198 | // the API gives us progress as 0..1 double but we'll use percent to avoid unnecessary | 221 | // the API gives us progress as 0..1 double but we'll use percent to avoid unnecessary | ||
199 | // changes when it just changed a fraction of a percent, hence not using our fancy updateLauncherProperty method | 222 | // changes when it just changed a fraction of a percent, hence not using our fancy updateLauncherProperty method | ||
200 | auto foundProgress = properties.constFind(QStringLiteral("progress")); | 223 | auto foundProgress = properties.constFind(QStringLiteral("progress")); | ||
201 | if (foundProgress != propertiesEnd) { | 224 | if (foundProgress != propertiesEnd) { | ||
202 | int newProgress = qRound(foundProgress->toDouble() * 100); | 225 | const int oldSanitizedProgress = progress(storageId); | ||
203 | if (newProgress != foundEntry->progress) { | 226 | | ||
204 | foundEntry->progress = newProgress; | 227 | foundEntry->progress = qRound(foundProgress->toDouble() * 100); | ||
205 | emit progressChanged(storageId, newProgress); | 228 | | ||
229 | const int newSanitizedProgress = progress(storageId); | ||||
230 | | ||||
231 | if (oldSanitizedProgress != newSanitizedProgress) { | ||||
232 | emit progressChanged(storageId, newSanitizedProgress); | ||||
206 | } | 233 | } | ||
207 | } | 234 | } | ||
208 | 235 | | |||
209 | updateLauncherProperty(storageId, properties, QStringLiteral("progress-visible"), &foundEntry->progressVisible, &Backend::progressVisibleChanged); | 236 | updateLauncherProperty(storageId, properties, QStringLiteral("progress-visible"), &foundEntry->progressVisible, &Backend::progressVisible, &Backend::progressVisibleChanged); | ||
210 | updateLauncherProperty(storageId, properties, QStringLiteral("urgent"), &foundEntry->urgent, &Backend::urgentChanged); | 237 | updateLauncherProperty(storageId, properties, QStringLiteral("urgent"), &foundEntry->urgent, &Backend::urgent, &Backend::urgentChanged); | ||
211 | } | 238 | } | ||
212 | 239 | | |||
213 | void Backend::onServiceUnregistered(const QString &service) | 240 | void Backend::onServiceUnregistered(const QString &service) | ||
214 | { | 241 | { | ||
215 | const QString &launcherUrl = m_dbusServiceToLauncherUrl.take(service); | 242 | const QString &launcherUrl = m_dbusServiceToLauncherUrl.take(service); | ||
216 | if (launcherUrl.isEmpty()) { | 243 | if (launcherUrl.isEmpty()) { | ||
217 | return; | 244 | return; | ||
218 | } | 245 | } | ||
219 | 246 | | |||
220 | const QString &storageId = m_launcherUrlToStorageId.take(launcherUrl); | 247 | const QString &storageId = m_launcherUrlToStorageId.take(launcherUrl); | ||
221 | if (storageId.isEmpty()) { | 248 | if (storageId.isEmpty()) { | ||
222 | return; | 249 | return; | ||
223 | } | 250 | } | ||
224 | 251 | | |||
225 | m_launchers.remove(storageId); | 252 | m_launchers.remove(storageId); | ||
226 | emit launcherRemoved(storageId); | 253 | emit launcherRemoved(storageId); | ||
227 | } | 254 | } | ||
228 | | ||||
229 | void Backend::onApplicationJobAdded(const QString &source) | | |||
230 | { | | |||
231 | m_dataEngine->connectSource(source, this); | | |||
232 | } | | |||
233 | | ||||
234 | void Backend::onApplicationJobRemoved(const QString &source) | | |||
235 | { | | |||
236 | m_dataEngine->disconnectSource(source, this); | | |||
237 | | ||||
238 | const QString &storageId = m_dataSourceToStorageId.take(source); | | |||
239 | if (storageId.isEmpty()) { | | |||
240 | return; | | |||
241 | } | | |||
242 | | ||||
243 | // remove job, calculate new percentage, or remove launcher if gone altogether | | |||
244 | auto &jobs = m_storageIdToJobs[storageId]; | | |||
245 | jobs.removeOne(source); | | |||
246 | if (jobs.isEmpty()) { | | |||
247 | m_storageIdToJobs.remove(storageId); | | |||
248 | } | | |||
249 | | ||||
250 | m_jobProgress.remove(source); | | |||
251 | | ||||
252 | auto foundEntry = m_launchers.find(storageId); | | |||
253 | if (foundEntry == m_launchers.end()) { | | |||
254 | qWarning() << "Cannot remove application job" << source << "as we don't know" << storageId; | | |||
255 | return; | | |||
256 | } | | |||
257 | | ||||
258 | updateApplicationJobPercent(storageId, &*foundEntry); | | |||
259 | | ||||
260 | if (!foundEntry->progressVisible && !foundEntry->progress) { | | |||
261 | // no progress anymore whatsoever, remove entire launcher | | |||
262 | m_launchers.remove(storageId); | | |||
263 | emit launcherRemoved(storageId); | | |||
264 | } | | |||
265 | } | | |||
266 | | ||||
267 | void Backend::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) | | |||
268 | { | | |||
269 | QString storageId; | | |||
270 | | ||||
271 | auto foundStorageId = m_dataSourceToStorageId.constFind(sourceName); | | |||
272 | if (foundStorageId == m_dataSourceToStorageId.constEnd()) { // we don't know this one, register | | |||
273 | QString appName = data.value(QStringLiteral("appName")).toString(); | | |||
274 | if (appName.isEmpty()) { | | |||
275 | qWarning() << "Application jobs got update for" << sourceName << "without app name"; | | |||
276 | return; | | |||
277 | } | | |||
278 | | ||||
279 | KService::Ptr service = KService::serviceByStorageId(appName); | | |||
280 | if (!service) { | | |||
281 | appName.prepend(QLatin1String("org.kde.")); | | |||
282 | // HACK try to find a service with org.kde. notation | | |||
283 | service = KService::serviceByStorageId(appName); | | |||
284 | if (!service) { | | |||
285 | qWarning() << "Could not find service for job" << sourceName << "with app name" << appName; | | |||
286 | return; | | |||
287 | } | | |||
288 | } | | |||
289 | | ||||
290 | storageId = service->storageId(); | | |||
291 | m_dataSourceToStorageId.insert(sourceName, storageId); | | |||
292 | } else { | | |||
293 | storageId = *foundStorageId; | | |||
294 | } | | |||
295 | | ||||
296 | auto foundEntry = m_launchers.find(storageId); | | |||
297 | if (foundEntry == m_launchers.end()) { // we don't have it yet, create new Entry | | |||
298 | Entry entry; | | |||
299 | foundEntry = m_launchers.insert(storageId, entry); | | |||
300 | } | | |||
301 | | ||||
302 | int percent = data.value(QStringLiteral("percentage"), 0).toInt(); | | |||
303 | | ||||
304 | // setup everything and calculate new percentage | | |||
305 | auto &jobs = m_storageIdToJobs[storageId]; | | |||
306 | if (!jobs.contains(sourceName)) { | | |||
307 | jobs.append(sourceName); | | |||
308 | } | | |||
309 | | ||||
310 | m_jobProgress.insert(sourceName, percent); // insert() overrides if exist | | |||
311 | | ||||
312 | updateApplicationJobPercent(storageId, &*foundEntry); | | |||
313 | } | | |||
314 | | ||||
315 | void Backend::updateApplicationJobPercent(const QString &storageId, Entry *entry) | | |||
316 | { | | |||
317 | // basically get all jobs for the given storageId and calculate an average progress | | |||
318 | | ||||
319 | const auto &jobs = m_storageIdToJobs.value(storageId); | | |||
320 | qreal jobCount = jobs.count(); | | |||
321 | | ||||
322 | int totalProgress = 0; | | |||
323 | for (const QString &job : jobs) { | | |||
324 | totalProgress += m_jobProgress.value(job, 0); | | |||
325 | } | | |||
326 | | ||||
327 | int progress = 0; | | |||
328 | if (jobCount > 0) { | | |||
329 | progress = qRound(totalProgress / jobCount); | | |||
330 | } | | |||
331 | | ||||
332 | bool visible = (jobCount > 0); | | |||
333 | | ||||
334 | if (entry->count != jobCount) { | | |||
335 | entry->count = jobCount; | | |||
336 | emit countChanged(storageId, jobCount); | | |||
337 | } | | |||
338 | | ||||
339 | if (entry->countVisible != visible) { | | |||
340 | entry->countVisible = visible; | | |||
341 | emit countVisibleChanged(storageId, visible); | | |||
342 | } | | |||
343 | | ||||
344 | if (entry->progress != progress) { | | |||
345 | entry->progress = progress; | | |||
346 | emit progressChanged(storageId, progress); | | |||
347 | } | | |||
348 | | ||||
349 | if (entry->progressVisible != visible) { | | |||
350 | entry->progressVisible = visible; | | |||
351 | emit progressVisibleChanged(storageId, visible); | | |||
352 | } | | |||
353 | } | |