🏡 index : quine-global-labs/plasma-desktop.git

// vim: noexpandtab shiftwidth=4 tabstop=4
/* This file is part of the KDE project
   Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org>
   Copyright (C) 2020 Kai Uwe Broulik <kde@broulik.de>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "kcmkded.h"

#include "debug.h"

#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusPendingCall>
#include <QDBusPendingCallWatcher>

#include <KAboutData>
#include <KConfig>
#include <KConfigGroup>
#include <KPluginInfo>
#include <KPluginFactory>
#include <KPluginLoader>
#include <KPluginMetaData>
#include <KLocalizedString>

#include <algorithm>

#include "modulesmodel.h"
#include "filterproxymodel.h"

#include "kded_interface.h"

K_PLUGIN_FACTORY_WITH_JSON(KCMStyleFactory, "kcmkded.json", registerPlugin<KDEDConfig>();)

static const QString s_kdedServiceName = QStringLiteral("org.kde.kded5");

KDEDConfig::KDEDConfig(QObject* parent, const QVariantList &args)
	: KQuickAddons::ConfigModule(parent, args)
    , m_model(new ModulesModel(this))
    , m_filteredModel(new FilterProxyModel(this))
    , m_kdedInterface(new org::kde::kded5(s_kdedServiceName,
                                          QStringLiteral("/kded"),
                                          QDBusConnection::sessionBus()))
    , m_kdedWatcher(new QDBusServiceWatcher(s_kdedServiceName,
                                            QDBusConnection::sessionBus(),
                                            QDBusServiceWatcher::WatchForOwnerChange, this))
{
    qmlRegisterUncreatableType<KDEDConfig>("org.kde.private.kcms.style", 1, 0, "KCM", QStringLiteral("Cannot create instances of KCM"));
    // FIXME Qt 5.14 qmlRegisterAnonymousType
    qmlRegisterType<ModulesModel>();
    qmlRegisterType<FilterProxyModel>();

    KAboutData *about = new KAboutData(QStringLiteral("kcm5_kded"), i18n("Background Services"),
        QStringLiteral("2.0"), QString(), KAboutLicense::GPL,
        i18n("(c) 2002 Daniel Molkentin, (c) 2020 Kai Uwe Broulik")
    );
	about->addAuthor(i18n("Daniel Molkentin"), QString(),QStringLiteral("molkentin@kde.org"));
    about->addAuthor(i18n("Kai Uwe Broulik"), QString(),QStringLiteral("kde@broulik.de"));
    setAboutData(about);
    setButtons(Apply|Default|Help);

    m_filteredModel->setSourceModel(m_model);

    connect(m_model, &ModulesModel::autoloadedModulesChanged, this, [this] {
        setNeedsSave(m_model->needsSave());
        setRepresentsDefaults(m_model->representsDefault());
    });

    connect(m_kdedWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this,
        [this](const QString &service, const QString &oldOwner, const QString &newOwner) {
        Q_UNUSED(service)
        Q_UNUSED(oldOwner)
        setKdedRunning(!newOwner.isEmpty());
    });
    setKdedRunning(QDBusConnection::sessionBus().interface()->isServiceRegistered(s_kdedServiceName));
}

ModulesModel *KDEDConfig::model() const
{
    return m_model;
}

FilterProxyModel *KDEDConfig::filteredModel() const
{
    return m_filteredModel;
}

bool KDEDConfig::kdedRunning() const
{
    return m_kdedRunning;
}

void KDEDConfig::setKdedRunning(bool kdedRunning)
{
    if (m_kdedRunning == kdedRunning) {
        return;
    }

    m_kdedRunning = kdedRunning;
    emit kdedRunningChanged();

    if (kdedRunning) {
        getModuleStatus();
    } else {
        m_model->setRunningModulesKnown(false);
    }
}

void KDEDConfig::startModule(const QString &moduleName)
{
    startOrStopModule(moduleName, Running);
}

void KDEDConfig::stopModule(const QString &moduleName)
{
    startOrStopModule(moduleName, NotRunning);
}

void KDEDConfig::startOrStopModule(const QString &moduleName, ModuleStatus status)
{
    auto call = (status == NotRunning ? m_kdedInterface->unloadModule(moduleName)
                                      : m_kdedInterface->loadModule(moduleName));

    QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this);
    connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this, moduleName, status](QDBusPendingCallWatcher *watcher) {
        QDBusPendingReply<bool> reply = *watcher;
        watcher->deleteLater();

        if (reply.isError()) {
            if (status == NotRunning) {
                emit errorMessage(i18n("Failed to stop service: %1", reply.error().message()));
            } else {
                emit errorMessage(i18n("Failed to start service: %1", reply.error().message()));
            }
            return;
        }

        if (!reply.value()) {
            if (status == NotRunning) {
                emit errorMessage(i18n("Failed to stop service."));
            } else {
                emit errorMessage(i18n("Failed to start service."));
            }
            return;
        }

        qCDebug(KCM_KDED) << "Successfully" << (status == Running ? "started" : "stopped") << moduleName;
        if (status == Running) {
            m_lastStartedModule = moduleName;
        } else {
            m_lastStartedModule.clear();
        }
        getModuleStatus();
    });
}

void KDEDConfig::getModuleStatus()
{
    auto call = m_kdedInterface->loadedModules();

    QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this);
    connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
        QDBusPendingReply<QStringList> reply = *watcher;
        watcher->deleteLater();

        if (reply.isError()) {
            qCWarning(KCM_KDED) << "Failed to get loaded modules" << reply.error().name() << reply.error().message();
            return;
        }

        QStringList runningModules = reply.value();
        m_model->setRunningModules(runningModules);
        m_model->setRunningModulesKnown(true);

        // Check if the user just tried starting a module that then disabled itself again.
        // Some kded modules disable themselves on start when they deem themselves unnecessary
        // based on some configuration independ of kded or the current environment.
        // At least provide some feedback and not leave the user wondering why the service doesn't start.
        if (!m_lastStartedModule.isEmpty() && !runningModules.contains(m_lastStartedModule)) {
            emit showSelfDisablingModulesHint();
        }
        m_lastStartedModule.clear();

        // Check if any modules got started/stopped as a result of reloading kded
        if (!m_runningModulesBeforeReconfigure.isEmpty()) {
            std::sort(m_runningModulesBeforeReconfigure.begin(), m_runningModulesBeforeReconfigure.end());
            std::sort(runningModules.begin(), runningModules.end());

            if (m_runningModulesBeforeReconfigure != runningModules) {
                emit showRunningModulesChangedAfterSaveHint();
            }
        }
        m_runningModulesBeforeReconfigure.clear();
    });
}

void KDEDConfig::load()
{
    m_model->load();

    setNeedsSave(false);
    setRepresentsDefaults(m_model->representsDefault());
}

void KDEDConfig::save()
{
	KConfig kdedrc(QStringLiteral("kded5rc"), KConfig::NoGlobals);

    for (int i = 0; i < m_model->rowCount(); ++i) {
        const QModelIndex idx = m_model->index(i, 0);

        const auto type = static_cast<ModuleType>(idx.data(ModulesModel::TypeRole).toInt());
        if (type != AutostartType) {
            continue;
        }

        const QString moduleName = idx.data(ModulesModel::ModuleNameRole).toString();

        const bool autoloadEnabled = idx.data(ModulesModel::AutoloadEnabledRole).toBool();
        KConfigGroup cg(&kdedrc, QStringLiteral("Module-%1").arg(moduleName));
        cg.writeEntry("autoload", autoloadEnabled);
    }

    kdedrc.sync();
    m_model->refreshAutoloadEnabledSavedState();
    setNeedsSave(false);

    m_runningModulesBeforeReconfigure = m_model->runningModules();

    // Is all of this really necessary? I would also think it to be fire and forget...
    // Only if changing autoload for a module may load/unload it, otherwise there's no point.
    // Autoload doesn't affect a running session and reloading the running modules is also useless then.
    auto call = m_kdedInterface->reconfigure();
    QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this);
    connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
        QDBusPendingReply<void> reply = *watcher;
        watcher->deleteLater();

        if (reply.isError()) {
            emit errorMessage(i18n("Failed to notify KDE Service Manager (kded5) of saved changed: %1", reply.error().message()));
            return;
        }

        qCDebug(KCM_KDED) << "Successfully reconfigured kded";
        getModuleStatus();
    });
}

void KDEDConfig::defaults()
{
    for (int i = 0; i < m_model->rowCount(); ++i) {
        const QModelIndex idx = m_model->index(i, 0);
        m_model->setData(idx, true, ModulesModel::AutoloadEnabledRole);
    }
}

#include "kcmkded.moc"