// vim: expandtab sw=4 ts=4 /* This file is part of the KDE libraries * Copyright (C) 1999 David Faure * Copyright (C) 2000 Waldo Bastian * * 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 "kdedmodule.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define KDED_EXENAME "kded4" #define MODULES_PATH "/modules/" #define MODULES_PATH_SIZE 9 Kded *Kded::_self = 0; static bool bCheckSycoca = true; static bool bCheckStamps = true; static bool bCheckHostname = true; static int iHostnamePollInterval = 5000; QT_BEGIN_NAMESPACE extern Q_DBUS_EXPORT void qDBusAddSpyHook(void (*)(const QDBusMessage&)); QT_END_NAMESPACE static void runBuildSycoca() { const QString exe = KStandardDirs::findExe(KBUILDSYCOCA_EXENAME); Q_ASSERT(!exe.isEmpty()); QStringList args; if (bCheckStamps) { args.append("--checkstamps"); } if (QProcess::execute(exe, args) != 0) { kWarning() << "a problem occured while running" << exe; } } static void runDontChangeHostname(const QByteArray &oldName, const QByteArray &newName) { const QString exe = KStandardDirs::findExe(QString::fromLatin1("kdontchangethehostname")); Q_ASSERT(!exe.isEmpty()); QStringList args; args.append(QFile::decodeName(oldName)); args.append(QFile::decodeName(newName)); if (QProcess::execute(exe, args) != 0) { kWarning() << "a problem occured while running" << exe; } } Kded::Kded(QObject *parent) : QObject(parent), m_pDirWatch(nullptr), m_pTimer(nullptr), m_hTimer(nullptr) { _self = this; new KBuildsycocaAdaptor(this); new KdedAdaptor(this); QDBusConnection session = QDBusConnection::sessionBus(); session.registerObject("/kbuildsycoca", this); session.registerObject("/kded", this); session.registerService("org.kde.kded"); updateResourceList(); updateDirWatch(); initModules(); qDBusAddSpyHook(messageFilter); m_pTimer = new QTimer(this); m_pTimer->setSingleShot(true); connect(m_pTimer, SIGNAL(timeout()), this, SLOT(recreate())); if (bCheckHostname) { // Watch for hostname changes m_hTimer = new QTimer(this); m_hTimer->start(iHostnamePollInterval); // repetitive timer (not single-shot) connect(m_hTimer, SIGNAL(timeout()), this, SLOT(checkHostname())); checkHostname(); } } Kded::~Kded() { _self = 0; QDBusConnection session = QDBusConnection::sessionBus(); session.unregisterObject("/kbuildsycoca"); session.unregisterObject("/kded"); session.unregisterService("org.kde.kded"); m_pTimer->stop(); delete m_pTimer; delete m_pDirWatch; if (m_hTimer) { m_hTimer->stop(); delete m_hTimer; } QHashIterator it(m_modules); while (it.hasNext()) { it.next(); KDEDModule* module(it.value()); // first disconnect otherwise slotKDEDModuleRemoved() is called // and changes m_modules while we're iterating over it disconnect(module, SIGNAL(moduleDeleted(KDEDModule*)), this, SLOT(slotKDEDModuleRemoved(KDEDModule*))); 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; } if (message.type() != QDBusMessage::MethodCallMessage) { return; } QString obj = message.path(); if (!obj.startsWith(QLatin1String(MODULES_PATH))) { return; } // Remove the part obj = obj.mid(MODULES_PATH_SIZE); // Remove the part after the modules name int index = obj.indexOf('/'); if (index != -1) { obj = obj.left(index); } if (self()->m_dontLoad.value(obj, 0)) { return; } KDEDModule *module = self()->loadModule(obj, true); if (!module) { kDebug(7020) << "Failed to load module for " << obj; } Q_UNUSED(module); } static int phaseForModule(const KService::Ptr &service) { const QVariant phasev = service->property("X-KDE-Kded-phase", QVariant::Int); return (phasev.isValid() ? phasev.toInt() : 2); } static bool allowedForDesktop(const KService::Ptr &service, const QString &desktop) { const QString only_show_in = service->property("OnlyShowIn", QVariant::String).toString(); kDebug(7020) << "only_show_in" << service->entryPath() << only_show_in; if (only_show_in.isEmpty()) { return true; } const QStringList only_show_in_list = only_show_in.split(QLatin1Char(';')); return only_show_in_list.contains(desktop); } static bool excludedForDesktop(const KService::Ptr &service, const QString &desktop) { const QString not_show_in = service->property("NotShowIn", QVariant::String).toString(); kDebug(7020) << "not_show_in" << service->entryPath() << not_show_in; if (not_show_in.isEmpty()) { return false; } const QStringList not_show_in_list = not_show_in.split(QLatin1Char(';')); return !not_show_in_list.contains(desktop); } void Kded::initModules() { m_dontLoad.clear(); const QString current_desktop = QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP")); kDebug(7020) << "current_desktop" << current_desktop; // Preload kded modules. const KService::List kdedModules = KServiceTypeTrader::self()->query("KDEDModule"); for (int phase = 0; phase < 2; phase++) { foreach (const KService::Ptr service, kdedModules) { const int module_phase = phaseForModule(service); // Should the service be loaded in this phase or later? if (module_phase != phase) { continue; } // Should the service load on startup? const bool autoload = isModuleAutoloaded(service); bool prevent_autoload = false; switch( module_phase ) { case 0: { // always autoload break; } case 1: { // autoload only in the current desktop? if (current_desktop.isEmpty()) { prevent_autoload = true; break; } if (!prevent_autoload && !allowedForDesktop(service, current_desktop)) { prevent_autoload = true; } if (!prevent_autoload && excludedForDesktop(service, current_desktop)) { prevent_autoload = true; } break; } default: { Q_ASSERT(false); break; } } // Load the module if necessary and allowed if (autoload && !prevent_autoload) { if (!loadModule(service, false)) { continue; } } // Remember if the module is allowed to load on demand bool loadOnDemand = isModuleLoadedOnDemand(service); if (!loadOnDemand) { noDemandLoad(service->desktopEntryName()); } // 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(service->desktopEntryName().toLatin1()); } } } } void Kded::loadSecondPhase() { kDebug(7020) << "Loading second phase autoload"; KSharedConfig::Ptr config = KGlobal::config(); KService::List kdedModules = KServiceTypeTrader::self()->query("KDEDModule"); foreach (const KService::Ptr service, kdedModules) { const bool autoload = isModuleAutoloaded(service); if (autoload && phaseForModule(service) == 2) { // kDebug(7020) << "2nd phase: loading" << service->desktopEntryName(); loadModule(service, false); } } } void Kded::noDemandLoad(const QString &obj) { m_dontLoad.insert(obj.toLatin1(), this); } void Kded::setModuleAutoloading(const QString &obj, bool autoload) { KSharedConfig::Ptr config = KGlobal::config(); // Ensure the service exists. KService::Ptr service = KService::serviceByDesktopPath("kded/" + obj + ".desktop"); if (!service) { return; } KConfigGroup cg(config, QString("Module-%1").arg(service->desktopEntryName())); cg.writeEntry("autoload", autoload); cg.sync(); } bool Kded::isModuleAutoloaded(const QString &obj) const { KService::Ptr s = KService::serviceByDesktopPath("kded/" + obj + ".desktop"); if (!s) { return false; } return isModuleAutoloaded(s); } bool Kded::isModuleAutoloaded(const KService::Ptr &module) const { KSharedConfig::Ptr config = KGlobal::config(); bool autoload = module->property("X-KDE-Kded-autoload", QVariant::Bool).toBool(); KConfigGroup cg(config, QString("Module-%1").arg(module->desktopEntryName())); autoload = cg.readEntry("autoload", autoload); return autoload; } bool Kded::isModuleLoadedOnDemand(const QString &obj) const { KService::Ptr s = KService::serviceByDesktopPath("kded/" + obj + ".desktop"); if (!s) { return false; } return isModuleLoadedOnDemand(s); } bool Kded::isModuleLoadedOnDemand(const KService::Ptr &module) const { KSharedConfig::Ptr config = KGlobal::config(); bool loadOnDemand = true; QVariant p = module->property("X-KDE-Kded-load-on-demand", QVariant::Bool); 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. Q_ASSERT(obj.indexOf('/') == -1); KDEDModule *module = m_modules.value(obj, 0); if (module) { return module; } KService::Ptr s = KService::serviceByDesktopPath("kded/" + obj + ".desktop"); return loadModule(s, onDemand); } KDEDModule *Kded::loadModule(const KService::Ptr& s, bool onDemand) { if (s && !s->library().isEmpty()) { QString obj = s->desktopEntryName(); KDEDModule *oldModule = m_modules.value(obj, 0); if (oldModule) { return oldModule; } if (onDemand) { QVariant p = s->property("X-KDE-Kded-load-on-demand", QVariant::Bool); if (p.isValid() && (p.toBool() == false)) { noDemandLoad(s->desktopEntryName()); return 0; } } QString libname = "kded_" + s->library(); KPluginLoader loader(libname); KPluginFactory *factory = loader.factory(); KDEDModule *module = 0; if (factory) { module = factory->create(this); } if (module) { module->setModuleName(obj); m_modules.insert(obj, module); // m_libs.insert(obj, lib); connect(module, SIGNAL(moduleDeleted(KDEDModule*)), SLOT(slotKDEDModuleRemoved(KDEDModule*))); kDebug(7020) << "Successfully loaded module" << obj; return module; } else { kDebug(7020) << "Could not load module" << obj << loader.errorString(); // loader.unload(); } } return 0; } bool Kded::unloadModule(const QString &obj) { KDEDModule *module = m_modules.value(obj, 0); if (!module) { return false; } kDebug(7020) << "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()); // QLibrary *lib = m_libs.take(module->moduleName()); // if (lib) { // lib->unload(); // } } void Kded::updateDirWatch() { if (!bCheckSycoca) { return; } delete m_pDirWatch; m_pDirWatch = new KDirWatch(this); connect(m_pDirWatch, SIGNAL(dirty(QString)), this, SLOT(update(QString))); foreach(const QString &it, m_allResourceDirs) { m_pDirWatch->addDir(it, true); } } void Kded::updateResourceList() { KSycoca::clearCaches(); foreach(const QString &it, KSycoca::self()->allResourceDirs()) { if (!m_allResourceDirs.contains(it)) { m_allResourceDirs.append(it); } } } void Kded::recreate() { runBuildSycoca(); updateResourceList(); updateDirWatch(); // NOTE: phase 2 is done by ksmserver, i.e. it will be redone on the next session initModules(); } void Kded::update(const QString& path) { Q_UNUSED(path); if (!m_pTimer->isActive()) { m_pTimer->start(5000); } } void Kded::checkHostname() { const QByteArray newHostname = QHostInfo::localHostName().toUtf8(); if (newHostname.isEmpty()) { return; } if (m_hostname.isEmpty()) { m_hostname = newHostname; return; } if (m_hostname == newHostname) { return; } runDontChangeHostname(m_hostname, newHostname); m_hostname = newHostname; } KBuildsycocaAdaptor::KBuildsycocaAdaptor(QObject *parent) : QDBusAbstractAdaptor(parent) { } void KBuildsycocaAdaptor::recreate() { Kded::self()->recreate(); } int main(int argc, char *argv[]) { KAboutData aboutData("kded" /*don't change this one to kded4! dbus registration should be org.kde.kded etc.*/, "kdelibs4", ki18n("KDE Daemon"), KDE_VERSION_STRING, ki18n("KDE Daemon - triggers Sycoca database updates when needed")); KCmdLineArgs::init(argc, argv, &aboutData); KComponentData componentData(&aboutData); KSharedConfig::Ptr config = componentData.config(); // Enable translations. KApplication app; app.setQuitOnLastWindowClosed(false); QDBusConnection session = QDBusConnection::sessionBus(); if (!session.isConnected()) { kWarning() << "No DBUS session-bus found. Check if you have started the DBUS server."; return 1; } QDBusReply sessionReply = session.interface()->isServiceRegistered("org.kde.kded"); if (sessionReply.isValid() && sessionReply.value() == true) { kWarning() << "Another instance of kded4 is already running!"; return 2; } KConfigGroup cg(config, "General"); bCheckSycoca = cg.readEntry("CheckSycoca", true); bCheckStamps = cg.readEntry("CheckFileStamps", true); bCheckHostname = cg.readEntry("CheckHostname", true); iHostnamePollInterval = cg.readEntry("HostnamePollInterval", 5000); Kded kded(&app); return app.exec(); // keep running } #include "moc_kded.cpp"