/*  This file is part of the KDE libraries
 *  Copyright (C) 1999 David Faure <faure@kde.org>
 *  Copyright (C) 2000 Waldo Bastian <bastian@kde.org>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License version 2 as published by the Free Software Foundation;
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 **/

#include "kded.h"
#include "kdedadaptor.h"
#include "kded_version.h"

#include <kcrash.h>

#include <qplatformdefs.h>

#include <QLoggingCategory>
#include <QDir>
#include <QTimer>
#include <QCommandLineParser>
#include <QApplication>

#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusServiceWatcher>

#include <KDBusService>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <KDirWatch>
#include <KServiceTypeTrader>
#include <KToolInvocation>
#include <KPluginInfo>
#include <KPluginMetaData>

#ifdef Q_OS_OSX
#include <CoreFoundation/CoreFoundation.h>
#endif

Q_DECLARE_LOGGING_CATEGORY(KDED)

Q_LOGGING_CATEGORY(KDED, "kf5.kded", QtWarningMsg)

Kded *Kded::_self = nullptr;

static bool delayedCheck;
static bool bCheckSycoca;
static bool bCheckUpdates;

#ifdef Q_DBUS_EXPORT
extern Q_DBUS_EXPORT void qDBusAddSpyHook(void (*)(const QDBusMessage &));
#else
extern QDBUS_EXPORT void qDBusAddSpyHook(void (*)(const QDBusMessage &));
#endif

static void runKonfUpdate()
{
    KToolInvocation::kdeinitExecWait(QStringLiteral(KCONF_UPDATE_EXE),
            QStringList(), nullptr, nullptr, "0" /*no startup notification*/);
}

Kded::Kded()
    : m_pDirWatch(nullptr)
    , m_pTimer(new QTimer(this))
    , m_needDelayedCheck(false)
{
    _self = this;

    m_serviceWatcher = new QDBusServiceWatcher(this);
    m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
    m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
    QObject::connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered,
                     this, &Kded::slotApplicationRemoved);

    new KBuildsycocaAdaptor(this);
    new KdedAdaptor(this);

    QDBusConnection session = QDBusConnection::sessionBus();
    session.registerObject(QStringLiteral("/kbuildsycoca"), this);
    session.registerObject(QStringLiteral("/kded"), this);

    qDBusAddSpyHook(messageFilter);

    m_pTimer->setSingleShot(true);
    connect(m_pTimer, &QTimer::timeout, this, static_cast<void(Kded::*)()>(&Kded::recreate));
}

Kded::~Kded()
{
    _self = nullptr;
    m_pTimer->stop();
    delete m_pTimer;
    delete m_pDirWatch;

    for (QHash<QString, KDEDModule *>::const_iterator
            it(m_modules.constBegin()), itEnd(m_modules.constEnd());
            it != itEnd; ++it) {
        KDEDModule *module(it.value());

        // first disconnect otherwise slotKDEDModuleRemoved() is called
        // and changes m_modules while we're iterating over it
        disconnect(module, &KDEDModule::moduleDeleted,
                   this, &Kded::slotKDEDModuleRemoved);

        delete module;
    }
}

// on-demand module loading
// this function is called by the D-Bus message processing function before
// calls are delivered to objects
void Kded::messageFilter(const QDBusMessage &message)
{
    // This happens when kded goes down and some modules try to clean up.
    if (!self()) {
        return;
    }

    QString obj = KDEDModule::moduleForMessage(message);
    if (obj.isEmpty() || obj == QLatin1String("ksycoca")) {
        return;
    }

    if (self()->m_dontLoad.value(obj, nullptr)) {
        return;
    }

    self()->loadModule(obj, true);
}

static int phaseForModule(const KPluginMetaData &module)
{
    const QVariant phasev = module.rawData().value(QStringLiteral("X-KDE-Kded-phase")).toVariant();
    return phasev.isValid() ? phasev.toInt() : 2;
}

