1 /*
2 SPDX-FileCopyrightText: 2016, 2019 Kai Uwe Broulik <kde@privat.broulik.de>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "smartlauncherbackend.h"
8
9 #include <QDBusConnection>
10 #include <QDBusMessage>
11 #include <QDBusServiceWatcher>
12 #include <QDebug>
13
14 #include <KConfigGroup>
15 #include <KService>
16 #include <KSharedConfig>
17
18 #include <algorithm>
19
20 #include <notificationmanager/settings.h>
21
22 using namespace SmartLauncher;
23 using namespace NotificationManager;
24
Backend(QObject * parent)25 Backend::Backend(QObject *parent)
26 : QObject(parent)
27 , m_watcher(new QDBusServiceWatcher(this))
28 , m_settings(new Settings(this))
29 {
30 setupUnity();
31
32 reload();
33 connect(m_settings, &Settings::settingsChanged, this, &Backend::reload);
34 }
35
36 Backend::~Backend() = default;
37
reload()38 void Backend::reload()
39 {
40 m_badgeBlacklist = m_settings->badgeBlacklistedApplications();
41
42 // Unity Launcher API operates on storage IDs ("foo.desktop"), whereas settings return desktop entries "foo"
43 std::transform(m_badgeBlacklist.begin(), m_badgeBlacklist.end(), m_badgeBlacklist.begin(), [](const QString &desktopEntry) -> QString {
44 return desktopEntry + QStringLiteral(".desktop");
45 });
46
47 setupApplicationJobs();
48
49 Q_EMIT reloadRequested(QString() /*all*/);
50 }
51
doNotDisturbMode() const52 bool Backend::doNotDisturbMode() const
53 {
54 return m_settings->notificationsInhibitedByApplication()
55 || (m_settings->notificationsInhibitedUntil().isValid() && m_settings->notificationsInhibitedUntil() > QDateTime::currentDateTimeUtc());
56 }
57
setupUnity()58 void Backend::setupUnity()
59 {
60 auto sessionBus = QDBusConnection::sessionBus();
61
62 if (!sessionBus.connect({},
63 {},
64 QStringLiteral("com.canonical.Unity.LauncherEntry"),
65 QStringLiteral("Update"),
66 this,
67 SLOT(update(QString, QMap<QString, QVariant>)))) {
68 qWarning() << "failed to register Update signal";
69 return;
70 }
71
72 if (!sessionBus.registerObject(QStringLiteral("/Unity"), this)) {
73 qWarning() << "Failed to register unity object";
74 return;
75 }
76
77 if (!sessionBus.registerService(QStringLiteral("com.canonical.Unity"))) {
78 qWarning() << "Failed to register unity service";
79 // In case an external process uses this (e.g. Latte Dock), let it just listen.
80 }
81
82 KConfigGroup grp(KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")), QStringLiteral("Unity Launcher Mapping"));
83
84 foreach (const QString &key, grp.keyList()) {
85 const QString &value = grp.readEntry(key, QString());
86 if (value.isEmpty()) {
87 continue;
88 }
89
90 m_unityMappingRules.insert(key, value);
91 }
92 }
93
setupApplicationJobs()94 void Backend::setupApplicationJobs()
95 {
96 if (m_settings->jobsInTaskManager() && !m_jobsModel) {
97 m_jobsModel = JobsModel::createJobsModel();
98 m_jobsModel->init();
99 } else if (!m_settings->jobsInTaskManager() && m_jobsModel) {
100 m_jobsModel = nullptr;
101 }
102 }
103
hasLauncher(const QString & storageId) const104 bool Backend::hasLauncher(const QString &storageId) const
105 {
106 return m_launchers.contains(storageId);
107 }
108
count(const QString & uri) const109 int Backend::count(const QString &uri) const
110 {
111 if (!m_settings->badgesInTaskManager() || doNotDisturbMode() || m_badgeBlacklist.contains(uri)) {
112 return 0;
113 }
114 return m_launchers.value(uri).count;
115 }
116
countVisible(const QString & uri) const117 bool Backend::countVisible(const QString &uri) const
118 {
119 if (!m_settings->badgesInTaskManager() || doNotDisturbMode() || m_badgeBlacklist.contains(uri)) {
120 return false;
121 }
122 return m_launchers.value(uri).countVisible;
123 }
124
progress(const QString & uri) const125 int Backend::progress(const QString &uri) const
126 {
127 if (!m_settings->jobsInTaskManager()) {
128 return 0;
129 }
130 return m_launchers.value(uri).progress;
131 }
132
progressVisible(const QString & uri) const133 bool Backend::progressVisible(const QString &uri) const
134 {
135 if (!m_settings->jobsInTaskManager()) {
136 return false;
137 }
138 return m_launchers.value(uri).progressVisible;
139 }
140
urgent(const QString & uri) const141 bool Backend::urgent(const QString &uri) const
142 {
143 return m_launchers.value(uri).urgent;
144 }
145
unityMappingRules() const146 QHash<QString, QString> Backend::unityMappingRules() const
147 {
148 return m_unityMappingRules;
149 }
150
update(const QString & uri,const QMap<QString,QVariant> & properties)151 void Backend::update(const QString &uri, const QMap<QString, QVariant> &properties)
152 {
153 Q_ASSERT(calledFromDBus());
154
155 QString storageId;
156
157 auto foundStorageId = m_launcherUrlToStorageId.constFind(uri);
158 if (foundStorageId == m_launcherUrlToStorageId.constEnd()) { // we don't know this one, register
159 // According to Unity Launcher API documentation applications *should* send along their
160 // desktop file name with application:// prefix
161 const QString applicationSchemePrefix = QStringLiteral("application://");
162
163 QString normalizedUri = uri;
164 if (normalizedUri.startsWith(applicationSchemePrefix)) {
165 normalizedUri = uri.mid(applicationSchemePrefix.length());
166 }
167
168 KService::Ptr service = KService::serviceByStorageId(normalizedUri);
169 if (!service) {
170 qWarning() << "Failed to find service for Unity Launcher" << uri;
171 return;
172 }
173
174 storageId = service->storageId();
175 m_launcherUrlToStorageId.insert(uri, storageId);
176
177 m_dbusServiceToLauncherUrl.insert(message().service(), uri);
178 m_watcher->addWatchedService(message().service());
179 } else {
180 storageId = *foundStorageId;
181 }
182
183 auto foundEntry = m_launchers.find(storageId);
184 if (foundEntry == m_launchers.end()) { // we don't have it yet, create a new Entry
185 Entry entry;
186 foundEntry = m_launchers.insert(storageId, entry);
187 }
188
189 auto propertiesEnd = properties.constEnd();
190
191 auto foundCount = properties.constFind(QStringLiteral("count"));
192 if (foundCount != propertiesEnd) {
193 qint64 newCount = foundCount->toLongLong();
194 // 2 billion unread emails ought to be enough for anybody
195 if (newCount < std::numeric_limits<int>::max()) {
196 int saneCount = static_cast<int>(newCount);
197 if (saneCount != foundEntry->count) {
198 foundEntry->count = saneCount;
199 Q_EMIT countChanged(storageId, saneCount);
200 }
201 }
202 }
203
204 updateLauncherProperty(storageId, properties, QStringLiteral("count"), &foundEntry->count, &Backend::count, &Backend::countChanged);
205 updateLauncherProperty(storageId,
206 properties,
207 QStringLiteral("count-visible"),
208 &foundEntry->countVisible,
209 &Backend::countVisible,
210 &Backend::countVisibleChanged);
211
212 // the API gives us progress as 0..1 double but we'll use percent to avoid unnecessary
213 // changes when it just changed a fraction of a percent, hence not using our fancy updateLauncherProperty method
214 auto foundProgress = properties.constFind(QStringLiteral("progress"));
215 if (foundProgress != propertiesEnd) {
216 const int oldSanitizedProgress = progress(storageId);
217
218 foundEntry->progress = qRound(foundProgress->toDouble() * 100);
219
220 const int newSanitizedProgress = progress(storageId);
221
222 if (oldSanitizedProgress != newSanitizedProgress) {
223 Q_EMIT progressChanged(storageId, newSanitizedProgress);
224 }
225 }
226
227 updateLauncherProperty(storageId,
228 properties,
229 QStringLiteral("progress-visible"),
230 &foundEntry->progressVisible,
231 &Backend::progressVisible,
232 &Backend::progressVisibleChanged);
233 updateLauncherProperty(storageId, properties, QStringLiteral("urgent"), &foundEntry->urgent, &Backend::urgent, &Backend::urgentChanged);
234 }
235
onServiceUnregistered(const QString & service)236 void Backend::onServiceUnregistered(const QString &service)
237 {
238 const QString &launcherUrl = m_dbusServiceToLauncherUrl.take(service);
239 if (launcherUrl.isEmpty()) {
240 return;
241 }
242
243 const QString &storageId = m_launcherUrlToStorageId.take(launcherUrl);
244 if (storageId.isEmpty()) {
245 return;
246 }
247
248 m_launchers.remove(storageId);
249 Q_EMIT launcherRemoved(storageId);
250 }
251