File indexing completed on 2024-05-12 17:08:49

0001 /*
0002     SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
0003     SPDX-FileCopyrightText: 2020 Konrad Materka <materka@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "dbusserviceobserver.h"
0009 #include "debug.h"
0010 
0011 #include "systemtraysettings.h"
0012 
0013 #include <KPluginMetaData>
0014 
0015 #include <QDBusConnection>
0016 #include <QDBusConnectionInterface>
0017 #include <QDBusServiceWatcher>
0018 
0019 DBusServiceObserver::DBusServiceObserver(const QPointer<SystemTraySettings> &settings, QObject *parent)
0020     : QObject(parent)
0021     , m_settings(settings)
0022     , m_sessionServiceWatcher(new QDBusServiceWatcher(this))
0023     , m_systemServiceWatcher(new QDBusServiceWatcher(this))
0024 {
0025     m_sessionServiceWatcher->setConnection(QDBusConnection::sessionBus());
0026     m_systemServiceWatcher->setConnection(QDBusConnection::systemBus());
0027 
0028     connect(m_settings, &SystemTraySettings::enabledPluginsChanged, this, &DBusServiceObserver::initDBusActivatables);
0029 
0030     // Watch for new services
0031     connect(m_sessionServiceWatcher, &QDBusServiceWatcher::serviceRegistered, this, [this](const QString &serviceName) {
0032         if (!m_dbusSessionServiceNamesFetched) {
0033             return;
0034         }
0035         serviceRegistered(serviceName);
0036     });
0037     connect(m_sessionServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &serviceName) {
0038         if (!m_dbusSessionServiceNamesFetched) {
0039             return;
0040         }
0041         serviceUnregistered(serviceName);
0042     });
0043     connect(m_systemServiceWatcher, &QDBusServiceWatcher::serviceRegistered, this, [this](const QString &serviceName) {
0044         if (!m_dbusSystemServiceNamesFetched) {
0045             return;
0046         }
0047         serviceRegistered(serviceName);
0048     });
0049     connect(m_systemServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &serviceName) {
0050         if (!m_dbusSystemServiceNamesFetched) {
0051             return;
0052         }
0053         serviceUnregistered(serviceName);
0054     });
0055 }
0056 
0057 void DBusServiceObserver::registerPlugin(const KPluginMetaData &pluginMetaData)
0058 {
0059     const QString dbusactivation = pluginMetaData.value(QStringLiteral("X-Plasma-DBusActivationService"));
0060     if (!dbusactivation.isEmpty()) {
0061         qCDebug(SYSTEM_TRAY) << "Found DBus-able Applet: " << pluginMetaData.pluginId() << dbusactivation;
0062         QRegExp rx(dbusactivation);
0063         rx.setPatternSyntax(QRegExp::Wildcard);
0064         m_dbusActivatableTasks[pluginMetaData.pluginId()] = rx;
0065 
0066         const QString watchedService = QString(dbusactivation).replace(QLatin1String(".*"), QLatin1String("*"));
0067         m_sessionServiceWatcher->addWatchedService(watchedService);
0068         m_systemServiceWatcher->addWatchedService(watchedService);
0069     }
0070 }
0071 
0072 void DBusServiceObserver::unregisterPlugin(const QString &pluginId)
0073 {
0074     if (m_dbusActivatableTasks.contains(pluginId)) {
0075         QRegExp rx = m_dbusActivatableTasks.take(pluginId);
0076         const QString watchedService = rx.pattern().replace(QLatin1String(".*"), QLatin1String("*"));
0077         m_sessionServiceWatcher->removeWatchedService(watchedService);
0078         m_systemServiceWatcher->removeWatchedService(watchedService);
0079     }
0080 }
0081 
0082 bool DBusServiceObserver::isDBusActivable(const QString &pluginId)
0083 {
0084     return m_dbusActivatableTasks.contains(pluginId);
0085 }
0086 
0087 /* Loading and unloading Plasmoids when dbus services come and go
0088  *
0089  * This works as follows:
0090  * - we collect a list of plugins and related services in m_dbusActivatableTasks
0091  * - we query DBus for the list of services, async (initDBusActivatables())
0092  * - we go over that list, adding tasks when a service and plugin match ({session,system}BusNameFetchFinished())
0093  * - we start watching for new services, and do the same (serviceRegistered())
0094  * - whenever a service is gone, we check whether to unload a Plasmoid (serviceUnregistered())
0095  *
0096  * Order of events has to be:
0097  * - create a match rule for new service on DBus daemon
0098  * - start fetching a list of names
0099  * - ignore all changes that happen in the meantime
0100  * - handle the list of all names
0101  */
0102 void DBusServiceObserver::initDBusActivatables()
0103 {
0104     // fetch list of existing services
0105     QDBusConnection::sessionBus().interface()->callWithCallback(QStringLiteral("ListNames"),
0106                                                                 QList<QVariant>(),
0107                                                                 this,
0108                                                                 SLOT(sessionBusNameFetchFinished(QStringList)),
0109                                                                 SLOT(sessionBusNameFetchError(QDBusError)));
0110 
0111     QDBusConnection::systemBus().interface()->callWithCallback(QStringLiteral("ListNames"),
0112                                                                QList<QVariant>(),
0113                                                                this,
0114                                                                SLOT(systemBusNameFetchFinished(QStringList)),
0115                                                                SLOT(systemBusNameFetchError(QDBusError)));
0116 }
0117 
0118 void DBusServiceObserver::sessionBusNameFetchFinished(const QStringList &list)
0119 {
0120     for (const QString &serviceName : list) {
0121         serviceRegistered(serviceName);
0122     }
0123 
0124     m_dbusSessionServiceNamesFetched = true;
0125 }
0126 
0127 void DBusServiceObserver::sessionBusNameFetchError(const QDBusError &error)
0128 {
0129     qCWarning(SYSTEM_TRAY) << "Could not get list of available D-Bus services on the session bus:" << error.name() << ":" << error.message();
0130 }
0131 
0132 void DBusServiceObserver::systemBusNameFetchFinished(const QStringList &list)
0133 {
0134     for (const QString &serviceName : list) {
0135         serviceRegistered(serviceName);
0136     }
0137 
0138     m_dbusSystemServiceNamesFetched = true;
0139 }
0140 
0141 void DBusServiceObserver::systemBusNameFetchError(const QDBusError &error)
0142 {
0143     qCWarning(SYSTEM_TRAY) << "Could not get list of available D-Bus services on the system bus:" << error.name() << ":" << error.message();
0144 }
0145 
0146 void DBusServiceObserver::serviceRegistered(const QString &service)
0147 {
0148     if (service.startsWith(QLatin1Char(':'))) {
0149         return;
0150     }
0151 
0152     for (auto it = m_dbusActivatableTasks.constBegin(), end = m_dbusActivatableTasks.constEnd(); it != end; ++it) {
0153         const QString &plugin = it.key();
0154         if (!m_settings->isEnabledPlugin(plugin)) {
0155             continue;
0156         }
0157 
0158         const auto &rx = it.value();
0159         if (rx.exactMatch(service)) {
0160             qCDebug(SYSTEM_TRAY) << "DBus service" << service << "matching" << m_dbusActivatableTasks[plugin] << "appeared. Loading" << plugin;
0161             Q_EMIT serviceStarted(plugin);
0162             m_dbusServiceCounts[plugin]++;
0163         }
0164     }
0165 }
0166 
0167 void DBusServiceObserver::serviceUnregistered(const QString &service)
0168 {
0169     for (auto it = m_dbusActivatableTasks.constBegin(), end = m_dbusActivatableTasks.constEnd(); it != end; ++it) {
0170         const QString &plugin = it.key();
0171         if (!m_settings->isEnabledPlugin(plugin)) {
0172             continue;
0173         }
0174 
0175         const auto &rx = it.value();
0176         if (rx.exactMatch(service)) {
0177             m_dbusServiceCounts[plugin]--;
0178             Q_ASSERT(m_dbusServiceCounts[plugin] >= 0);
0179             if (m_dbusServiceCounts[plugin] == 0) {
0180                 qCDebug(SYSTEM_TRAY) << "DBus service" << service << "matching" << m_dbusActivatableTasks[plugin] << "disappeared. Unloading" << plugin;
0181                 Q_EMIT serviceStopped(plugin);
0182             }
0183         }
0184     }
0185 }