QVector<KPluginMetaData> Kded::availableModules() const
{
    QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("kf5/kded"));
    QSet<QString> moduleIds;
    for (const KPluginMetaData &md : qAsConst(plugins)) {
        moduleIds.insert(md.pluginId());
    }
    // also search for old .desktop based kded modules
    const KPluginInfo::List oldStylePlugins = KPluginInfo::fromServices(KServiceTypeTrader::self()->query(QStringLiteral("KDEDModule")));
    for (const KPluginInfo &info : oldStylePlugins) {
        if (moduleIds.contains(info.pluginName())) {
            qCWarning(KDED).nospace() << "kded module " << info.pluginName() << " has already been found using "
                "JSON metadata, please don't install the now unneeded .desktop file (" << info.entryPath() << ").";
        } else {
            qCDebug(KDED).nospace() << "kded module " << info.pluginName() << " still uses .desktop files ("
                << info.entryPath() << "). Please port it to JSON metadata.";
            plugins.append(info.toMetaData());
        }
    }
    return plugins;
}

static KPluginMetaData findModule(const QString &id)
{
    KPluginMetaData module(QStringLiteral("kf5/kded/") + id);
    if (module.isValid()) {
        return module;
    }
    // TODO KF6: remove the .desktop fallback code
    KService::Ptr oldStyleModule = KService::serviceByDesktopPath(QStringLiteral("kded/") + id + QStringLiteral(".desktop"));
    if (oldStyleModule) {
        qCDebug(KDED).nospace() << "kded module " << oldStyleModule->desktopEntryName()
            << " still uses .desktop files (" << oldStyleModule->entryPath() << "). Please port it to JSON metadata.";
        return KPluginInfo(oldStyleModule).toMetaData();
    }
    qCWarning(KDED) << "could not find kded module with id" << id;
    return KPluginMetaData();
}

void Kded::initModules()
{
    m_dontLoad.clear();
#ifdef Q_OS_OSX
    // it seems there is no reason to honour KDE_FULL_SESSION on OS X even if it's set for some reason.
    // That way kdeinit5 is always auto-started if required (and the kded is as good a candidate
    // for starting this service as any other application).
    bool kde_running = false;
#else
    bool kde_running = !qEnvironmentVariableIsEmpty("KDE_FULL_SESSION");
#endif
    if (kde_running) {
#ifndef Q_OS_WIN
        // not the same user like the one running the session (most likely we're run via sudo or something)
        const QByteArray sessionUID = qgetenv("KDE_SESSION_UID");
        if (!sessionUID.isEmpty() && uid_t(sessionUID.toInt()) != getuid()) {
            kde_running = false;
        }
#endif
        //TODO: Change 5 to KDED_VERSION_MAJOR the moment KF5 are stable
        // not the same kde version as the current desktop
        const QByteArray kdeSession = qgetenv("KDE_SESSION_VERSION");
        if (kdeSession.toInt() != 5) {
            kde_running = false;
        }
    }

    // There will be a "phase 2" only if we're in the KDE startup.
    // If kded is restarted by its crashhandled or by hand,
    // then there will be no second phase autoload, so load
    // these modules now, if in a KDE session.
    const bool loadPhase2Now = (kde_running && qEnvironmentVariableIntValue("KDED_STARTED_BY_KDEINIT") == 0);

    // Preload kded modules.
    const QVector<KPluginMetaData> kdedModules = availableModules();
    for (const KPluginMetaData &module : kdedModules) {
        // Should the service load on startup?
        const bool autoload = isModuleAutoloaded(module);
        if (!platformSupportsModule(module)) {
            continue;
        }

        // see ksmserver's README for description of the phases
        bool prevent_autoload = false;
        switch (phaseForModule(module)) {
        case 0: // always autoload
            break;
        case 1: // autoload only in KDE
            if (!kde_running) {
                prevent_autoload = true;
            }
            break;
        case 2: // autoload delayed, only in KDE
        default:
            if (!loadPhase2Now) {
                prevent_autoload = true;
            }
            break;
        }

        // Load the module if necessary and allowed
        if (autoload && !prevent_autoload) {
            if (!loadModule(module, false)) {
                continue;
            }
        }

        // Remember if the module is allowed to load on demand
        bool loadOnDemand = isModuleLoadedOnDemand(module);
        if (!loadOnDemand) {
            noDemandLoad(module.pluginId());
        }

        // In case of reloading the configuration it is possible for a module
        // to run even if it is now allowed to. Stop it then.
        if (!loadOnDemand && !autoload) {
            unloadModule(module.pluginId());
        }
    }
}

