File indexing completed on 2025-01-26 05:06:18
0001 /* 0002 SPDX-FileCopyrightText: 2016, 2019 Kai Uwe Broulik <kde@privat.broulik.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "smartlauncherbackend.h" 0008 0009 #include <QDBusConnection> 0010 #include <QDBusMessage> 0011 #include <QDBusServiceWatcher> 0012 #include <QDebug> 0013 0014 #include <KConfigGroup> 0015 #include <KService> 0016 #include <KSharedConfig> 0017 0018 #include <algorithm> 0019 0020 #include <settings.h> 0021 0022 using namespace SmartLauncher; 0023 using namespace NotificationManager; 0024 0025 Backend::Backend(QObject *parent) 0026 : QObject(parent) 0027 , m_watcher(new QDBusServiceWatcher(this)) 0028 , m_jobsModel(nullptr) 0029 , m_settings(new Settings(this)) 0030 { 0031 setupUnity(); 0032 0033 reload(); 0034 connect(m_settings, &Settings::settingsChanged, this, &Backend::reload); 0035 } 0036 0037 Backend::~Backend() = default; 0038 0039 void Backend::reload() 0040 { 0041 m_badgeBlacklist = m_settings->badgeBlacklistedApplications(); 0042 0043 // Unity Launcher API operates on storage IDs ("foo.desktop"), whereas settings return desktop entries "foo" 0044 std::transform(m_badgeBlacklist.begin(), m_badgeBlacklist.end(), m_badgeBlacklist.begin(), [](const QString &desktopEntry) -> QString { 0045 return desktopEntry + QStringLiteral(".desktop"); 0046 }); 0047 0048 if (!m_jobsModel) { 0049 m_jobsModel = JobsModel::createJobsModel(); 0050 m_jobsModel->init(); 0051 } 0052 0053 Q_EMIT reloadRequested(QString() /*all*/); 0054 } 0055 0056 bool Backend::doNotDisturbMode() const 0057 { 0058 return m_settings->notificationsInhibitedByApplication() 0059 || (m_settings->notificationsInhibitedUntil().isValid() && m_settings->notificationsInhibitedUntil() > QDateTime::currentDateTimeUtc()); 0060 } 0061 0062 void Backend::setupUnity() 0063 { 0064 auto sessionBus = QDBusConnection::sessionBus(); 0065 0066 if (!sessionBus.connect({}, 0067 {}, 0068 QStringLiteral("com.canonical.Unity.LauncherEntry"), 0069 QStringLiteral("Update"), 0070 this, 0071 SLOT(update(QString, QMap<QString, QVariant>)))) { 0072 qWarning() << "failed to register Update signal"; 0073 return; 0074 } 0075 0076 if (!sessionBus.registerObject(QStringLiteral("/Unity"), this)) { 0077 qWarning() << "Failed to register unity object"; 0078 return; 0079 } 0080 0081 if (!sessionBus.registerService(QStringLiteral("com.canonical.Unity"))) { 0082 qWarning() << "Failed to register unity service"; 0083 // In case an external process uses this (e.g. Latte Dock), let it just listen. 0084 } 0085 0086 KConfigGroup grp(KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")), QStringLiteral("Unity Launcher Mapping")); 0087 0088 const QStringList keys = grp.keyList(); 0089 for (const QString &key : keys) { 0090 const QString &value = grp.readEntry(key, QString()); 0091 if (value.isEmpty()) { 0092 continue; 0093 } 0094 0095 m_unityMappingRules.insert(key, value); 0096 } 0097 } 0098 0099 bool Backend::hasLauncher(const QString &storageId) const 0100 { 0101 return m_launchers.contains(storageId); 0102 } 0103 0104 int Backend::count(const QString &uri) const 0105 { 0106 if (!m_settings->badgesInTaskManager() || doNotDisturbMode() || m_badgeBlacklist.contains(uri)) { 0107 return 0; 0108 } 0109 return m_launchers.value(uri).count; 0110 } 0111 0112 bool Backend::countVisible(const QString &uri) const 0113 { 0114 if (!m_settings->badgesInTaskManager() || doNotDisturbMode() || m_badgeBlacklist.contains(uri)) { 0115 return false; 0116 } 0117 return m_launchers.value(uri).countVisible; 0118 } 0119 0120 int Backend::progress(const QString &uri) const 0121 { 0122 return m_launchers.value(uri).progress; 0123 } 0124 0125 bool Backend::progressVisible(const QString &uri) const 0126 { 0127 return m_launchers.value(uri).progressVisible; 0128 } 0129 0130 bool Backend::urgent(const QString &uri) const 0131 { 0132 return m_launchers.value(uri).urgent; 0133 } 0134 0135 QHash<QString, QString> Backend::unityMappingRules() const 0136 { 0137 return m_unityMappingRules; 0138 } 0139 0140 void Backend::update(const QString &uri, const QMap<QString, QVariant> &properties) 0141 { 0142 Q_ASSERT(calledFromDBus()); 0143 0144 QString storageId; 0145 0146 auto foundStorageId = m_launcherUrlToStorageId.constFind(uri); 0147 if (foundStorageId == m_launcherUrlToStorageId.constEnd()) { // we don't know this one, register 0148 // According to Unity Launcher API documentation applications *should* send along their 0149 // desktop file name with application:// prefix 0150 const QString applicationSchemePrefix = QStringLiteral("application://"); 0151 0152 QString normalizedUri = uri; 0153 if (normalizedUri.startsWith(applicationSchemePrefix)) { 0154 normalizedUri = uri.mid(applicationSchemePrefix.length()); 0155 } 0156 0157 KService::Ptr service = KService::serviceByStorageId(normalizedUri); 0158 if (!service) { 0159 qWarning() << "Failed to find service for Unity Launcher" << uri; 0160 return; 0161 } 0162 0163 storageId = service->storageId(); 0164 m_launcherUrlToStorageId.insert(uri, storageId); 0165 0166 m_dbusServiceToLauncherUrl.insert(message().service(), uri); 0167 m_watcher->addWatchedService(message().service()); 0168 } else { 0169 storageId = *foundStorageId; 0170 } 0171 0172 auto foundEntry = m_launchers.find(storageId); 0173 if (foundEntry == m_launchers.end()) { // we don't have it yet, create a new Entry 0174 Entry entry; 0175 foundEntry = m_launchers.insert(storageId, entry); 0176 } 0177 0178 auto propertiesEnd = properties.constEnd(); 0179 0180 auto foundCount = properties.constFind(QStringLiteral("count")); 0181 if (foundCount != propertiesEnd) { 0182 qint64 newCount = foundCount->toLongLong(); 0183 // 2 billion unread emails ought to be enough for anybody 0184 if (newCount < std::numeric_limits<int>::max()) { 0185 int saneCount = static_cast<int>(newCount); 0186 if (saneCount != foundEntry->count) { 0187 foundEntry->count = saneCount; 0188 Q_EMIT countChanged(storageId, saneCount); 0189 } 0190 } 0191 } 0192 0193 updateLauncherProperty(storageId, properties, QStringLiteral("count"), &foundEntry->count, &Backend::count, &Backend::countChanged); 0194 updateLauncherProperty(storageId, 0195 properties, 0196 QStringLiteral("count-visible"), 0197 &foundEntry->countVisible, 0198 &Backend::countVisible, 0199 &Backend::countVisibleChanged); 0200 0201 // the API gives us progress as 0..1 double but we'll use percent to avoid unnecessary 0202 // changes when it just changed a fraction of a percent, hence not using our fancy updateLauncherProperty method 0203 auto foundProgress = properties.constFind(QStringLiteral("progress")); 0204 if (foundProgress != propertiesEnd) { 0205 const int oldSanitizedProgress = progress(storageId); 0206 0207 foundEntry->progress = qRound(foundProgress->toDouble() * 100); 0208 0209 const int newSanitizedProgress = progress(storageId); 0210 0211 if (oldSanitizedProgress != newSanitizedProgress) { 0212 Q_EMIT progressChanged(storageId, newSanitizedProgress); 0213 } 0214 } 0215 0216 updateLauncherProperty(storageId, 0217 properties, 0218 QStringLiteral("progress-visible"), 0219 &foundEntry->progressVisible, 0220 &Backend::progressVisible, 0221 &Backend::progressVisibleChanged); 0222 updateLauncherProperty(storageId, properties, QStringLiteral("urgent"), &foundEntry->urgent, &Backend::urgent, &Backend::urgentChanged); 0223 } 0224 0225 void Backend::onServiceUnregistered(const QString &service) 0226 { 0227 const QString &launcherUrl = m_dbusServiceToLauncherUrl.take(service); 0228 if (launcherUrl.isEmpty()) { 0229 return; 0230 } 0231 0232 const QString &storageId = m_launcherUrlToStorageId.take(launcherUrl); 0233 if (storageId.isEmpty()) { 0234 return; 0235 } 0236 0237 m_launchers.remove(storageId); 0238 Q_EMIT launcherRemoved(storageId); 0239 }