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"