void Kded::loadSecondPhase()
{
    qCDebug(KDED) << "Loading second phase autoload";
    KSharedConfig::Ptr config = KSharedConfig::openConfig();
    const QVector<KPluginMetaData> kdedModules = availableModules();
    for (const KPluginMetaData &module : kdedModules) {
        const bool autoload = isModuleAutoloaded(module);
        if (autoload && phaseForModule(module) == 2) {
            qCDebug(KDED) << "2nd phase: loading" << module.pluginId();
            loadModule(module, false);
        }
    }
}

void Kded::noDemandLoad(const QString &obj)
{
    m_dontLoad.insert(obj, this);
}

void Kded::setModuleAutoloading(const QString &obj, bool autoload)
{
    KSharedConfig::Ptr config = KSharedConfig::openConfig();
    // Ensure the service exists.
    KPluginMetaData module = findModule(obj);
    if (!module.isValid()) {
        return;
    }
    KConfigGroup cg(config, QStringLiteral("Module-").append(module.pluginId()));
    cg.writeEntry("autoload", autoload);
    cg.sync();
}

bool Kded::isModuleAutoloaded(const QString &obj) const
{
    return isModuleAutoloaded(findModule(obj));
}

bool Kded::isModuleAutoloaded(const KPluginMetaData &module) const
{
    if (!module.isValid()) {
        return false;
    }
    KSharedConfig::Ptr config = KSharedConfig::openConfig();
    bool autoload = module.rawData().value(QStringLiteral("X-KDE-Kded-autoload")).toVariant().toBool();
    KConfigGroup cg(config, QStringLiteral("Module-").append(module.pluginId()));
    autoload = cg.readEntry("autoload", autoload);
    return autoload;
}

bool Kded::platformSupportsModule(const KPluginMetaData &module) const
{
    const QStringList supportedPlatforms = KPluginMetaData::readStringList(module.rawData(), QStringLiteral("X-KDE-OnlyShowOnQtPlatforms"));

    return supportedPlatforms.isEmpty() || supportedPlatforms.contains(qApp->platformName());
}

bool Kded::isModuleLoadedOnDemand(const QString &obj) const
{
    return isModuleLoadedOnDemand(findModule(obj));
}

bool Kded::isModuleLoadedOnDemand(const KPluginMetaData &module) const
{
    if (!module.isValid()) {
        return false;
    }
    KSharedConfig::Ptr config = KSharedConfig::openConfig();
    bool loadOnDemand = true;
    // use toVariant() since it could be string or bool in the json and QJsonObject does not convert
    QVariant p = module.rawData().value(QStringLiteral("X-KDE-Kded-load-on-demand")).toVariant();
    if (p.isValid() && (p.toBool() == false)) {
        loadOnDemand = false;
    }
    return loadOnDemand;
}

KDEDModule *Kded::loadModule(const QString &obj, bool onDemand)
{
    // Make sure this method is only called with valid module names.
    if (obj.contains(QLatin1Char('/'))) {
        qCWarning(KDED) << "attempting to load invalid kded module name:" << obj;
        return nullptr;
    }
    KDEDModule *module = m_modules.value(obj, nullptr);
    if (module) {
        return module;
    }
    return loadModule(findModule(obj), onDemand);
}

KDEDModule *Kded::loadModule(const KPluginMetaData &module, bool onDemand)
{
    if (!module.isValid() || module.fileName().isEmpty()) {
        qCWarning(KDED) << "attempted to load an invalid module.";
        return nullptr;
    }
    const QString moduleId = module.pluginId();
    KDEDModule *oldModule = m_modules.value(moduleId, nullptr);
    if (oldModule) {
        qCDebug(KDED) << "kded module" << moduleId << "is already loaded.";
        return oldModule;
    }

    if (onDemand) {
        // use toVariant() since it could be string or bool in the json and QJsonObject does not convert
        QVariant p = module.rawData().value(QStringLiteral("X-KDE-Kded-load-on-demand")).toVariant();
        if (p.isValid() && (p.toBool() == false)) {
            noDemandLoad(moduleId);
            return nullptr;
        }
    }

    KDEDModule *kdedModule = nullptr;

    KPluginLoader loader(module.fileName());
    KPluginFactory *factory = loader.factory();
    if (factory) {
        kdedModule = factory->create<KDEDModule>(this);
    } else {
        // TODO: remove this fallback code, the kded modules should all be fixed instead
        KPluginLoader loader2(QStringLiteral("kded_") + module.fileName());
        factory = loader2.factory();
        if (factory) {
            qCWarning(KDED).nospace() << "found kded module " << moduleId
                << " by prepending 'kded_' to the library path, please fix your metadata.";
            kdedModule = factory->create<KDEDModule>(this);
        } else {
            qCWarning(KDED).nospace() << "Could not load kded module " << moduleId << ":"
                << loader.errorString() << " (library path was:" << module.fileName() << ")";
        }
    }

    if (kdedModule) {
        kdedModule->setModuleName(moduleId);
        m_modules.insert(moduleId, kdedModule);
        //m_libs.insert(moduleId, lib);
        connect(kdedModule, &KDEDModule::moduleDeleted, this, &Kded::slotKDEDModuleRemoved);
        qCDebug(KDED) << "Successfully loaded module" << moduleId;
        return kdedModule;
    }
    return nullptr;
}

