File indexing completed on 2024-05-12 17:07:12

0001 /*
0002     SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <kde@broulik.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "modulesmodel.h"
0008 
0009 #include <QCollator>
0010 
0011 #include <KConfig>
0012 #include <KConfigGroup>
0013 #include <KPluginInfo>
0014 #include <KServiceTypeTrader>
0015 
0016 #include <algorithm>
0017 
0018 #include "debug.h"
0019 
0020 ModulesModel::ModulesModel(QObject *parent)
0021     : QAbstractListModel(parent)
0022 {
0023 }
0024 
0025 ModulesModel::~ModulesModel() = default;
0026 
0027 int ModulesModel::rowCount(const QModelIndex &parent) const
0028 {
0029     if (parent.isValid()) {
0030         return 0;
0031     }
0032 
0033     return m_data.count();
0034 }
0035 
0036 QVariant ModulesModel::data(const QModelIndex &index, int role) const
0037 {
0038     if (!checkIndex(index)) {
0039         return QVariant();
0040     }
0041 
0042     const auto &item = m_data.at(index.row());
0043 
0044     switch (role) {
0045     case Qt::DisplayRole:
0046         return item.display;
0047     case DescriptionRole:
0048         return item.description;
0049     case TypeRole:
0050         return item.type;
0051     case AutoloadEnabledRole:
0052         if (item.type == KDEDConfig::AutostartType) {
0053             return item.autoloadEnabled;
0054         }
0055         return QVariant();
0056     case StatusRole: {
0057         if (!m_runningModulesKnown) {
0058             return KDEDConfig::UnknownStatus;
0059         }
0060         if (m_runningModules.contains(item.moduleName)) {
0061             return KDEDConfig::Running;
0062         }
0063         return KDEDConfig::NotRunning;
0064     }
0065     case ModuleNameRole:
0066         return item.moduleName;
0067     case ImmutableRole:
0068         return item.immutable;
0069     }
0070 
0071     return QVariant();
0072 }
0073 
0074 bool ModulesModel::representsDefault() const
0075 {
0076     bool isDefault = true;
0077     for (int i = 0; i < m_data.count(); ++i) {
0078         auto &item = m_data[i];
0079         if (item.type != KDEDConfig::AutostartType || item.immutable) {
0080             continue;
0081         }
0082         isDefault &= item.autoloadEnabled;
0083     }
0084     return isDefault;
0085 }
0086 
0087 bool ModulesModel::needsSave() const
0088 {
0089     bool save = false;
0090     for (int i = 0; i < m_data.count(); ++i) {
0091         auto &item = m_data[i];
0092         if (item.type != KDEDConfig::AutostartType || item.immutable) {
0093             continue;
0094         }
0095         save |= item.autoloadEnabled != item.savedAutoloadEnabled;
0096     }
0097     return save;
0098 }
0099 
0100 bool ModulesModel::setData(const QModelIndex &index, const QVariant &value, int role)
0101 {
0102     bool dirty = false;
0103 
0104     if (!checkIndex(index)) {
0105         return dirty;
0106     }
0107 
0108     auto &item = m_data[index.row()];
0109 
0110     if (item.type != KDEDConfig::AutostartType || item.immutable) {
0111         return dirty;
0112     }
0113 
0114     switch (role) {
0115     case AutoloadEnabledRole: {
0116         const bool autoloadEnabled = value.toBool();
0117         if (item.autoloadEnabled != autoloadEnabled) {
0118             item.autoloadEnabled = autoloadEnabled;
0119             dirty = true;
0120         }
0121         Q_EMIT autoloadedModulesChanged();
0122         break;
0123     }
0124     }
0125 
0126     if (dirty) {
0127         Q_EMIT dataChanged(index, index, {role});
0128     }
0129 
0130     return dirty;
0131 }
0132 
0133 QHash<int, QByteArray> ModulesModel::roleNames() const
0134 {
0135     return {
0136         {Qt::DisplayRole, QByteArrayLiteral("display")},
0137         {DescriptionRole, QByteArrayLiteral("description")},
0138         {TypeRole, QByteArrayLiteral("type")},
0139         {AutoloadEnabledRole, QByteArrayLiteral("autoloadEnabled")},
0140         {StatusRole, QByteArrayLiteral("status")},
0141         {ModuleNameRole, QByteArrayLiteral("moduleName")},
0142         {ImmutableRole, QByteArrayLiteral("immutable")},
0143     };
0144 }
0145 
0146 // This code was copied from kded.cpp
0147 // TODO: move this KCM to the KDED framework and share the code?
0148 static QVector<KPluginMetaData> availableModules()
0149 {
0150     QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("kf5/kded"));
0151     QSet<QString> moduleIds;
0152     for (const KPluginMetaData &md : qAsConst(plugins)) {
0153         moduleIds.insert(md.pluginId());
0154     }
0155 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0156     // also search for old .desktop based kded modules
0157     const KPluginInfo::List oldStylePlugins = KPluginInfo::fromServices(KServiceTypeTrader::self()->query(QStringLiteral("KDEDModule")));
0158     for (const KPluginInfo &info : oldStylePlugins) {
0159         if (moduleIds.contains(info.pluginName())) {
0160             qCWarning(KCM_KDED).nospace() << "kded module " << info.pluginName()
0161                                           << " has already been found using "
0162                                              "JSON metadata, please don't install the now unneeded .desktop file ("
0163                                           << info.entryPath() << ").";
0164         } else {
0165             qCDebug(KCM_KDED).nospace() << "kded module " << info.pluginName() << " still uses .desktop files (" << info.entryPath()
0166                                         << "). Please port it to JSON metadata.";
0167             plugins.append(info.toMetaData());
0168         }
0169     }
0170 #endif
0171     return plugins;
0172 }
0173 
0174 // this code was copied from kded.cpp
0175 static bool isModuleLoadedOnDemand(const KPluginMetaData &module)
0176 {
0177     bool loadOnDemand = true;
0178     // use toVariant() since it could be string or bool in the json and QJsonObject does not convert
0179     QVariant p = module.rawData().value(QStringLiteral("X-KDE-Kded-load-on-demand")).toVariant();
0180     if (p.isValid() && p.canConvert<bool>() && (p.toBool() == false)) {
0181         loadOnDemand = false;
0182     }
0183     return loadOnDemand;
0184 }
0185 
0186 void ModulesModel::load()
0187 {
0188     beginResetModel();
0189 
0190     m_data.clear();
0191 
0192     KConfig kdedrc(QStringLiteral("kded5rc"), KConfig::NoGlobals);
0193 
0194     QStringList knownModules;
0195 
0196     QVector<ModulesModelData> autostartModules;
0197     QVector<ModulesModelData> onDemandModules;
0198 
0199     const auto modules = availableModules();
0200     for (const KPluginMetaData &module : modules) {
0201         QString servicePath = module.metaDataFileName();
0202 
0203         // autoload defaults to false if it is not found
0204         const bool autoload = module.rawData().value(QStringLiteral("X-KDE-Kded-autoload")).toVariant().toBool();
0205 
0206         // keep estimating dbusModuleName in sync with KDEDModule (kdbusaddons) and kded (kded)
0207         // currently (KF5) the module name in the D-Bus object path is set by the pluginId
0208         const QString dbusModuleName = module.pluginId();
0209         qCDebug(KCM_KDED) << "reading kded info from" << servicePath << "autoload =" << autoload << "dbus module name =" << dbusModuleName;
0210 
0211         if (knownModules.contains(dbusModuleName)) {
0212             continue;
0213         }
0214 
0215         knownModules.append(dbusModuleName);
0216 
0217         KConfigGroup cg(&kdedrc, QStringLiteral("Module-%1").arg(dbusModuleName));
0218         const bool autoloadEnabled = cg.readEntry("autoload", true);
0219         const bool immutable = cg.isEntryImmutable("autoload");
0220 
0221         ModulesModelData data{module.name(), module.description(), KDEDConfig::UnknownType, autoloadEnabled, dbusModuleName, immutable, autoloadEnabled};
0222 
0223         // The logic has to be identical to Kded::initModules.
0224         // They interpret X-KDE-Kded-autoload as false if not specified
0225         //                X-KDE-Kded-load-on-demand as true if not specified
0226         if (autoload) {
0227             data.type = KDEDConfig::AutostartType;
0228             autostartModules << data;
0229         } else if (isModuleLoadedOnDemand(module)) {
0230             data.type = KDEDConfig::OnDemandType;
0231             onDemandModules << data;
0232         } else {
0233             qCWarning(KCM_KDED) << "kcmkded: Module " << module.name() << "from file" << module.metaDataFileName()
0234                                 << " not loaded on demand or startup! Skipping.";
0235             continue;
0236         }
0237     }
0238 
0239     QCollator collator;
0240     // Otherwise "Write" daemon with quotes will be at the top
0241     collator.setIgnorePunctuation(true);
0242     auto sortAlphabetically = [&collator](const ModulesModelData &a, const ModulesModelData &b) {
0243         return collator.compare(a.display, b.display) < 0;
0244     };
0245 
0246     std::sort(autostartModules.begin(), autostartModules.end(), sortAlphabetically);
0247     std::sort(onDemandModules.begin(), onDemandModules.end(), sortAlphabetically);
0248 
0249     m_data << autostartModules << onDemandModules;
0250 
0251     endResetModel();
0252 }
0253 
0254 bool ModulesModel::runningModulesKnown() const
0255 {
0256     return m_runningModulesKnown;
0257 }
0258 
0259 void ModulesModel::setRunningModulesKnown(bool known)
0260 {
0261     if (m_runningModulesKnown != known) {
0262         m_runningModulesKnown = known;
0263         Q_EMIT dataChanged(index(0, 0), index(m_data.count() - 1, 0), {StatusRole});
0264     }
0265 }
0266 
0267 QStringList ModulesModel::runningModules() const
0268 {
0269     return m_runningModules;
0270 }
0271 
0272 void ModulesModel::setRunningModules(const QStringList &runningModules)
0273 {
0274     if (m_runningModules == runningModules) {
0275         return;
0276     }
0277 
0278     m_runningModules = runningModules;
0279     if (m_runningModulesKnown) {
0280         Q_EMIT dataChanged(index(0, 0), index(m_data.count() - 1, 0), {StatusRole});
0281     }
0282 }
0283 
0284 void ModulesModel::refreshAutoloadEnabledSavedState()
0285 {
0286     for (int i = 0; i < m_data.count(); ++i) {
0287         auto &item = m_data[i];
0288         item.savedAutoloadEnabled = item.autoloadEnabled;
0289     }
0290 }