File indexing completed on 2024-05-05 17:39:47

0001 /*
0002     SPDX-FileCopyrightText: 2020 Marco Martin <mart@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "SensorFaceController.h"
0008 #include "SensorFaceController_p.h"
0009 #include "SensorFace_p.h"
0010 #include <Sensor.h>
0011 #include <SensorQuery.h>
0012 
0013 #include <QtQml>
0014 
0015 #include <KConfigLoader>
0016 #include <KConfigPropertyMap>
0017 #include <KDesktopFile>
0018 #include <KLocalizedString>
0019 #include <KPackage/PackageLoader>
0020 #include <KPluginMetaData>
0021 #include <Solid/Block>
0022 #include <Solid/Device>
0023 #include <Solid/Predicate>
0024 #include <Solid/StorageAccess>
0025 #include <Solid/StorageVolume>
0026 
0027 using namespace KSysGuard;
0028 
0029 FacesModel::FacesModel(QObject *parent)
0030     : QStandardItemModel(parent)
0031 {
0032     reload();
0033 }
0034 
0035 void FacesModel::reload()
0036 {
0037     clear();
0038 
0039     auto list = KPackage::PackageLoader::self()->listPackages(QStringLiteral("KSysguard/SensorFace"));
0040     // NOTE: This will disable completely the internal in-memory cache
0041     KPackage::Package p;
0042     p.install(QString(), QString());
0043 
0044     for (auto plugin : list) {
0045         QStandardItem *item = new QStandardItem(plugin.name());
0046         item->setData(plugin.pluginId(), FacesModel::PluginIdRole);
0047         appendRow(item);
0048     }
0049 }
0050 
0051 QString FacesModel::pluginId(int row)
0052 {
0053     return data(index(row, 0), PluginIdRole).toString();
0054 }
0055 
0056 QHash<int, QByteArray> FacesModel::roleNames() const
0057 {
0058     QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
0059 
0060     roles[PluginIdRole] = "pluginId";
0061     return roles;
0062 }
0063 
0064 PresetsModel::PresetsModel(QObject *parent)
0065     : QStandardItemModel(parent)
0066 {
0067     reload();
0068 }
0069 
0070 void PresetsModel::reload()
0071 {
0072     clear();
0073     QList<KPluginMetaData> plugins =
0074         KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), [](const KPluginMetaData &plugin) {
0075             return plugin.value(QStringLiteral("X-Plasma-RootPath")) == QStringLiteral("org.kde.plasma.systemmonitor");
0076         });
0077 
0078     QSet<QString> usedNames;
0079 
0080     // We iterate backwards because packages under ~/.local are listed first, while we want them last
0081     auto it = plugins.rbegin();
0082     for (; it != plugins.rend(); ++it) {
0083         const auto &plugin = *it;
0084         KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), plugin.pluginId());
0085         auto metadata = p.metadata();
0086 
0087         QString baseName = metadata.name();
0088         QString name = baseName;
0089         int id = 0;
0090 
0091         while (usedNames.contains(name)) {
0092             name = baseName + QStringLiteral(" (") + QString::number(++id) + QStringLiteral(")");
0093         }
0094         usedNames << name;
0095 
0096         QStandardItem *item = new QStandardItem(baseName);
0097 
0098         // TODO config
0099         QVariantMap config;
0100 
0101         KConfigGroup configGroup(KSharedConfig::openConfig(p.filePath("config", QStringLiteral("faceproperties")), KConfig::SimpleConfig),
0102                                  QStringLiteral("Config"));
0103 
0104         const QStringList keys = configGroup.keyList();
0105         for (const QString &key : keys) {
0106             // all strings for now, type conversion happens in QML side when we have the config property map
0107             config.insert(key, configGroup.readEntry(key));
0108         }
0109 
0110         item->setData(plugin.pluginId(), PresetsModel::PluginIdRole);
0111         item->setData(config, PresetsModel::ConfigRole);
0112 
0113         item->setData(QFileInfo(p.metadata().metaDataFileName()).isWritable(), PresetsModel::WritableRole);
0114 
0115         appendRow(item);
0116     }
0117 }
0118 
0119 QHash<int, QByteArray> PresetsModel::roleNames() const
0120 {
0121     QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
0122 
0123     roles[PluginIdRole] = "pluginId";
0124     roles[ConfigRole] = "config";
0125     roles[WritableRole] = "writable";
0126     return roles;
0127 }
0128 
0129 SensorResolver::SensorResolver(SensorFaceController *_controller, const QJsonArray &_expected)
0130     : controller(_controller)
0131     , expected(_expected)
0132 {
0133 }
0134 
0135 void SensorResolver::execute()
0136 {
0137     std::transform(expected.begin(), expected.end(), std::back_inserter(queries), [this](const QJsonValue &entry) {
0138         auto query = new SensorQuery{entry.toString()};
0139         query->connect(query, &KSysGuard::SensorQuery::finished, controller, [this](SensorQuery *query) {
0140             query->sortByName();
0141             query->deleteLater();
0142 
0143             const auto ids = query->sensorIds();
0144             if (ids.isEmpty()) {
0145                 missing.append(query->path());
0146             } else {
0147                 std::transform(ids.begin(), ids.end(), std::back_inserter(found), [](const QString &id) {
0148                     return id;
0149                 });
0150             }
0151 
0152             queries.removeOne(query);
0153             if (queries.isEmpty()) {
0154                 callback(this);
0155             }
0156         });
0157         query->execute();
0158         return query;
0159     });
0160 }
0161 
0162 QVector<QPair<QRegularExpression, QString>> KSysGuard::SensorFaceControllerPrivate::sensorIdReplacements;
0163 QRegularExpression SensorFaceControllerPrivate::oldDiskSensor = QRegularExpression(QStringLiteral("^disk\\/(.+)_\\(\\d+:\\d+\\)"));
0164 QRegularExpression SensorFaceControllerPrivate::oldPartitionSensor = QRegularExpression(QStringLiteral("^partitions(\\/.+)\\/"));
0165 
0166 SensorFaceControllerPrivate::SensorFaceControllerPrivate()
0167 {
0168     if (SensorFaceControllerPrivate::sensorIdReplacements.isEmpty()) {
0169         // A list of conversion rules to convert old sensor ids to new ones.
0170         // When loading, each regular expression tries to match to the sensor
0171         // id. If it matches, it will be be used to replace the sensor id with
0172         // the second argument.
0173         sensorIdReplacements = {
0174             {QRegularExpression(QStringLiteral("network/interfaces/(.*)")), QStringLiteral("network/\\1")},
0175             {QRegularExpression(QStringLiteral("network/all/receivedDataRate$")), QStringLiteral("network/all/download")},
0176             {QRegularExpression(QStringLiteral("network/all/sentDataRate$")), QStringLiteral("network/all/upload")},
0177             {QRegularExpression(QStringLiteral("network/all/totalReceivedData$")), QStringLiteral("network/all/totalDownload")},
0178             {QRegularExpression(QStringLiteral("network/all/totalSentData$")), QStringLiteral("network/all/totalUpload")},
0179             {QRegularExpression(QStringLiteral("(.*)/receiver/data$")), QStringLiteral("\\1/download")},
0180             {QRegularExpression(QStringLiteral("(.*)/transmitter/data$")), QStringLiteral("\\1/upload")},
0181             {QRegularExpression(QStringLiteral("(.*)/receiver/dataTotal$")), QStringLiteral("\\1/totalDownload")},
0182             {QRegularExpression(QStringLiteral("(.*)/transmitter/dataTotal$")), QStringLiteral("\\1/totalUpload")},
0183             {QRegularExpression(QStringLiteral("(.*)/Rate/rio")), QStringLiteral("\\1/read")},
0184             {QRegularExpression(QStringLiteral("(.*)/Rate/wio$")), QStringLiteral("\\1/write")},
0185             {QRegularExpression(QStringLiteral("(.*)/freespace$")), QStringLiteral("\\1/free")},
0186             {QRegularExpression(QStringLiteral("(.*)/filllevel$")), QStringLiteral("\\1/usedPercent")},
0187             {QRegularExpression(QStringLiteral("(.*)/usedspace$")), QStringLiteral("\\1/used")},
0188             {QRegularExpression(QStringLiteral("cpu/system/(.*)$")), QStringLiteral("cpu/all/\\1")},
0189             {QRegularExpression(QStringLiteral("cpu/(.*)/sys$")), QStringLiteral("cpu/\\1/system")},
0190             {QRegularExpression(QStringLiteral("cpu/(.*)/TotalLoad$")), QStringLiteral("cpu/\\1/usage")},
0191             {QRegularExpression(QStringLiteral("cpu/cpu(\\d+)/clock$")), QStringLiteral("cpu/cpu\\1/frequency")},
0192             {QRegularExpression(QStringLiteral("mem/(.*)level")), QStringLiteral("mem/\\1Percent")},
0193             {QRegularExpression(QStringLiteral("mem/physical/allocated")), QStringLiteral("memory/physical/used")},
0194             {QRegularExpression(QStringLiteral("mem/physical/available")), QStringLiteral("memory/physical/free")},
0195             {QRegularExpression(QStringLiteral("mem/physical/buf")), QStringLiteral("memory/physical/buffer")},
0196             {QRegularExpression(QStringLiteral("mem/physical/cached")), QStringLiteral("memory/physical/cache")},
0197             {QRegularExpression(QStringLiteral("^mem/(.*)")), QStringLiteral("memory/\\1")},
0198             {QRegularExpression(QStringLiteral("nvidia/(.*)/temperature$")), QStringLiteral("gpu/\\1/temperature")},
0199             {QRegularExpression(QStringLiteral("nvidia/(.*)/memoryClock$")), QStringLiteral("gpu/\\1/memoryFrequency")},
0200             {QRegularExpression(QStringLiteral("nvidia/(.*)/processorClock$")), QStringLiteral("gpu/\\1/coreFrequency")},
0201             {QRegularExpression(QStringLiteral("nvidia/(.*)/(memory|sharedMemory)$")), QStringLiteral("gpu/\\1/usedVram")},
0202             {QRegularExpression(QStringLiteral("nvidia/(.*)/(encoderUsage|decoderUsage)$")), QStringLiteral("gpu/\\1/usage")},
0203             {QRegularExpression(QStringLiteral("^(uptime|system/uptime/uptime)$")), QStringLiteral("os/system/uptime")},
0204         };
0205     }
0206 }
0207 
0208 QString SensorFaceControllerPrivate::replaceDiskId(const QString &entryName) const
0209 {
0210     const auto match = oldDiskSensor.match(entryName);
0211     if (!match.hasMatch()) {
0212         return entryName;
0213     }
0214     const QString device = match.captured(1);
0215     Solid::Predicate predicate(Solid::DeviceInterface::StorageAccess);
0216     predicate &= Solid::Predicate(Solid::DeviceInterface::Block, QStringLiteral("device"), QStringLiteral("/dev/%1").arg(device));
0217     const auto devices = Solid::Device::listFromQuery(predicate);
0218     if (devices.empty()) {
0219         return QString();
0220     }
0221     QString sensorId = entryName;
0222     const auto volume = devices[0].as<Solid::StorageVolume>();
0223     const QString id = volume->uuid().isEmpty() ? volume->label() : volume->uuid();
0224     return sensorId.replace(match.captured(0), QStringLiteral("disk/") + id);
0225 }
0226 
0227 QString SensorFaceControllerPrivate::replacePartitionId(const QString &entryName) const
0228 {
0229     const auto match = oldPartitionSensor.match(entryName);
0230     if (!match.hasMatch()) {
0231         return entryName;
0232     }
0233     QString sensorId = entryName;
0234 
0235     if (match.captured(1) == QLatin1String("/all")) {
0236         return sensorId.replace(match.captured(0), QStringLiteral("disk/all/"));
0237     }
0238 
0239     const QString filePath = match.captured(1) == QLatin1String("/__root__") ? QStringLiteral("/") : match.captured(1);
0240     const Solid::Predicate predicate(Solid::DeviceInterface::StorageAccess, QStringLiteral("filePath"), filePath);
0241     const auto devices = Solid::Device::listFromQuery(predicate);
0242     if (devices.empty()) {
0243         return entryName;
0244     }
0245     const auto volume = devices[0].as<Solid::StorageVolume>();
0246     const QString id = volume->uuid().isEmpty() ? volume->label() : volume->uuid();
0247     return sensorId.replace(match.captured(0), QStringLiteral("disk/%1/").arg(id));
0248 }
0249 
0250 QJsonArray SensorFaceControllerPrivate::readSensors(const KConfigGroup &read, const QString &entryName)
0251 {
0252     auto original = QJsonDocument::fromJson(read.readEntry(entryName, QString()).toUtf8()).array();
0253     QJsonArray newSensors;
0254     for (auto entry : original) {
0255         QString sensorId = entry.toString();
0256         for (auto replacement : std::as_const(sensorIdReplacements)) {
0257             auto match = replacement.first.match(sensorId);
0258             if (match.hasMatch()) {
0259                 sensorId.replace(replacement.first, replacement.second);
0260             }
0261         }
0262         sensorId = replaceDiskId(sensorId);
0263         sensorId = replacePartitionId(sensorId);
0264         newSensors.append(sensorId);
0265     }
0266 
0267     return newSensors;
0268 }
0269 
0270 QJsonArray SensorFaceControllerPrivate::readAndUpdateSensors(KConfigGroup &config, const QString &entryName)
0271 {
0272     auto original = QJsonDocument::fromJson(config.readEntry(entryName, QString()).toUtf8()).array();
0273 
0274     const KConfigGroup &group = config;
0275     auto newSensors = readSensors(group, entryName);
0276 
0277     if (newSensors != original) {
0278         config.writeEntry(entryName, QJsonDocument(newSensors).toJson(QJsonDocument::Compact));
0279     }
0280 
0281     return newSensors;
0282 }
0283 
0284 void SensorFaceControllerPrivate::resolveSensors(const QJsonArray &partialEntries, std::function<void(SensorResolver *)> callback)
0285 {
0286     auto resolver = new SensorResolver{q, partialEntries};
0287     resolver->callback = [this, callback](SensorResolver *resolver) {
0288         callback(resolver);
0289 
0290         if (!resolver->missing.isEmpty()) {
0291             for (const auto &entry : std::as_const(resolver->missing)) {
0292                 missingSensors.append(entry);
0293             }
0294             Q_EMIT q->missingSensorsChanged();
0295         }
0296 
0297         delete resolver;
0298     };
0299     resolver->execute();
0300 }
0301 
0302 SensorFace *SensorFaceControllerPrivate::createGui(const QString &qmlPath)
0303 {
0304     QQmlComponent *component = new QQmlComponent(engine, qmlPath, nullptr);
0305     // TODO: eventually support async  components? (only useful for qml files from http, we probably don't want that)
0306     if (component->status() != QQmlComponent::Ready) {
0307         qCritical() << "Error creating component:";
0308         for (auto err : component->errors()) {
0309             qWarning() << err.toString();
0310         }
0311         component->deleteLater();
0312         return nullptr;
0313     }
0314 
0315     QQmlContext *context = new QQmlContext(engine);
0316     context->setContextObject(contextObj);
0317     QObject *guiObject = component->beginCreate(context);
0318     SensorFace *gui = qobject_cast<SensorFace *>(guiObject);
0319     if (!gui) {
0320         qWarning() << "ERROR: QML gui" << guiObject << "not a SensorFace instance";
0321         guiObject->deleteLater();
0322         context->deleteLater();
0323         return nullptr;
0324     }
0325     context->setParent(gui);
0326 
0327     gui->setController(q);
0328     gui->setParent(q);
0329 
0330     component->completeCreate();
0331 
0332     component->deleteLater();
0333     return gui;
0334 }
0335 
0336 QQuickItem *SensorFaceControllerPrivate::createConfigUi(const QString &file, const QVariantMap &initialProperties)
0337 {
0338     QQmlComponent *component = new QQmlComponent(engine, file, nullptr);
0339     // TODO: eventually support async  components? (only useful for qml files from http, we probably don't want that)
0340     if (component->status() != QQmlComponent::Ready) {
0341         qCritical() << "Error creating component:";
0342         for (auto err : component->errors()) {
0343             qWarning() << err.toString();
0344         }
0345         component->deleteLater();
0346         return nullptr;
0347     }
0348 
0349     QQmlContext *context = new QQmlContext(engine);
0350     context->setContextObject(contextObj);
0351     QObject *guiObject = component->createWithInitialProperties(initialProperties, context);
0352     QQuickItem *gui = qobject_cast<QQuickItem *>(guiObject);
0353     Q_ASSERT(gui);
0354     context->setParent(gui);
0355     gui->setParent(q);
0356 
0357     component->deleteLater();
0358 
0359     return gui;
0360 }
0361 
0362 SensorFaceController::SensorFaceController(KConfigGroup &config, QQmlEngine *engine)
0363     : QObject(engine)
0364     , d(std::make_unique<SensorFaceControllerPrivate>())
0365 {
0366     d->q = this;
0367     d->configGroup = config;
0368     d->appearanceGroup = KConfigGroup(&config, "Appearance");
0369     d->sensorsGroup = KConfigGroup(&config, "Sensors");
0370     d->colorsGroup = KConfigGroup(&config, "SensorColors");
0371     d->labelsGroup = KConfigGroup(&config, "SensorLabels");
0372     d->engine = engine;
0373     d->syncTimer = new QTimer(this);
0374     d->syncTimer->setSingleShot(true);
0375     d->syncTimer->setInterval(5000);
0376     connect(d->syncTimer, &QTimer::timeout, this, [this]() {
0377         if (!d->shouldSync) {
0378             return;
0379         }
0380         d->appearanceGroup.sync();
0381         d->sensorsGroup.sync();
0382     });
0383 
0384     d->contextObj = new KLocalizedContext(this);
0385 
0386     d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("totalSensors")), [this](SensorResolver *resolver) {
0387         d->totalSensors = resolver->found;
0388         Q_EMIT totalSensorsChanged();
0389     });
0390     d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("lowPrioritySensorIds")), [this](SensorResolver *resolver) {
0391         d->lowPrioritySensorIds = resolver->found;
0392         Q_EMIT lowPrioritySensorIdsChanged();
0393     });
0394     d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("highPrioritySensorIds")), [this](SensorResolver *resolver) {
0395         d->highPrioritySensorIds = resolver->found;
0396         Q_EMIT highPrioritySensorIdsChanged();
0397     });
0398 
0399     setFaceId(d->appearanceGroup.readEntry("chartFace", QStringLiteral("org.kde.ksysguard.piechart")));
0400 }
0401 
0402 SensorFaceController::~SensorFaceController()
0403 {
0404     auto forceSave = d->faceProperties.readEntry(QStringLiteral("ForceSaveOnDestroy"), false);
0405     if (!forceSave) {
0406         if (!d->shouldSync) {
0407             // If we should not sync automatically, clear all changes before we
0408             // destroy the config objects, otherwise they will be written during
0409             // destruction.
0410             d->appearanceGroup.markAsClean();
0411             d->colorsGroup.markAsClean();
0412             d->labelsGroup.markAsClean();
0413             if (d->faceConfigLoader && d->faceConfigLoader->isSaveNeeded()) {
0414                 d->faceConfigLoader->load();
0415             }
0416         }
0417     } else {
0418         d->faceConfigLoader->save();
0419     }
0420 }
0421 
0422 KConfigGroup KSysGuard::SensorFaceController::configGroup() const
0423 {
0424     return d->configGroup;
0425 }
0426 
0427 QString SensorFaceController::title() const
0428 {
0429     // both Title and title can exist to allow i18n of Title
0430     if (d->appearanceGroup.hasKey("title")) {
0431         return d->appearanceGroup.readEntry("title");
0432     } else {
0433         // if neither exist fall back to name
0434         return d->appearanceGroup.readEntry("Title", i18n("System Monitor Sensor"));
0435     }
0436 }
0437 
0438 void SensorFaceController::setTitle(const QString &title)
0439 {
0440     if (title == SensorFaceController::title()) {
0441         return;
0442     }
0443 
0444     d->appearanceGroup.writeEntry("title", title);
0445     d->syncTimer->start();
0446 
0447     Q_EMIT titleChanged();
0448 }
0449 
0450 bool SensorFaceController::showTitle() const
0451 {
0452     return d->appearanceGroup.readEntry("showTitle", true);
0453 }
0454 
0455 void SensorFaceController::setShowTitle(bool show)
0456 {
0457     if (show == showTitle()) {
0458         return;
0459     }
0460 
0461     d->appearanceGroup.writeEntry("showTitle", show);
0462     d->syncTimer->start();
0463 
0464     Q_EMIT showTitleChanged();
0465 }
0466 
0467 QJsonArray SensorFaceController::totalSensors() const
0468 {
0469     return d->totalSensors;
0470 }
0471 
0472 void SensorFaceController::setTotalSensors(const QJsonArray &totalSensors)
0473 {
0474     if (totalSensors == d->totalSensors) {
0475         return;
0476     }
0477     const auto currentEntry = QJsonDocument::fromJson(d->sensorsGroup.readEntry("totalSensors").toUtf8()).array();
0478     if (totalSensors == currentEntry) {
0479         return;
0480     }
0481     d->sensorsGroup.writeEntry("totalSensors", QJsonDocument(totalSensors).toJson(QJsonDocument::Compact));
0482     // Until we have resolved
0483     d->totalSensors = totalSensors;
0484     d->syncTimer->start();
0485     Q_EMIT totalSensorsChanged();
0486     d->resolveSensors(totalSensors, [this](SensorResolver *resolver) {
0487         if (resolver->found == d->totalSensors) {
0488             return;
0489         }
0490         d->totalSensors = resolver->found;
0491         Q_EMIT totalSensorsChanged();
0492     });
0493 }
0494 
0495 QJsonArray SensorFaceController::highPrioritySensorIds() const
0496 {
0497     return d->highPrioritySensorIds;
0498 }
0499 
0500 void SensorFaceController::setHighPrioritySensorIds(const QJsonArray &highPrioritySensorIds)
0501 {
0502     if (highPrioritySensorIds == d->highPrioritySensorIds) {
0503         return;
0504     }
0505     const auto currentEntry = QJsonDocument::fromJson(d->sensorsGroup.readEntry("highPrioritySensorIds").toUtf8()).array();
0506     if (highPrioritySensorIds == currentEntry) {
0507         return;
0508     }
0509     d->sensorsGroup.writeEntry("highPrioritySensorIds", QJsonDocument(highPrioritySensorIds).toJson(QJsonDocument::Compact));
0510     // Until we have resolved
0511     d->syncTimer->start();
0512     d->highPrioritySensorIds = highPrioritySensorIds;
0513     Q_EMIT highPrioritySensorIdsChanged();
0514     d->resolveSensors(highPrioritySensorIds, [this](SensorResolver *resolver) {
0515         if (resolver->found == d->highPrioritySensorIds) {
0516             return;
0517         }
0518         d->highPrioritySensorIds = resolver->found;
0519         Q_EMIT highPrioritySensorIdsChanged();
0520     });
0521 }
0522 
0523 QJsonArray KSysGuard::SensorFaceController::missingSensors() const
0524 {
0525     return d->missingSensors;
0526 }
0527 
0528 QVariantMap SensorFaceController::sensorColors() const
0529 {
0530     QVariantMap colors;
0531     for (const auto &key : d->colorsGroup.keyList()) {
0532         QColor color = d->colorsGroup.readEntry(key, QColor());
0533 
0534         if (color.isValid()) {
0535             colors[key] = color;
0536         }
0537     }
0538     return colors;
0539 }
0540 
0541 void SensorFaceController::setSensorColors(const QVariantMap &colors)
0542 {
0543     if (colors == this->sensorColors()) {
0544         return;
0545     }
0546 
0547     d->colorsGroup.deleteGroup();
0548     d->colorsGroup = KConfigGroup(&d->configGroup, "SensorColors");
0549 
0550     auto it = colors.constBegin();
0551     for (; it != colors.constEnd(); ++it) {
0552         d->colorsGroup.writeEntry(it.key(), it.value());
0553     }
0554 
0555     d->syncTimer->start();
0556     Q_EMIT sensorColorsChanged();
0557 }
0558 
0559 QVariantMap SensorFaceController::sensorLabels() const
0560 {
0561     QVariantMap labels;
0562     for (const auto &key : d->labelsGroup.keyList()) {
0563         labels[key] = d->labelsGroup.readEntry(key);
0564     }
0565     return labels;
0566 }
0567 
0568 void SensorFaceController::setSensorLabels(const QVariantMap &labels)
0569 {
0570     if (labels == this->sensorLabels()) {
0571         return;
0572     }
0573 
0574     d->labelsGroup.deleteGroup();
0575     d->labelsGroup = KConfigGroup(&d->configGroup, "SensorLabels");
0576 
0577     for (auto it = labels.cbegin(); it != labels.cend(); ++it) {
0578         const auto label = it.value().toString();
0579         if (!label.isEmpty()) {
0580             d->labelsGroup.writeEntry(it.key(), label);
0581         }
0582     }
0583 
0584     d->syncTimer->start();
0585     Q_EMIT sensorLabelsChanged();
0586 }
0587 
0588 QJsonArray SensorFaceController::lowPrioritySensorIds() const
0589 {
0590     return d->lowPrioritySensorIds;
0591 }
0592 
0593 void SensorFaceController::setLowPrioritySensorIds(const QJsonArray &lowPrioritySensorIds)
0594 {
0595     if (lowPrioritySensorIds == d->lowPrioritySensorIds) {
0596         return;
0597     }
0598     const auto currentEntry = QJsonDocument::fromJson(d->sensorsGroup.readEntry("lowPrioritySensorIds").toUtf8()).array();
0599     if (lowPrioritySensorIds == currentEntry) {
0600         return;
0601     }
0602     d->sensorsGroup.writeEntry("lowPrioritySensorIds", QJsonDocument(lowPrioritySensorIds).toJson(QJsonDocument::Compact));
0603     // Until we have resolved
0604     d->lowPrioritySensorIds = lowPrioritySensorIds;
0605     d->syncTimer->start();
0606     Q_EMIT lowPrioritySensorIdsChanged();
0607     d->resolveSensors(lowPrioritySensorIds, [this](SensorResolver *resolver) {
0608         if (resolver->found == d->lowPrioritySensorIds) {
0609             return;
0610         }
0611         d->lowPrioritySensorIds = resolver->found;
0612         Q_EMIT lowPrioritySensorIdsChanged();
0613     });
0614 }
0615 
0616 int SensorFaceController::updateRateLimit() const
0617 {
0618     return d->appearanceGroup.readEntry<int>(QStringLiteral("updateRateLimit"), 0);
0619 }
0620 
0621 void SensorFaceController::setUpdateRateLimit(int limit)
0622 {
0623     if (limit == updateRateLimit()) {
0624         return;
0625     }
0626 
0627     d->appearanceGroup.writeEntry("updateRateLimit", limit);
0628     d->syncTimer->start();
0629 
0630     Q_EMIT updateRateLimitChanged();
0631 }
0632 
0633 // from face config, immutable by the user
0634 QString SensorFaceController::name() const
0635 {
0636     return d->facePackage.metadata().name();
0637 }
0638 
0639 const QString SensorFaceController::icon() const
0640 {
0641     return d->facePackage.metadata().iconName();
0642 }
0643 
0644 bool SensorFaceController::supportsSensorsColors() const
0645 {
0646     return d->faceProperties.readEntry("SupportsSensorsColors", false);
0647 }
0648 
0649 bool SensorFaceController::supportsTotalSensors() const
0650 {
0651     return d->faceProperties.readEntry("SupportsTotalSensors", false);
0652 }
0653 
0654 bool SensorFaceController::supportsLowPrioritySensors() const
0655 {
0656     return d->faceProperties.readEntry("SupportsLowPrioritySensors", false);
0657 }
0658 
0659 int SensorFaceController::maxTotalSensors() const
0660 {
0661     return d->faceProperties.readEntry("MaxTotalSensors", 1);
0662 }
0663 
0664 void SensorFaceController::setFaceId(const QString &face)
0665 {
0666     if (d->faceId == face) {
0667         return;
0668     }
0669 
0670     if (d->fullRepresentation) {
0671         d->fullRepresentation->deleteLater();
0672         d->fullRepresentation.clear();
0673     }
0674     if (d->compactRepresentation) {
0675         d->compactRepresentation->deleteLater();
0676         d->compactRepresentation.clear();
0677     }
0678     if (d->faceConfigUi) {
0679         d->faceConfigUi->deleteLater();
0680         d->faceConfigUi.clear();
0681     }
0682 
0683     d->faceId = face;
0684 
0685     d->facePackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KSysguard/SensorFace"), face);
0686 
0687     if (d->faceConfiguration) {
0688         d->faceConfiguration->deleteLater();
0689         d->faceConfiguration = nullptr;
0690     }
0691     if (d->faceConfigLoader) {
0692         d->faceConfigLoader->deleteLater();
0693         d->faceConfigLoader = nullptr;
0694     }
0695 
0696     if (!d->facePackage.isValid()) {
0697         Q_EMIT faceIdChanged();
0698         return;
0699     }
0700 
0701     d->contextObj->setTranslationDomain(QLatin1String("ksysguard_face_") + face);
0702 
0703     d->faceProperties = KConfigGroup(KSharedConfig::openConfig(d->facePackage.filePath("FaceProperties"), KConfig::SimpleConfig), QStringLiteral("Config"));
0704 
0705     reloadFaceConfiguration();
0706 
0707     d->appearanceGroup.writeEntry("chartFace", face);
0708     d->syncTimer->start();
0709     Q_EMIT faceIdChanged();
0710     return;
0711 }
0712 
0713 QString SensorFaceController::faceId() const
0714 {
0715     return d->faceId;
0716 }
0717 
0718 KConfigPropertyMap *SensorFaceController::faceConfiguration() const
0719 {
0720     return d->faceConfiguration;
0721 }
0722 
0723 QQuickItem *SensorFaceController::compactRepresentation()
0724 {
0725     if (!d->facePackage.isValid()) {
0726         return nullptr;
0727     } else if (d->compactRepresentation) {
0728         return d->compactRepresentation;
0729     }
0730 
0731     d->compactRepresentation = d->createGui(d->facePackage.filePath("ui", QStringLiteral("CompactRepresentation.qml")));
0732     return d->compactRepresentation;
0733 }
0734 
0735 QQuickItem *SensorFaceController::fullRepresentation()
0736 {
0737     if (!d->facePackage.isValid()) {
0738         return nullptr;
0739     } else if (d->fullRepresentation) {
0740         return d->fullRepresentation;
0741     }
0742 
0743     d->fullRepresentation = d->createGui(d->facePackage.filePath("ui", QStringLiteral("FullRepresentation.qml")));
0744     return d->fullRepresentation;
0745 }
0746 
0747 QQuickItem *SensorFaceController::faceConfigUi()
0748 {
0749     if (!d->facePackage.isValid()) {
0750         return nullptr;
0751     } else if (d->faceConfigUi) {
0752         return d->faceConfigUi;
0753     }
0754 
0755     const QString filePath = d->facePackage.filePath("ui", QStringLiteral("Config.qml"));
0756 
0757     if (filePath.isEmpty()) {
0758         return nullptr;
0759     }
0760 
0761     d->faceConfigUi = d->createConfigUi(QStringLiteral(":/FaceDetailsConfig.qml"),
0762                                         {{QStringLiteral("controller"), QVariant::fromValue(this)}, {QStringLiteral("source"), filePath}});
0763 
0764     if (d->faceConfigUi && !d->faceConfigUi->property("item").value<QQuickItem *>()) {
0765         d->faceConfigUi->deleteLater();
0766         d->faceConfigUi.clear();
0767     }
0768     return d->faceConfigUi;
0769 }
0770 
0771 QQuickItem *SensorFaceController::appearanceConfigUi()
0772 {
0773     if (d->appearanceConfigUi) {
0774         return d->appearanceConfigUi;
0775     }
0776 
0777     d->appearanceConfigUi = d->createConfigUi(QStringLiteral(":/ConfigAppearance.qml"), {{QStringLiteral("controller"), QVariant::fromValue(this)}});
0778 
0779     return d->appearanceConfigUi;
0780 }
0781 
0782 QQuickItem *SensorFaceController::sensorsConfigUi()
0783 {
0784     if (d->sensorsConfigUi) {
0785         return d->sensorsConfigUi;
0786     }
0787 
0788     if (d->faceProperties.readEntry("SupportsSensors", true)) {
0789         d->sensorsConfigUi = d->createConfigUi(QStringLiteral(":/ConfigSensors.qml"), {{QStringLiteral("controller"), QVariant::fromValue(this)}});
0790     } else {
0791         d->sensorsConfigUi = new QQuickItem;
0792     }
0793     return d->sensorsConfigUi;
0794 }
0795 
0796 QAbstractItemModel *SensorFaceController::availableFacesModel()
0797 {
0798     if (d->availableFacesModel) {
0799         return d->availableFacesModel;
0800     }
0801 
0802     d->availableFacesModel = new FacesModel(this);
0803     return d->availableFacesModel;
0804 }
0805 
0806 QAbstractItemModel *SensorFaceController::availablePresetsModel()
0807 {
0808     if (d->availablePresetsModel) {
0809         return d->availablePresetsModel;
0810     }
0811 
0812     d->availablePresetsModel = new PresetsModel(this);
0813 
0814     return d->availablePresetsModel;
0815 }
0816 
0817 void SensorFaceController::reloadConfig()
0818 {
0819     if (d->faceConfigLoader) {
0820         d->faceConfigLoader->load();
0821     }
0822 
0823     d->missingSensors = QJsonArray{};
0824     Q_EMIT missingSensorsChanged();
0825 
0826     d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("totalSensors")), [this](SensorResolver *resolver) {
0827         d->totalSensors = resolver->found;
0828         Q_EMIT totalSensorsChanged();
0829     });
0830     d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("lowPrioritySensorIds")), [this](SensorResolver *resolver) {
0831         d->lowPrioritySensorIds = resolver->found;
0832         Q_EMIT lowPrioritySensorIdsChanged();
0833     });
0834     d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("highPrioritySensorIds")), [this](SensorResolver *resolver) {
0835         d->highPrioritySensorIds = resolver->found;
0836         Q_EMIT highPrioritySensorIdsChanged();
0837     });
0838 
0839     // Force to re-read all the values
0840     setFaceId(d->appearanceGroup.readEntry("chartFace", QStringLiteral("org.kde.ksysguard.textonly")));
0841     Q_EMIT titleChanged();
0842     Q_EMIT sensorColorsChanged();
0843     Q_EMIT sensorLabelsChanged();
0844     Q_EMIT showTitleChanged();
0845     Q_EMIT updateRateLimitChanged();
0846 }
0847 
0848 void SensorFaceController::loadPreset(const QString &preset)
0849 {
0850     if (preset.isEmpty()) {
0851         return;
0852     }
0853 
0854     auto presetPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"));
0855 
0856     presetPackage.setPath(preset);
0857 
0858     if (!presetPackage.isValid()) {
0859         return;
0860     }
0861 
0862     if (presetPackage.metadata().value(QStringLiteral("X-Plasma-RootPath")) != QStringLiteral("org.kde.plasma.systemmonitor")) {
0863         return;
0864     }
0865 
0866     auto c = KSharedConfig::openConfig(presetPackage.filePath("config", QStringLiteral("faceproperties")), KConfig::SimpleConfig);
0867     const KConfigGroup presetGroup(c, QStringLiteral("Config"));
0868     const KConfigGroup colorsGroup(c, QStringLiteral("SensorColors"));
0869 
0870     // Load the title
0871     setTitle(presetPackage.metadata().name());
0872 
0873     // Remove the "custom" value from presets models
0874     if (d->availablePresetsModel && d->availablePresetsModel->data(d->availablePresetsModel->index(0, 0), PresetsModel::PluginIdRole).toString().isEmpty()) {
0875         d->availablePresetsModel->removeRow(0);
0876     }
0877 
0878     setTotalSensors(d->readSensors(presetGroup, QStringLiteral("totalSensors")));
0879     setHighPrioritySensorIds(d->readSensors(presetGroup, QStringLiteral("highPrioritySensorIds")));
0880     setLowPrioritySensorIds(d->readSensors(presetGroup, QStringLiteral("lowPrioritySensorIds")));
0881 
0882     setFaceId(presetGroup.readEntry(QStringLiteral("chartFace"), QStringLiteral("org.kde.ksysguard.piechart")));
0883 
0884     colorsGroup.copyTo(&d->colorsGroup);
0885     Q_EMIT sensorColorsChanged();
0886 
0887     if (d->faceConfigLoader) {
0888         KConfigGroup presetGroup(KSharedConfig::openConfig(presetPackage.filePath("FaceProperties"), KConfig::SimpleConfig), QStringLiteral("FaceConfig"));
0889 
0890         for (const QString &key : presetGroup.keyList()) {
0891             KConfigSkeletonItem *item = d->faceConfigLoader->findItemByName(key);
0892             if (item) {
0893                 if (item->property().type() == QVariant::StringList) {
0894                     item->setProperty(presetGroup.readEntry(key, QStringList()));
0895                 } else {
0896                     item->setProperty(presetGroup.readEntry(key));
0897                 }
0898                 d->faceConfigLoader->save();
0899                 d->faceConfigLoader->read();
0900             }
0901         }
0902     }
0903 }
0904 
0905 void SensorFaceController::savePreset()
0906 {
0907     QString pluginName = QStringLiteral("org.kde.plasma.systemmonitor.") + title().simplified().replace(QLatin1Char(' '), QStringLiteral("")).toLower();
0908     int suffix = 0;
0909 
0910     auto presetPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"));
0911 
0912     presetPackage.setPath(pluginName);
0913     if (presetPackage.isValid()) {
0914         do {
0915             presetPackage.setPath(QString());
0916             presetPackage.setPath(pluginName + QString::number(++suffix));
0917         } while (presetPackage.isValid());
0918 
0919         pluginName += QString::number(suffix);
0920     }
0921 
0922     QTemporaryDir dir;
0923     if (!dir.isValid()) {
0924         return;
0925     }
0926 
0927     // First write "new style" plugin JSON file.
0928     QJsonDocument json;
0929     json.setObject({
0930         {"KPlugin",
0931          QJsonObject{
0932              {"Id", pluginName},
0933              {"Name", title()},
0934              {"Icon", "ksysguardd"},
0935              {"Category", "System Information"},
0936              {"License", "LGPL 2.1+"},
0937              {"EnabledByDefault", true},
0938              {"Version", "0.1"},
0939              {"ServiceTypes", QJsonArray{{"Plasma/Applet"}}} //
0940          }},
0941         {"X-Plasma-API", "declarativeappletscript"},
0942         {"X-Plasma-MainScript", "ui/main.qml"},
0943         {"X-Plasma-Provides", "org.kde.plasma.systemmonitor"},
0944         {"X-Plasma-RootPath", "org.kde.plasma.systemmonitor"},
0945     });
0946 
0947     if (QFile file{dir.path() % QStringLiteral("/metadata.json")}; file.open(QIODevice::WriteOnly)) {
0948         file.write(json.toJson());
0949     } else {
0950         qWarning() << "Could not write metadata.json file for preset" << title();
0951     }
0952 
0953     // Backward compatibility: Also write out an "old style" desktop file so the
0954     // preset works with older Plasma.
0955     KConfig c(dir.path() % QStringLiteral("/metadata.desktop"));
0956 
0957     KConfigGroup cg(&c, "Desktop Entry");
0958     cg.writeEntry("Name", title());
0959     cg.writeEntry("Icon", "ksysguardd");
0960     cg.writeEntry("X-Plasma-API", "declarativeappletscript");
0961     cg.writeEntry("X-Plasma-MainScript", "ui/main.qml");
0962     cg.writeEntry("X-Plasma-Provides", "org.kde.plasma.systemmonitor");
0963     cg.writeEntry("X-Plasma-RootPath", "org.kde.plasma.systemmonitor");
0964     cg.writeEntry("X-KDE-PluginInfo-Name", pluginName);
0965     cg.writeEntry("X-KDE-ServiceTypes", "Plasma/Applet");
0966     cg.writeEntry("X-KDE-PluginInfo-Category", "System Information");
0967     cg.writeEntry("X-KDE-PluginInfo-License", "LGPL 2.1+");
0968     cg.writeEntry("X-KDE-PluginInfo-EnabledByDefault", "true");
0969     cg.writeEntry("X-KDE-PluginInfo-Version", "0.1");
0970     cg.sync();
0971 
0972     QDir subDir(dir.path());
0973     subDir.mkpath(QStringLiteral("contents/config"));
0974     KConfig faceConfig(subDir.path() % QStringLiteral("/contents/config/faceproperties"));
0975 
0976     KConfigGroup configGroup(&faceConfig, "Config");
0977 
0978     auto sensors = d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("totalSensors"));
0979     configGroup.writeEntry(QStringLiteral("totalSensors"), QJsonDocument(sensors).toJson(QJsonDocument::Compact));
0980     sensors = d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("highPrioritySensorIds"));
0981     configGroup.writeEntry(QStringLiteral("highPrioritySensorIds"), QJsonDocument(sensors).toJson(QJsonDocument::Compact));
0982     sensors = d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("lowPrioritySensorIds"));
0983     configGroup.writeEntry(QStringLiteral("lowPrioritySensorIds"), QJsonDocument(sensors).toJson(QJsonDocument::Compact));
0984     configGroup.writeEntry(QStringLiteral("chartFace"), faceId());
0985 
0986     KConfigGroup colorsGroup(&faceConfig, "SensorColors");
0987     d->colorsGroup.copyTo(&colorsGroup);
0988     colorsGroup.sync();
0989 
0990     configGroup = KConfigGroup(&faceConfig, "FaceConfig");
0991     if (d->faceConfigLoader) {
0992         const auto &items = d->faceConfigLoader->items();
0993         for (KConfigSkeletonItem *item : items) {
0994             configGroup.writeEntry(item->key(), item->property());
0995         }
0996     }
0997     configGroup.sync();
0998 
0999     auto *job = presetPackage.install(dir.path());
1000 
1001     connect(job, &KJob::finished, this, [this, pluginName]() {
1002         d->availablePresetsModel->reload();
1003     });
1004 }
1005 
1006 void SensorFaceController::uninstallPreset(const QString &pluginId)
1007 {
1008     auto presetPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), pluginId);
1009 
1010     if (presetPackage.metadata().value(QStringLiteral("X-Plasma-RootPath")) != QStringLiteral("org.kde.plasma.systemmonitor")) {
1011         return;
1012     }
1013 
1014     QDir root(presetPackage.path());
1015     root.cdUp();
1016     auto *job = presetPackage.uninstall(pluginId, root.path());
1017 
1018     connect(job, &KJob::finished, this, [this]() {
1019         d->availablePresetsModel->reload();
1020     });
1021 }
1022 
1023 bool SensorFaceController::shouldSync() const
1024 {
1025     return d->shouldSync;
1026 }
1027 
1028 void SensorFaceController::setShouldSync(bool sync)
1029 {
1030     d->shouldSync = sync;
1031     if (!d->shouldSync && d->syncTimer->isActive()) {
1032         d->syncTimer->stop();
1033     }
1034 }
1035 
1036 void SensorFaceController::reloadFaceConfiguration()
1037 {
1038     const QString xmlPath = d->facePackage.filePath("mainconfigxml");
1039 
1040     if (!xmlPath.isEmpty()) {
1041         QFile file(xmlPath);
1042         KConfigGroup cg(&d->configGroup, d->faceId);
1043 
1044         if (d->faceConfigLoader) {
1045             delete d->faceConfigLoader;
1046         }
1047 
1048         if (d->faceConfiguration) {
1049             delete d->faceConfiguration;
1050         }
1051 
1052         d->faceConfigLoader = new KConfigLoader(cg, &file, this);
1053         d->faceConfiguration = new KConfigPropertyMap(d->faceConfigLoader, this);
1054         connect(d->faceConfiguration, &KConfigPropertyMap::valueChanged, this, [this](const QString &key) {
1055             auto item = d->faceConfigLoader->findItemByName(key);
1056             if (item) {
1057                 item->writeConfig(d->faceConfigLoader->config());
1058             }
1059         });
1060 
1061         Q_EMIT faceConfigurationChanged();
1062     }
1063 }
1064 
1065 void KSysGuard::SensorFaceController::replaceSensors(const QString &from, const QString &to)
1066 {
1067     auto replaceSensors = [this, from, to](const QString &configEntry) {
1068         auto array = QJsonDocument::fromJson(d->sensorsGroup.readEntry(configEntry, QString()).toUtf8()).array();
1069         for (auto itr = array.begin(); itr != array.end(); ++itr) {
1070             if (itr->toString() == from) {
1071                 *itr = QJsonValue(to);
1072             }
1073         }
1074         return QJsonDocument(array).toJson(QJsonDocument::Compact);
1075     };
1076 
1077     d->sensorsGroup.writeEntry("totalSensors", replaceSensors(QStringLiteral("totalSensors")));
1078     d->sensorsGroup.writeEntry("highPrioritySensorIds", replaceSensors(QStringLiteral("highPrioritySensorIds")));
1079     d->sensorsGroup.writeEntry("lowPrioritySensorIds", replaceSensors(QStringLiteral("lowPrioritySensorIds")));
1080 
1081     if (d->shouldSync) {
1082         d->sensorsGroup.sync();
1083     }
1084 }
1085 
1086 #include "moc_SensorFaceController.cpp"