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 }