File indexing completed on 2024-05-12 05:37:09

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         QRegularExpression rx = QRegularExpression::fromWildcard(dbusactivation);
0063         m_dbusActivatableTasks[pluginMetaData.pluginId()] = rx;
0064 
0065         const QString watchedService = QString(dbusactivation).replace(QLatin1String(".*"), QLatin1String("*"));
0066         m_sessionServiceWatcher->addWatchedService(watchedService);
0067         m_systemServiceWatcher->addWatchedService(watchedService);
0068     }
0069 }
0070 
0071 void DBusServiceObserver::unregisterPlugin(const QString &pluginId)
0072 {
0073     if (m_dbusActivatableTasks.contains(pluginId)) {
0074         auto rx = m_dbusActivatableTasks.take(pluginId);
0075         const QString watchedService = rx.pattern().replace(QLatin1String(".*"), QLatin1String("*"));
0076         m_sessionServiceWatcher->removeWatchedService(watchedService);
0077         m_systemServiceWatcher->removeWatchedService(watchedService);
0078     }
0079 }
0080 
0081 bool DBusServiceObserver::isDBusActivable(const QString &pluginId)
0082 {
0083     return m_dbusActivatableTasks.contains(pluginId);
0084 }
0085 
0086 /* Loading and unloading Plasmoids when dbus services come and go
0087  *
0088  * This works as follows:
0089  * - we collect a list of plugins and related services in m_dbusActivatableTasks
0090  * - we query DBus for the list of services, async (initDBusActivatables())
0091  * - we go over that list, adding tasks when a service and plugin match ({session,system}BusNameFetchFinished())
0092  * - we start watching for new services, and do the same (serviceRegistered())
0093  * - whenever a service is gone, we check whether to unload a Plasmoid (serviceUnregistered())
0094  *
0095  * Order of events has to be:
0096  * - create a match rule for new service on DBus daemon
0097  * - start fetching a list of names
0098  * - ignore all changes that happen in the meantime
0099  * - handle the list of all names
0100  */
0101 void DBusServiceObserver::initDBusActivatables()
0102 {
0103     // fetch list of existing services
0104     QDBusConnection::sessionBus().interface()->callWithCallback(QStringLiteral("ListNames"),
0105                                                                 QList<QVariant>(),
0106                                                                 this,
0107                                                                 SLOT(sessionBusNameFetchFinished(QStringList)),
0108                                                                 SLOT(sessionBusNameFetchError(QDBusError)));
0109 
0110     QDBusConnection::systemBus().interface()->callWithCallback(QStringLiteral("ListNames"),
0111                                                                QList<QVariant>(),
0112                                                                this,
0113                                                                SLOT(systemBusNameFetchFinished(QStringList)),
0114                                                                SLOT(systemBusNameFetchError(QDBusError)));
0115 }
0116 
0117 void DBusServiceObserver::sessionBusNameFetchFinished(const QStringList &list)
0118 {
0119     for (const QString &serviceName : list) {
0120         serviceRegistered(serviceName);
0121     }
0122 
0123     m_dbusSessionServiceNamesFetched = true;
0124 }
0125 
0126 void DBusServiceObserver::sessionBusNameFetchError(const QDBusError &error)
0127 {
0128     qCWarning(SYSTEM_TRAY) << "Could not get list of available D-Bus services on the session bus:" << error.name() << ":" << error.message();
0129 }
0130 
0131 void DBusServiceObserver::systemBusNameFetchFinished(const QStringList &list)
0132 {
0133     for (const QString &serviceName : list) {
0134         serviceRegistered(serviceName);
0135     }
0136 
0137     m_dbusSystemServiceNamesFetched = true;
0138 }
0139 
0140 void DBusServiceObserver::systemBusNameFetchError(const QDBusError &error)
0141 {
0142     qCWarning(SYSTEM_TRAY) << "Could not get list of available D-Bus services on the system bus:" << error.name() << ":" << error.message();
0143 }
0144 
0145 void DBusServiceObserver::serviceRegistered(const QString &service)
0146 {
0147     if (service.startsWith(QLatin1Char(':'))) {
0148         return;
0149     }
0150 
0151     for (auto it = m_dbusActivatableTasks.constBegin(), end = m_dbusActivatableTasks.constEnd(); it != end; ++it) {
0152         const QString &plugin = it.key();
0153         if (!m_settings->isEnabledPlugin(plugin)) {
0154             continue;
0155         }
0156 
0157         const auto &rx = it.value();
0158         if (rx.match(service).hasMatch()) {
0159             qCDebug(SYSTEM_TRAY) << "DBus service" << service << "matching" << m_dbusActivatableTasks[plugin] << "appeared. Loading" << plugin;
0160             Q_EMIT serviceStarted(plugin);
0161             m_dbusServiceCounts[plugin]++;
0162         }
0163     }
0164 }
0165 
0166 void DBusServiceObserver::serviceUnregistered(const QString &service)
0167 {
0168     for (auto it = m_dbusActivatableTasks.constBegin(), end = m_dbusActivatableTasks.constEnd(); it != end; ++it) {
0169         const QString &plugin = it.key();
0170         if (!m_settings->isEnabledPlugin(plugin)) {
0171             continue;
0172         }
0173 
0174         const auto &rx = it.value();
0175         if (rx.match(service).hasMatch()) {
0176             m_dbusServiceCounts[plugin]--;
0177             Q_ASSERT(m_dbusServiceCounts[plugin] >= 0);
0178             if (m_dbusServiceCounts[plugin] == 0) {
0179                 qCDebug(SYSTEM_TRAY) << "DBus service" << service << "matching" << m_dbusActivatableTasks[plugin] << "disappeared. Unloading" << plugin;
0180                 Q_EMIT serviceStopped(plugin);
0181             }
0182         }
0183     }
0184 }