bool Kded::unloadModule(const QString &obj)
{
    KDEDModule *module = m_modules.value(obj, nullptr);
    if (!module) {
        return false;
    }
    qCDebug(KDED) << "Unloading module" << obj;
    m_modules.remove(obj);
    delete module;
    return true;
}

QStringList Kded::loadedModules()
{
    return m_modules.keys();
}

void Kded::slotKDEDModuleRemoved(KDEDModule *module)
{
    m_modules.remove(module->moduleName());
}

void Kded::slotApplicationRemoved(const QString &name)
{
#if 0 // see kdedmodule.cpp (KDED_OBJECTS)
    foreach (KDEDModule *module, m_modules) {
        module->removeAll(appId);
    }
#endif
    m_serviceWatcher->removeWatchedService(name);
    const QList<qlonglong> windowIds = m_windowIdList.value(name);
    for (QList<qlonglong>::ConstIterator it = windowIds.begin();
            it != windowIds.end(); ++it) {
        qlonglong windowId = *it;
        m_globalWindowIdList.remove(windowId);
        for (KDEDModule *module : qAsConst(m_modules)) {
            emit module->windowUnregistered(windowId);
        }
    }
    m_windowIdList.remove(name);
}

void Kded::updateDirWatch()
{
    if (!bCheckUpdates) {
        return;
    }

    delete m_pDirWatch;
    m_pDirWatch = new KDirWatch;

    QObject::connect(m_pDirWatch, &KDirWatch::dirty,
                     this, &Kded::update);
    QObject::connect(m_pDirWatch, &KDirWatch::created,
                     this, &Kded::update);
    QObject::connect(m_pDirWatch, &KDirWatch::deleted,
                     this, &Kded::dirDeleted);

    // For each resource
    for (QStringList::ConstIterator it = m_allResourceDirs.constBegin();
            it != m_allResourceDirs.constEnd();
            ++it) {
        readDirectory(*it);
    }
}

void Kded::updateResourceList()
{
    KSycoca::clearCaches();

    if (!bCheckUpdates) {
        return;
    }

    if (delayedCheck) {
        return;
    }

    const QStringList dirs = KSycoca::self()->allResourceDirs();
    // For each resource
    for (QStringList::ConstIterator it = dirs.begin();
            it != dirs.end();
            ++it) {
        if (!m_allResourceDirs.contains(*it)) {
            m_allResourceDirs.append(*it);
            readDirectory(*it);
        }
    }
}

void Kded::recreate()
{
    recreate(false);
}

void Kded::runDelayedCheck()
{
    if (m_needDelayedCheck) {
        recreate(false);
    }
    m_needDelayedCheck = false;
}

void Kded::recreate(bool initial)
{
    // Using KLauncher here is difficult since we might not have a
    // database

    if (!initial) {
        updateDirWatch(); // Update tree first, to be sure to miss nothing.
        KSycoca::self()->ensureCacheValid();
        recreateDone();
    } else {
        if (!delayedCheck) {
            updateDirWatch();    // this would search all the directories
        }
        if (bCheckSycoca) {
            KSycoca::self()->ensureCacheValid();
        }
        recreateDone();
        if (delayedCheck) {
            // do a proper ksycoca check after a delay
            QTimer::singleShot(60000, this, &Kded::runDelayedCheck);
            m_needDelayedCheck = true;
            delayedCheck = false;
        } else {
            m_needDelayedCheck = false;
        }
    }
}

void Kded::recreateDone()
{
    updateResourceList();

    initModules();
}

