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