File indexing completed on 2024-04-28 11:34:17

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
0004     SPDX-FileCopyrightText: 2003 Matthias Kretz <kretz@kde.org>
0005     SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-only
0008 */
0009 
0010 #include "kcmoduleproxy.h"
0011 #include "kcmoduleproxy_p.h"
0012 
0013 #include <QApplication>
0014 #include <QLayout>
0015 
0016 #include <QDBusConnection>
0017 #include <QDBusConnectionInterface>
0018 #include <QDBusInterface>
0019 #include <QDBusReply>
0020 #include <QDBusServiceWatcher>
0021 #include <QPluginLoader>
0022 
0023 #include <KAboutData>
0024 #include <kcmoduleinfo.h>
0025 
0026 #include <KLocalizedString>
0027 
0028 #include <kcmoduleloader.h>
0029 #include <kcmoduleqml_p.h>
0030 
0031 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 82)
0032 #include "ksettingswidgetadaptor.h"
0033 #endif
0034 
0035 #include <kcmutils_debug.h>
0036 
0037 /*
0038  TODO:
0039 
0040  - Resizing horizontally is constrained; minimum size is set somewhere.
0041     It appears to be somehow derived from the module's size.
0042 
0043  - Prettify: set icon in KCMultiDialog.
0044 
0045  */
0046 /***************************************************************/
0047 KCModule *KCModuleProxy::realModule() const
0048 {
0049     Q_D(const KCModuleProxy);
0050     /*
0051      * Note, don't call any function that calls realModule() since
0052      * that leads to an infinite loop.
0053      */
0054 
0055     /* Already loaded */
0056     if (!d->kcm) {
0057         QApplication::setOverrideCursor(Qt::WaitCursor);
0058         const_cast<KCModuleProxyPrivate *>(d)->loadModule();
0059         QApplication::restoreOverrideCursor();
0060     }
0061     return d->kcm;
0062 }
0063 
0064 void KCModuleProxyPrivate::loadModule()
0065 {
0066     if (!topLayout) {
0067         topLayout = new QVBoxLayout(parent);
0068         QString name;
0069         if (metaData) {
0070             name = metaData.value().pluginId();
0071         }
0072 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 85)
0073         if (name.isEmpty()) {
0074             name = modInfo.handle();
0075         }
0076 #endif
0077         name.replace(QLatin1Char('-'), QLatin1Char('_')); // hyphen is not allowed in dbus, only [A-Z][a-z][0-9]_
0078         name.replace(QLatin1Char('/'), QLatin1Char('_')); // same goes for '/'
0079         name.replace(QLatin1Char(' '), QLatin1Char('_')); // same goes for space characters
0080         dbusService = QLatin1String("org.kde.internal.KSettingsWidget_") + name;
0081 
0082         // for dbus path, we also need to convert '.' characters
0083         name.replace(QLatin1Char('.'), QLatin1Char('_'));
0084         dbusPath = QLatin1String("/internal/KSettingsWidget/") + name;
0085     }
0086 
0087     const bool canRegisterService = QDBusConnection::sessionBus().registerService(dbusService);
0088 
0089     if (!canRegisterService) {
0090         /* We didn't get the name we requested, because it's already taken, */
0091         /* Figure out the name of where the module is already loaded */
0092         QDBusInterface proxy(dbusService, dbusPath, QStringLiteral("org.kde.internal.KSettingsWidget"));
0093         QDBusReply<QString> reply = proxy.call(QStringLiteral("applicationName"));
0094         /* If we get a valid application name, the module is already loaded */
0095         if (reply.isValid()) {
0096             auto *watcher = new QDBusServiceWatcher(parent);
0097             watcher->addWatchedService(dbusService);
0098             watcher->setConnection(QDBusConnection::sessionBus());
0099             watcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange);
0100             QObject::connect(watcher,
0101                              &QDBusServiceWatcher::serviceOwnerChanged,
0102                              parent,
0103                              [this](const QString &serviceName, const QString &oldOwner, const QString &newOwner) {
0104                                  _k_ownerChanged(serviceName, oldOwner, newOwner);
0105                              });
0106 
0107             kcm = KCModuleLoader::reportError(KCModuleLoader::Inline,
0108                                               i18nc("Argument is application name", "This configuration section is already opened in %1", reply.value()),
0109                                               QStringLiteral(" "),
0110                                               parent);
0111             topLayout->addWidget(kcm);
0112             return;
0113         }
0114     }
0115 
0116     // qDebug() << "Module not already loaded, loading module " << modInfo.moduleName() << " from library " << modInfo.library() << " using symbol " << modInfo.handle();
0117     if (metaData) {
0118         kcm = KCModuleLoader::loadModule(metaData.value(), parent, QVariantList(args.cbegin(), args.cend()));
0119     } else {
0120 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 88)
0121         kcm = KCModuleLoader::loadModule(modInfo, KCModuleLoader::Inline, parent, args);
0122 #endif
0123     }
0124 
0125     QObject::connect(kcm, &KCModule::changed, parent, [this](bool state) {
0126         _k_moduleChanged(state);
0127     });
0128     QObject::connect(kcm, &KCModule::defaulted, parent, [this](bool state) {
0129         _k_moduleDefaulted(state);
0130     });
0131     QObject::connect(kcm, &KCModule::destroyed, parent, [this]() {
0132         _k_moduleDestroyed();
0133     });
0134     QObject::connect(kcm, &KCModule::quickHelpChanged, parent, &KCModuleProxy::quickHelpChanged);
0135     parent->setWhatsThis(kcm->quickHelp());
0136 
0137     if (kcm->layout()) {
0138         kcm->layout()->setContentsMargins(0, 0, 0, 0);
0139     }
0140     if (qobject_cast<KCModuleQml *>(kcm)) {
0141         topLayout->setContentsMargins(0, 0, 0, 0);
0142     }
0143 
0144     topLayout->addWidget(kcm);
0145 
0146 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 82)
0147     if (!modInfo.handle().isEmpty()) {
0148         QDBusConnection::sessionBus().registerObject(dbusPath, new KSettingsWidgetAdaptor(parent), QDBusConnection::ExportAllSlots);
0149     }
0150 #endif
0151 }
0152 
0153 void KCModuleProxyPrivate::_k_ownerChanged(const QString &service, const QString &oldOwner, const QString &)
0154 {
0155     if (service == dbusService && !oldOwner.isEmpty()) {
0156         // Violence: Get rid of KCMError & CO, so that
0157         // realModule() attempts to reload the module
0158         delete kcm;
0159         kcm = nullptr;
0160         Q_Q(KCModuleProxy);
0161         q->realModule();
0162 
0163         Q_ASSERT(kcm);
0164         kcm->show();
0165     }
0166 }
0167 
0168 void KCModuleProxy::showEvent(QShowEvent *ev)
0169 {
0170     Q_D(KCModuleProxy);
0171 
0172     (void)realModule();
0173 
0174     /* We have no kcm, if we're in root mode */
0175     if (d->kcm) {
0176         d->kcm->showEvent(ev);
0177     }
0178 
0179     QWidget::showEvent(ev);
0180 }
0181 
0182 KCModuleProxy::~KCModuleProxy()
0183 {
0184     deleteClient();
0185     if (metaData().isValid()) {
0186         // Do not try to unload static plugins
0187         if (!metaData().isStaticPlugin()) {
0188             QPluginLoader(metaData().fileName()).unload();
0189         }
0190     } else {
0191 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 88)
0192         KCModuleLoader::unloadModule(moduleInfo());
0193 #endif
0194     }
0195 
0196     delete d_ptr;
0197 }
0198 
0199 void KCModuleProxy::deleteClient()
0200 {
0201     Q_D(KCModuleProxy);
0202     delete d->kcm;
0203     d->kcm = nullptr;
0204 }
0205 
0206 void KCModuleProxyPrivate::_k_moduleChanged(bool c)
0207 {
0208     if (changed == c) {
0209         return;
0210     }
0211 
0212     Q_Q(KCModuleProxy);
0213     changed = c;
0214     Q_EMIT q->changed(c);
0215 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 87)
0216     Q_EMIT q->changed(q);
0217 #endif
0218 }
0219 
0220 void KCModuleProxyPrivate::_k_moduleDefaulted(bool d)
0221 {
0222     if (defaulted == d) {
0223         return;
0224     }
0225 
0226     Q_Q(KCModuleProxy);
0227     defaulted = d;
0228     Q_EMIT q->changed(changed);
0229 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 87)
0230     Q_EMIT q->changed(q);
0231 #endif
0232 }
0233 
0234 void KCModuleProxyPrivate::_k_moduleDestroyed()
0235 {
0236     kcm = nullptr;
0237 }
0238 
0239 KCModuleProxy::KCModuleProxy(const KPluginMetaData &metaData, QWidget *parent, const QStringList &args)
0240     : QWidget(parent)
0241     , d_ptr(new KCModuleProxyPrivate(this, metaData, args))
0242 {
0243 }
0244 
0245 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 88)
0246 KCModuleProxy::KCModuleProxy(const KService::Ptr &service, QWidget *parent, const QStringList &args)
0247     : QWidget(parent)
0248     , d_ptr(new KCModuleProxyPrivate(this, KCModuleInfo(service), args))
0249 {
0250     d_ptr->q_ptr = this;
0251 }
0252 
0253 KCModuleProxy::KCModuleProxy(const KCModuleInfo &info, QWidget *parent, const QStringList &args)
0254     : QWidget(parent)
0255     , d_ptr(new KCModuleProxyPrivate(this, info, args))
0256 {
0257 }
0258 
0259 KCModuleProxy::KCModuleProxy(const QString &serviceName, QWidget *parent, const QStringList &args)
0260     : QWidget(parent)
0261     , d_ptr(new KCModuleProxyPrivate(this, KCModuleInfo(serviceName), args))
0262 {
0263 }
0264 #endif
0265 
0266 void KCModuleProxy::load()
0267 {
0268     Q_D(KCModuleProxy);
0269     if (realModule()) {
0270         d->kcm->load();
0271         d->_k_moduleChanged(false);
0272     }
0273 }
0274 
0275 void KCModuleProxy::save()
0276 {
0277     Q_D(KCModuleProxy);
0278     if (d->changed && realModule()) {
0279         d->kcm->save();
0280         d->_k_moduleChanged(false);
0281     }
0282 }
0283 
0284 void KCModuleProxy::defaults()
0285 {
0286     Q_D(KCModuleProxy);
0287     if (realModule()) {
0288         d->kcm->defaults();
0289     }
0290 }
0291 
0292 QString KCModuleProxy::quickHelp() const
0293 {
0294     return realModule() ? realModule()->quickHelp() : QString();
0295 }
0296 
0297 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 85)
0298 const KAboutData *KCModuleProxy::aboutData() const
0299 {
0300     QT_WARNING_PUSH
0301     QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
0302     QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
0303     return realModule() ? realModule()->aboutData() : nullptr;
0304     QT_WARNING_POP
0305 }
0306 #endif
0307 
0308 KCModule::Buttons KCModuleProxy::buttons() const
0309 {
0310     if (realModule()) {
0311         return realModule()->buttons();
0312     }
0313     return KCModule::Buttons(KCModule::Help | KCModule::Default | KCModule::Apply);
0314 }
0315 
0316 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 87)
0317 bool KCModuleProxy::changed() const
0318 {
0319     Q_D(const KCModuleProxy);
0320     return d->changed;
0321 }
0322 #endif
0323 
0324 bool KCModuleProxy::isChanged() const
0325 {
0326     Q_D(const KCModuleProxy);
0327     return d->changed;
0328 }
0329 
0330 bool KCModuleProxy::defaulted() const
0331 {
0332     Q_D(const KCModuleProxy);
0333     return d->defaulted;
0334 }
0335 
0336 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 88)
0337 KCModuleInfo KCModuleProxy::moduleInfo() const
0338 {
0339     Q_D(const KCModuleProxy);
0340     return d->modInfo;
0341 }
0342 #endif
0343 
0344 KPluginMetaData KCModuleProxy::metaData() const
0345 {
0346     Q_D(const KCModuleProxy);
0347     return d->metaData.has_value() ? d->metaData.value() : KPluginMetaData();
0348 }
0349 
0350 QString KCModuleProxy::dbusService() const
0351 {
0352     Q_D(const KCModuleProxy);
0353     return d->dbusService;
0354 }
0355 
0356 QString KCModuleProxy::dbusPath() const
0357 {
0358     Q_D(const KCModuleProxy);
0359     return d->dbusPath;
0360 }
0361 
0362 QSize KCModuleProxy::minimumSizeHint() const
0363 {
0364     return QWidget::minimumSizeHint();
0365 }
0366 
0367 void KCModuleProxy::setDefaultsIndicatorsVisible(bool show)
0368 {
0369     Q_D(KCModuleProxy);
0370     if (realModule()) {
0371         d->kcm->setDefaultsIndicatorsVisible(show);
0372     }
0373 }
0374 
0375 // X void KCModuleProxy::emitQuickHelpChanged()
0376 // X {
0377 // X     emit quickHelpChanged();
0378 // X }
0379 
0380 /***************************************************************/
0381 #include "moc_kcmoduleproxy.cpp"