void Kded::dirDeleted(const QString &path)
{
    update(path);
}

void Kded::update(const QString &)
{
    m_pTimer->start(10000);
}

void Kded::readDirectory(const QString &_path)
{
    QString path(_path);
    if (!path.endsWith(QLatin1Char('/'))) {
        path += QLatin1Char('/');
    }

    if (m_pDirWatch->contains(path)) {   // Already seen this one?
        return;
    }

    Q_ASSERT(path != QDir::homePath());
    m_pDirWatch->addDir(path, KDirWatch::WatchFiles | KDirWatch::WatchSubDirs);       // add watch on this dir
}

void Kded::registerWindowId(qlonglong windowId, const QString &sender)
{
    if (!m_windowIdList.contains(sender)) {
        m_serviceWatcher->addWatchedService(sender);
    }

    m_globalWindowIdList.insert(windowId);
    QList<qlonglong> windowIds = m_windowIdList.value(sender);
    windowIds.append(windowId);
    m_windowIdList.insert(sender, windowIds);

    for (KDEDModule *module : qAsConst(m_modules)) {
        qCDebug(KDED) << module->moduleName();
        emit module->windowRegistered(windowId);
    }
}

void Kded::unregisterWindowId(qlonglong windowId, const QString &sender)
{
    m_globalWindowIdList.remove(windowId);
    QList<qlonglong> windowIds = m_windowIdList.value(sender);
    if (!windowIds.isEmpty()) {
        windowIds.removeAll(windowId);
        if (windowIds.isEmpty()) {
            m_serviceWatcher->removeWatchedService(sender);
            m_windowIdList.remove(sender);
        } else {
            m_windowIdList.insert(sender, windowIds);
        }
    }

    for (KDEDModule *module : qAsConst(m_modules)) {
        qCDebug(KDED) << module->moduleName();
        emit module->windowUnregistered(windowId);
    }
}

static void sighandler(int /*sig*/)
{
    if (qApp) {
        qApp->quit();
    }
}

KUpdateD::KUpdateD()
{
    m_pDirWatch = new KDirWatch(this);
    m_pTimer = new QTimer(this);
    m_pTimer->setSingleShot(true);
    connect(m_pTimer, &QTimer::timeout, this, &KUpdateD::runKonfUpdate);
    QObject::connect(m_pDirWatch, &KDirWatch::dirty,
                     this, &KUpdateD::slotNewUpdateFile);

    const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kconf_update"), QStandardPaths::LocateDirectory);
    for (QStringList::ConstIterator it = dirs.begin();
            it != dirs.end();
            ++it) {
        QString path = *it;
        Q_ASSERT(path != QDir::homePath());
        if (path[path.length() - 1] != QLatin1Char('/')) {
            path += QLatin1Char('/');
        }

        if (!m_pDirWatch->contains(path)) {
            m_pDirWatch->addDir(path, KDirWatch::WatchFiles);
        }
    }
}

KUpdateD::~KUpdateD()
{
}

void KUpdateD::runKonfUpdate()
{
    ::runKonfUpdate();
}

void KUpdateD::slotNewUpdateFile(const QString &dirty)
{
    Q_UNUSED(dirty);
    qCDebug(KDED) << dirty;
    m_pTimer->start(500);
}

KBuildsycocaAdaptor::KBuildsycocaAdaptor(QObject *parent)
    : QDBusAbstractAdaptor(parent)
{
}

void KBuildsycocaAdaptor::recreate()
{
    Kded::self()->recreate();
}

// KF6: remove
bool KBuildsycocaAdaptor::isTestModeEnabled()
{
    return QStandardPaths::isTestModeEnabled();
}

// KF6: remove
void KBuildsycocaAdaptor::setTestModeEnabled()
{
    QStandardPaths::setTestModeEnabled(true);
}

static void setupAppInfo(QApplication *app)
{
    app->setApplicationName(QStringLiteral("kded5"));
    app->setApplicationDisplayName(QStringLiteral("KDE Daemon"));
    app->setOrganizationDomain(QStringLiteral("kde.org"));
    app->setApplicationVersion(QStringLiteral(KDED_VERSION_STRING));
}

static bool detectPlatform(int argc, char **argv)
{
    if (qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) {
        return false;
    }
    for (int i = 0; i < argc; i++) {
        if (qstrcmp(argv[i], "-platform") == 0 ||
                qstrcmp(argv[i], "--platform") == 0 ||
                QByteArray(argv[i]).startsWith("-platform=") ||
                QByteArray(argv[i]).startsWith("--platform=")) {
            return false;
        }
    }
    const QByteArray sessionType = qgetenv("XDG_SESSION_TYPE");
    if (sessionType.isEmpty()) {
        return false;
    }
    if (qstrcmp(sessionType, "wayland") == 0) {
        qputenv("QT_QPA_PLATFORM", "wayland");
        return true;
    } else if (qstrcmp(sessionType, "x11") == 0) {
        qputenv("QT_QPA_PLATFORM", "xcb");
        return true;
    }
    return false;
}

int main(int argc, char *argv[])
{
#ifdef Q_OS_OSX
    CFBundleRef mainBundle = CFBundleGetMainBundle();
    if (mainBundle) {
        // get the application's Info Dictionary. For app bundles this would live in the bundle's Info.plist,
        // for regular executables it is obtained in another way.
        CFMutableDictionaryRef infoDict = (CFMutableDictionaryRef) CFBundleGetInfoDictionary(mainBundle);
        if (infoDict) {
            // Add or set the "LSUIElement" key with/to value "1". This can simply be a CFString.
            CFDictionarySetValue(infoDict, CFSTR("LSUIElement"), CFSTR("1"));
            // That's it. We're now considered as an "agent" by the window server, and thus will have
            // neither menubar nor presence in the Dock or App Switcher.
        }
    }
#endif
    //options.add("check", qi18n("Check Sycoca database only once"));

    // WABA: Make sure not to enable session management.
    qunsetenv("SESSION_MANAGER");

    const bool unsetQpa = detectPlatform(argc, argv);

    // In older versions, QApplication creation was postponed until after
    // testing for --check, in which case, only a QCoreApplication was created.
    // Since that option is no longer used at startup, we removed that speed
    // optimization for code clarity and easier support of standard parameters.
    QApplication app(argc, argv);
    if (unsetQpa) {
        qunsetenv("QT_QPA_PLATFORM");
    }
    setupAppInfo(&app);
    app.setQuitOnLastWindowClosed(false);

    QCommandLineParser parser;
    parser.addHelpOption();
    parser.addVersionOption();
    parser.addOption(QCommandLineOption(QStringLiteral("check"), QStringLiteral("Check cache validity")));
    QCommandLineOption replaceOption({QStringLiteral("replace")}, QStringLiteral("Replace an existing instance"));
    parser.addOption(replaceOption);
    parser.process(app);

    // Parse command line before checking D-Bus
    if (parser.isSet(QStringLiteral("check"))) {
        // KDBusService not wanted here.
        KSycoca::self()->ensureCacheValid();
        runKonfUpdate();
        return 0;
    }

    KDBusService service(KDBusService::Unique | KDBusService::StartupOption(parser.isSet(replaceOption) ? KDBusService::Replace : 0));

    QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface();
    // Also register as all the names we should respond to (org.kde.kcookiejar, org.kde.khotkeys etc.)
    // so that the calling code is independent from the physical "location" of the service.
    const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("kf5/kded"));
    for (const KPluginMetaData &metaData : plugins) {
        const QString serviceName = metaData.rawData().value(QStringLiteral("X-KDE-DBus-ServiceName")).toString();
        if (serviceName.isEmpty()) {
            continue;
        }
        if (!bus->registerService(serviceName)) {
            qCWarning(KDED) << "Couldn't register name" << serviceName << "with DBUS - another process owns it already!";
        }
    }

    KSharedConfig::Ptr config = KSharedConfig::openConfig();
    KConfigGroup cg(config, "General");

    bCheckSycoca = cg.readEntry("CheckSycoca", true);
    bCheckUpdates = cg.readEntry("CheckUpdates", true);
    delayedCheck = cg.readEntry("DelayedCheck", false);

#ifndef Q_OS_WIN
    signal(SIGTERM, sighandler);
    signal(SIGHUP, sighandler);
#endif

    KCrash::setFlags(KCrash::AutoRestart);

    Kded *kded = new Kded();

    kded->recreate(true); // initial

    if (bCheckUpdates) {
        (void) new KUpdateD;    // Watch for updates
    }

    runKonfUpdate(); // Run it once.

    int result = app.exec(); // keep running

    delete kded;

    return result;
}

