Libksysguard

SensorFaceController.cpp
1 /*
2  Copyright (C) 2020 Marco Martin <[email protected]>
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License as published by the Free Software Foundation; either
7  version 2 of the License, or (at your option) any later version.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  Boston, MA 02110-1301, USA.
18 */
19 
20 #include "SensorFaceController.h"
21 #include "SensorFaceController_p.h"
22 #include "SensorFace_p.h"
23 #include <Sensor.h>
24 #include <SensorQuery.h>
25 
26 #include <QtQml>
27 
28 #include <KDesktopFile>
29 #include <KDeclarative/ConfigPropertyMap>
30 #include <KPackage/PackageLoader>
31 #include <KLocalizedString>
32 #include <KConfigLoader>
33 #include <KPluginMetaData>
34 #include <Solid/Block>
35 #include <Solid/Device>
36 #include <Solid/Predicate>
37 #include <Solid/StorageAccess>
38 #include <Solid/StorageVolume>
39 
40 using namespace KSysGuard;
41 
42 FacesModel::FacesModel(QObject *parent)
43  : QStandardItemModel(parent)
44 {
45  reload();
46 }
47 
48 void FacesModel::reload()
49 {
50  clear();
51 
52  auto list = KPackage::PackageLoader::self()->listPackages(QStringLiteral("KSysguard/SensorFace"));
53  // NOTE: This will disable completely the internal in-memory cache
55  p.install(QString(), QString());
56 
57  for (auto plugin : list) {
58  QStandardItem *item = new QStandardItem(plugin.name());
59  item->setData(plugin.pluginId(), FacesModel::PluginIdRole);
60  appendRow(item);
61  }
62 }
63 
64 QString FacesModel::pluginId(int row)
65 {
66  return data(index(row, 0), PluginIdRole).toString();
67 }
68 
69 QHash<int, QByteArray> FacesModel::roleNames() const
70 {
72 
73  roles[PluginIdRole] = "pluginId";
74  return roles;
75 }
76 
77 PresetsModel::PresetsModel(QObject *parent)
78  : QStandardItemModel(parent)
79 {
80  reload();
81 }
82 
83 void PresetsModel::reload()
84 {
85  clear();
86  QList<KPluginMetaData> plugins = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), [](const KPluginMetaData &plugin) {
87  return plugin.value(QStringLiteral("X-Plasma-RootPath")) == QStringLiteral("org.kde.plasma.systemmonitor");
88  });
89 
90  QSet<QString> usedNames;
91 
92  // We iterate backwards because packages under ~/.local are listed first, while we want them last
93  auto it = plugins.rbegin();
94  for (; it != plugins.rend(); ++it) {
95  const auto &plugin = *it;
96  KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), plugin.pluginId());
97  KDesktopFile df(p.path() + QStringLiteral("metadata.desktop"));
98 
99  QString baseName = df.readName();
100  QString name = baseName;
101  int id = 0;
102 
103  while (usedNames.contains(name)) {
104  name = baseName + QStringLiteral(" (") + QString::number(++id) + QStringLiteral(")");
105  }
106  usedNames << name;
107 
108  QStandardItem *item = new QStandardItem(baseName);
109 
110  // TODO config
111  QVariantMap config;
112 
113  KConfigGroup configGroup(KSharedConfig::openConfig(p.filePath("config", QStringLiteral("faceproperties")), KConfig::SimpleConfig), QStringLiteral("Config"));
114 
115  const QStringList keys = configGroup.keyList();
116  for (const QString &key : keys) {
117  // all strings for now, type conversion happens in QML side when we have the config property map
118  config.insert(key, configGroup.readEntry(key));
119  }
120 
121  item->setData(plugin.pluginId(), PresetsModel::PluginIdRole);
122  item->setData(config, PresetsModel::ConfigRole);
123 
124  item->setData(QFileInfo(p.path() + QStringLiteral("metadata.desktop")).isWritable(), PresetsModel::WritableRole);
125 
126  appendRow(item);
127  }
128 }
129 
130 QHash<int, QByteArray> PresetsModel::roleNames() const
131 {
133 
134  roles[PluginIdRole] = "pluginId";
135  roles[ConfigRole] = "config";
136  roles[WritableRole] = "writable";
137  return roles;
138 }
139 
140 QVector<QPair<QRegularExpression, QString>> KSysGuard::SensorFaceControllerPrivate::sensorIdReplacements;
141 QRegularExpression SensorFaceControllerPrivate::oldDiskSensor = QRegularExpression(QStringLiteral("^disk\\/(.+)_\\(\\d+:\\d+\\)"));
142 QRegularExpression SensorFaceControllerPrivate::oldPartitionSensor = QRegularExpression(QStringLiteral("^partitions(\\/.+)\\/"));
143 
144 SensorFaceControllerPrivate::SensorFaceControllerPrivate()
145 {
146  if (SensorFaceControllerPrivate::sensorIdReplacements.isEmpty()) {
147  // A list of conversion rules to convert old sensor ids to new ones.
148  // When loading, each regular expression tries to match to the sensor
149  // id. If it matches, it will be be used to replace the sensor id with
150  // the second argument.
151  sensorIdReplacements = {
152  { QRegularExpression(QStringLiteral("network/interfaces/(.*)")), QStringLiteral("network/\\1")},
153  { QRegularExpression(QStringLiteral("network/all/receivedDataRate$")), QStringLiteral("network/all/download")},
154  { QRegularExpression(QStringLiteral("network/all/sentDataRate$")), QStringLiteral("network/all/upload")},
155  { QRegularExpression(QStringLiteral("network/all/totalReceivedData$")), QStringLiteral("network/all/totalDownload")},
156  { QRegularExpression(QStringLiteral("network/all/totalSentData$")), QStringLiteral("network/all/totalUpload")},
157  { QRegularExpression(QStringLiteral("(.*)/receiver/data$")), QStringLiteral("\\1/download")},
158  { QRegularExpression(QStringLiteral("(.*)/transmitter/data$")), QStringLiteral("\\1/upload")},
159  { QRegularExpression(QStringLiteral("(.*)/receiver/dataTotal$")), QStringLiteral("\\1/totalDownload")},
160  { QRegularExpression(QStringLiteral("(.*)/transmitter/dataTotal$")), QStringLiteral("\\1/totalUpload")},
161  { QRegularExpression(QStringLiteral("(.*)/Rate/rio")), QStringLiteral("\\1/read")},
162  { QRegularExpression(QStringLiteral("(.*)/Rate/wio$")), QStringLiteral("\\1/write")},
163  { QRegularExpression(QStringLiteral("(.*)/freespace$")), QStringLiteral("\\1/free")},
164  { QRegularExpression(QStringLiteral("(.*)/filllevel$")), QStringLiteral("\\1/usedPercent")},
165  { QRegularExpression(QStringLiteral("(.*)/usedspace$")), QStringLiteral("\\1/used")},
166  { QRegularExpression(QStringLiteral("cpu/system/(.*)$")), QStringLiteral("cpu/all/\\1")},
167  { QRegularExpression(QStringLiteral("cpu/(.*)/sys$")), QStringLiteral("cpu/\\1/system")},
168  { QRegularExpression(QStringLiteral("cpu/(.*)/TotalLoad$")), QStringLiteral("cpu/\\1/usage")},
169  { QRegularExpression(QStringLiteral("cpu/cpu(\\d+)/clock$")), QStringLiteral("cpu/cpu\\1/frequency")},
170  { QRegularExpression(QStringLiteral("mem/(.*)level")), QStringLiteral("mem/\\1Percent")},
171  { QRegularExpression(QStringLiteral("mem/physical/allocated")), QStringLiteral("memory/physical/used")},
172  { QRegularExpression(QStringLiteral("mem/physical/available")), QStringLiteral("memory/physical/free")},
173  { QRegularExpression(QStringLiteral("mem/physical/buf")), QStringLiteral("memory/physical/buffer")},
174  { QRegularExpression(QStringLiteral("mem/physical/cached")), QStringLiteral("memory/physical/cache")},
175  { QRegularExpression(QStringLiteral("^mem/(.*)")), QStringLiteral("memory/\\1")},
176  { QRegularExpression(QStringLiteral("nvidia/(.*)/temperature$")), QStringLiteral("gpu/\\1/temperature")},
177  { QRegularExpression(QStringLiteral("nvidia/(.*)/memoryClock$")), QStringLiteral("gpu/\\1/memoryFrequency")},
178  { QRegularExpression(QStringLiteral("nvidia/(.*)/processorClock$")), QStringLiteral("gpu/\\1/coreFrequency")},
179  { QRegularExpression(QStringLiteral("nvidia/(.*)/(memory|sharedMemory)$")), QStringLiteral("gpu/\\1/usedVram")},
180  { QRegularExpression(QStringLiteral("nvidia/(.*)/(encoderUsage|decoderUsage)$")), QStringLiteral("gpu/\\1/usage")},
181  };
182  }
183 }
184 
185 QString SensorFaceControllerPrivate::replaceDiskId(const QString &entryName) const
186 {
187  const auto match = oldDiskSensor.match(entryName);
188  if (!match.hasMatch()) {
189  return entryName;
190  }
191  const QString device = match.captured(1);
192  Solid::Predicate predicate(Solid::DeviceInterface::StorageAccess);
193  predicate &= Solid::Predicate(Solid::DeviceInterface::Block, QStringLiteral("device"), QStringLiteral("/dev/%1").arg(device));
194  const auto devices = Solid::Device::listFromQuery(predicate);
195  if (devices.empty()) {
196  return QString();
197  }
198  QString sensorId = entryName;
199  const auto volume = devices[0].as<Solid::StorageVolume>();
200  const QString id = volume->uuid().isEmpty() ? volume->label() : volume->uuid();
201  return sensorId.replace(match.captured(0), QStringLiteral("disk/") + id);
202 }
203 
204 QString SensorFaceControllerPrivate::replacePartitionId(const QString &entryName) const
205 {
206  const auto match = oldPartitionSensor.match(entryName);
207  if (!match.hasMatch()) {
208  return entryName;
209  }
210  QString sensorId = entryName;
211 
212  if (match.captured(1) == QLatin1String("/all")) {
213  return sensorId.replace(match.captured(0), QStringLiteral("disk/all/"));
214  }
215 
216  const QString filePath = match.captured(1) == QLatin1String("/__root__") ? QStringLiteral("/") : match.captured(1);
217  const Solid::Predicate predicate(Solid::DeviceInterface::StorageAccess, QStringLiteral("filePath"), filePath);
218  const auto devices = Solid::Device::listFromQuery(predicate);
219  if (devices.empty()) {
220  return entryName;
221  }
222  const auto volume = devices[0].as<Solid::StorageVolume>();
223  const QString id = volume->uuid().isEmpty() ? volume->label() : volume->uuid();
224  return sensorId.replace(match.captured(0), QStringLiteral("disk/%1/").arg(id));
225 }
226 
227 QJsonArray SensorFaceControllerPrivate::readSensors(const KConfigGroup &read, const QString &entryName)
228 {
229  auto original = QJsonDocument::fromJson(read.readEntry(entryName, QString()).toUtf8()).array();
230  QJsonArray newSensors;
231  for (auto entry : original) {
232  QString sensorId = entry.toString();
233  for (auto replacement : qAsConst(sensorIdReplacements)) {
234  auto match = replacement.first.match(sensorId);
235  if (match.hasMatch()) {
236  sensorId.replace(replacement.first, replacement.second);
237  }
238  }
239  sensorId = replaceDiskId(sensorId);
240  sensorId = replacePartitionId(sensorId);
241  newSensors.append(sensorId);
242  }
243 
244  return newSensors;
245 }
246 
247 QJsonArray SensorFaceControllerPrivate::readAndUpdateSensors(KConfigGroup& config, const QString& entryName)
248 {
249  auto original = QJsonDocument::fromJson(config.readEntry(entryName, QString()).toUtf8()).array();
250 
251  const KConfigGroup &group = config;
252  auto newSensors = readSensors(group, entryName);
253 
254  if (newSensors != original) {
255  config.writeEntry(entryName, QJsonDocument(newSensors).toJson(QJsonDocument::Compact));
256  }
257 
258  return newSensors;
259 }
260 
261 void SensorFaceControllerPrivate::resolveSensors(const QJsonArray &partialEntries, std::function<void(const QJsonArray&)> callback)
262 {
263  if (partialEntries.isEmpty()) {
264  callback(partialEntries);
265  return;
266  }
267 
268  auto sensors = std::make_shared<QJsonArray>();
269 
270  for (const auto &id : partialEntries) {
271  auto query = new KSysGuard::SensorQuery{id.toString()};
272  query->connect(query, &KSysGuard::SensorQuery::finished, q, [this, query, sensors, callback] {
273  query->sortByName();
274  const auto ids = query->sensorIds();
275  delete query;
276  std::transform(ids.begin(), ids.end(), std::back_inserter(*sensors), [] (const QString &id) {
277  return QJsonValue(id);
278  });
279  if (sensors.use_count() == 1) {
280  // We are the last query
281  callback(*sensors);
282  }
283  });
284  query->execute();
285  }
286 }
287 
288 SensorFace *SensorFaceControllerPrivate::createGui(const QString &qmlPath)
289 {
290  QQmlComponent *component = new QQmlComponent(engine, qmlPath, nullptr);
291  // TODO: eventually support async components? (only useful for qml files from http, we probably don't want that)
292  if (component->status() != QQmlComponent::Ready) {
293  qCritical() << "Error creating component:";
294  for (auto err : component->errors()) {
295  qWarning() << err.toString();
296  }
297  component->deleteLater();
298  return nullptr;
299  }
300 
301  QQmlContext *context = new QQmlContext(engine);
302  context->setContextObject(contextObj);
303  QObject *guiObject = component->beginCreate(context);
304  SensorFace *gui = qobject_cast<SensorFace *>(guiObject);
305  if (!gui) {
306  qWarning()<<"ERROR: QML gui" << guiObject << "not a SensorFace instance";
307  guiObject->deleteLater();
308  context->deleteLater();
309  return nullptr;
310  }
311  context->setParent(gui);
312 
313  gui->setController(q);
314 
315  component->completeCreate();
316 
317  component->deleteLater();
318  return gui;
319 }
320 
321 QQuickItem *SensorFaceControllerPrivate::createConfigUi(const QString &file, const QVariantMap &initialProperties)
322 {
323  QQmlComponent *component = new QQmlComponent(engine, file, nullptr);
324  // TODO: eventually support async components? (only useful for qml files from http, we probably don't want that)
325  if (component->status() != QQmlComponent::Ready) {
326  qCritical() << "Error creating component:";
327  for (auto err : component->errors()) {
328  qWarning() << err.toString();
329  }
330  component->deleteLater();
331  return nullptr;
332  }
333 
334  QQmlContext *context = new QQmlContext(engine);
335  context->setContextObject(contextObj);
336  QObject *guiObject = component->createWithInitialProperties(
337  initialProperties, context);
338  QQuickItem *gui = qobject_cast<QQuickItem *>(guiObject);
339  Q_ASSERT(gui);
340  context->setParent(gui);
341 
342  component->deleteLater();
343 
344  return gui;
345 }
346 
347 
349  : QObject(engine),
350  d(std::make_unique<SensorFaceControllerPrivate>())
351 {
352  d->q = this;
353  d->configGroup = config;
354  d->appearanceGroup = KConfigGroup(&config, "Appearance");
355  d->sensorsGroup = KConfigGroup(&config, "Sensors");
356  d->colorsGroup = KConfigGroup(&config, "SensorColors");
357  d->engine = engine;
358  d->syncTimer = new QTimer(this);
359  d->syncTimer->setSingleShot(true);
360  d->syncTimer->setInterval(5000);
361  connect(d->syncTimer, &QTimer::timeout, this, [this]() {
362  if (!d->shouldSync) {
363  return;
364  }
365  d->appearanceGroup.sync();
366  d->sensorsGroup.sync();
367  });
368 
369  d->contextObj = new KLocalizedContext(this);
370 
371  d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("totalSensors")), [this] (const QJsonArray &resolvedSensors) {
372  d->totalSensors = resolvedSensors;
373  Q_EMIT totalSensorsChanged();
374  });
375  d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("lowPrioritySensorIds")), [this] (const QJsonArray &resolvedSensors) {
376  d->lowPrioritySensorIds = resolvedSensors;
377  Q_EMIT lowPrioritySensorIdsChanged();
378  });
379  d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("highPrioritySensorIds")), [this] (const QJsonArray &resolvedSensors) {
380  d->highPrioritySensorIds = resolvedSensors;
381  Q_EMIT highPrioritySensorIdsChanged();
382  });
383 
384  setFaceId(d->appearanceGroup.readEntry("chartFace", QStringLiteral("org.kde.ksysguard.piechart")));
385 }
386 
387 SensorFaceController::~SensorFaceController()
388 {
389  if (!d->shouldSync) {
390  // If we should not sync automatically, clear all changes before we
391  // destroy the config objects, otherwise they will be written during
392  // destruction.
393  d->appearanceGroup.markAsClean();
394  d->colorsGroup.markAsClean();
395  if (d->faceConfigLoader && d->faceConfigLoader->isSaveNeeded()) {
396  d->faceConfigLoader->load();
397  }
398  }
399 }
400 
401 QString SensorFaceController::title() const
402 {
403  // both Title and title can exist to allow i18n of Title
404  if (d->appearanceGroup.hasKey("title")) {
405  return d->appearanceGroup.readEntry("title");
406  } else {
407  // if neither exist fall back to name
408  return d->appearanceGroup.readEntry("Title", name());
409  }
410 }
411 
412 void SensorFaceController::setTitle(const QString &title)
413 {
414  if (title == SensorFaceController::title()) {
415  return;
416  }
417 
418  d->appearanceGroup.writeEntry("title", title);
419  d->syncTimer->start();
420 
421  emit titleChanged();
422 }
423 
424 bool SensorFaceController::showTitle() const
425 {
426  return d->appearanceGroup.readEntry("showTitle", true);
427 }
428 
429 void SensorFaceController::setShowTitle(bool show)
430 {
431  if (show == showTitle()) {
432  return;
433  }
434 
435  d->appearanceGroup.writeEntry("showTitle", show);
436  d->syncTimer->start();
437 
438  emit showTitleChanged();
439 }
440 
441 
442 QJsonArray SensorFaceController::totalSensors() const
443 {
444  return d->totalSensors;
445 }
446 
447 void SensorFaceController::setTotalSensors(const QJsonArray &totalSensors)
448 {
449  if (totalSensors == d->totalSensors) {
450  return;
451  }
452  const auto currentEntry = QJsonDocument::fromJson(d->sensorsGroup.readEntry("totalSensors").toUtf8()).array();
453  if (totalSensors == currentEntry) {
454  return;
455  }
456  d->sensorsGroup.writeEntry("totalSensors", QJsonDocument(totalSensors).toJson(QJsonDocument::Compact));
457  // Until we have resolved
458  d->totalSensors = totalSensors;
459  d->syncTimer->start();
460  Q_EMIT totalSensorsChanged();
461  d->resolveSensors(totalSensors, [this] (const QJsonArray &resolvedSensors) {
462  if (resolvedSensors == d->totalSensors) {
463  return;
464  }
465  d->totalSensors = resolvedSensors;
466  Q_EMIT totalSensorsChanged();
467  });
468 }
469 
470 QJsonArray SensorFaceController::highPrioritySensorIds() const
471 {
472  return d->highPrioritySensorIds;
473 }
474 
475 
476 void SensorFaceController::setHighPrioritySensorIds(const QJsonArray &highPrioritySensorIds)
477 {
478  if (highPrioritySensorIds == d->highPrioritySensorIds) {
479  return;
480  }
481  const auto currentEntry = QJsonDocument::fromJson(d->sensorsGroup.readEntry("highPrioritySensorIds").toUtf8()).array();
482  if (highPrioritySensorIds == currentEntry) {
483  return;
484  }
485  d->sensorsGroup.writeEntry("highPrioritySensorIds", QJsonDocument(highPrioritySensorIds).toJson(QJsonDocument::Compact));
486  // Until we have resolved
487  d->syncTimer->start();
488  d->highPrioritySensorIds = highPrioritySensorIds;
489  Q_EMIT highPrioritySensorIdsChanged();
490  d->resolveSensors(highPrioritySensorIds, [this] (const QJsonArray &resolvedSensors) {
491  if (resolvedSensors == d->highPrioritySensorIds) {
492  return;
493  }
494  d->highPrioritySensorIds = resolvedSensors;
495  Q_EMIT highPrioritySensorIdsChanged();
496  });
497 }
498 
499 QVariantMap SensorFaceController::sensorColors() const
500 {
501  QVariantMap colors;
502  for (const auto &key : d->colorsGroup.keyList()) {
503  QColor color = d->colorsGroup.readEntry(key, QColor());
504 
505  if (color.isValid()) {
506  colors[key] = color;
507  }
508  }
509  return colors;
510 }
511 
512 void SensorFaceController::setSensorColors(const QVariantMap &colors)
513 {
514  if (colors == this->sensorColors()) {
515  return;
516  }
517 
518  d->colorsGroup.deleteGroup();
519  d->colorsGroup = KConfigGroup(&d->configGroup, "SensorColors");
520 
521  auto it = colors.constBegin();
522  for (; it != colors.constEnd(); ++it) {
523  d->colorsGroup.writeEntry(it.key(), it.value());
524  }
525 
526  d->syncTimer->start();
527  emit sensorColorsChanged();
528 }
529 
530 QJsonArray SensorFaceController::lowPrioritySensorIds() const
531 {
532  return d->lowPrioritySensorIds;
533 }
534 
535 void SensorFaceController::setLowPrioritySensorIds(const QJsonArray &lowPrioritySensorIds)
536 {
537  if (lowPrioritySensorIds == d->lowPrioritySensorIds) {
538  return;
539  }
540  const auto currentEntry = QJsonDocument::fromJson(d->sensorsGroup.readEntry("lowPrioritySensorIds").toUtf8()).array();
541  if (lowPrioritySensorIds == currentEntry) {
542  return;
543  }
544  d->sensorsGroup.writeEntry("lowPrioritySensorIds", QJsonDocument(lowPrioritySensorIds).toJson(QJsonDocument::Compact));
545  // Until we have resolved
546  d->lowPrioritySensorIds = lowPrioritySensorIds;
547  d->syncTimer->start();
548  Q_EMIT lowPrioritySensorIdsChanged();
549  d->resolveSensors(lowPrioritySensorIds, [this] (const QJsonArray &resolvedSensors) {
550  if (resolvedSensors == d->lowPrioritySensorIds) {
551  return;
552  }
553  d->lowPrioritySensorIds = resolvedSensors;
554  Q_EMIT lowPrioritySensorIdsChanged();
555  });
556 }
557 
558 // from face config, immutable by the user
559 QString SensorFaceController::name() const
560 {
561  return d->facePackage.metadata().name();
562 }
563 
564 const QString SensorFaceController::icon() const
565 {
566  return d->facePackage.metadata().iconName();
567 }
568 
569 bool SensorFaceController::supportsSensorsColors() const
570 {
571  return d->faceProperties.readEntry("SupportsSensorsColors", false);
572 }
573 
574 bool SensorFaceController::supportsTotalSensors() const
575 {
576  return d->faceProperties.readEntry("SupportsTotalSensors", false);
577 }
578 
579 bool SensorFaceController::supportsLowPrioritySensors() const
580 {
581  return d->faceProperties.readEntry("SupportsLowPrioritySensors", false);
582 }
583 
584 int SensorFaceController::maxTotalSensors() const
585 {
586  return d->faceProperties.readEntry("MaxTotalSensors", 1);
587 }
588 
589 void SensorFaceController::setFaceId(const QString &face)
590 {
591  if (d->faceId == face) {
592  return;
593  }
594 
595  if (d->fullRepresentation) {
596  d->fullRepresentation->deleteLater();
597  d->fullRepresentation.clear();
598  }
599  if (d->compactRepresentation) {
600  d->compactRepresentation->deleteLater();
601  d->compactRepresentation.clear();
602  }
603  if (d->faceConfigUi) {
604  d->faceConfigUi->deleteLater();
605  d->faceConfigUi.clear();
606  }
607 
608  d->faceId = face;
609 
610  d->facePackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KSysguard/SensorFace"), face);
611 
612  if (d->faceConfiguration) {
613  d->faceConfiguration->deleteLater();
614  d->faceConfiguration = nullptr;
615  }
616  if (d->faceConfigLoader) {
617  d->faceConfigLoader ->deleteLater();
618  d->faceConfigLoader = nullptr;
619  }
620 
621  if (!d->facePackage.isValid()) {
622  emit faceIdChanged();
623  return;
624  }
625 
626  d->contextObj->setTranslationDomain(QLatin1String("ksysguard_face_") + face);
627 
628  d->faceProperties = KConfigGroup(KSharedConfig::openConfig(d->facePackage.filePath("FaceProperties"), KConfig::SimpleConfig), QStringLiteral("Config"));
629 
630  const QString xmlPath = d->facePackage.filePath("mainconfigxml");
631 
632  if (!xmlPath.isEmpty()) {
633  QFile file(xmlPath);
634  KConfigGroup cg(&d->configGroup, d->faceId);
635 
636  d->faceConfigLoader = new KConfigLoader(cg, &file, this);
637  d->faceConfiguration = new KDeclarative::ConfigPropertyMap(d->faceConfigLoader, this);
638  d->faceConfiguration->setAutosave(d->shouldSync);
639  connect(d->faceConfiguration, &KDeclarative::ConfigPropertyMap::valueChanged, this, [this] (const QString &key) {
640  auto item = d->faceConfigLoader->findItemByName(key);
641  if (item) {
642  item->writeConfig(d->faceConfigLoader->config());
643  }
644  });
645  }
646 
647  d->appearanceGroup.writeEntry("chartFace", face);
648  d->syncTimer->start();
649  emit faceIdChanged();
650  return;
651 }
652 
653 QString SensorFaceController::faceId() const
654 {
655  return d->faceId;
656 }
657 
658 KDeclarative::ConfigPropertyMap *SensorFaceController::faceConfiguration() const
659 {
660  return d->faceConfiguration;
661 }
662 
663 QQuickItem *SensorFaceController::compactRepresentation()
664 {
665  if (!d->facePackage.isValid()) {
666  return nullptr;
667  } else if (d->compactRepresentation) {
668  return d->compactRepresentation;
669  }
670 
671  d->compactRepresentation = d->createGui(d->facePackage.filePath("ui", QStringLiteral("CompactRepresentation.qml")));
672  return d->compactRepresentation;
673 }
674 
675 QQuickItem *SensorFaceController::fullRepresentation()
676 {
677  if (!d->facePackage.isValid()) {
678  return nullptr;
679  } else if (d->fullRepresentation) {
680  return d->fullRepresentation;
681  }
682 
683  d->fullRepresentation = d->createGui(d->facePackage.filePath("ui", QStringLiteral("FullRepresentation.qml")));
684  return d->fullRepresentation;
685 }
686 
687 QQuickItem *SensorFaceController::faceConfigUi()
688 {
689  if (!d->facePackage.isValid()) {
690  return nullptr;
691  } else if (d->faceConfigUi) {
692  return d->faceConfigUi;
693  }
694 
695  const QString filePath = d->facePackage.filePath("ui", QStringLiteral("Config.qml"));
696 
697  if (filePath.isEmpty()) {
698  return nullptr;
699  }
700 
701  d->faceConfigUi = d->createConfigUi(QStringLiteral(":/FaceDetailsConfig.qml"),
702  {{QStringLiteral("controller"), QVariant::fromValue(this)},
703  {QStringLiteral("source"), filePath}});
704 
705  if (d->faceConfigUi && !d->faceConfigUi->property("item").value<QQuickItem *>()) {
706  d->faceConfigUi->deleteLater();
707  d->faceConfigUi.clear();
708  }
709  return d->faceConfigUi;
710 }
711 
712 QQuickItem *SensorFaceController::appearanceConfigUi()
713 {
714  if (d->appearanceConfigUi) {
715  return d->appearanceConfigUi;
716  }
717 
718  d->appearanceConfigUi = d->createConfigUi(QStringLiteral(":/ConfigAppearance.qml"), {{QStringLiteral("controller"), QVariant::fromValue(this)}});
719 
720  return d->appearanceConfigUi;
721 }
722 
723 QQuickItem *SensorFaceController::sensorsConfigUi()
724 {
725  if (d->sensorsConfigUi) {
726  return d->sensorsConfigUi;
727  }
728 
729  if (d->faceProperties.readEntry("SupportsSensors", true)) {
730  d->sensorsConfigUi = d->createConfigUi(QStringLiteral(":/ConfigSensors.qml"),{{QStringLiteral("controller"), QVariant::fromValue(this)}});
731  } else {
732  d->sensorsConfigUi = new QQuickItem;
733  }
734  return d->sensorsConfigUi;
735 }
736 
737 QAbstractItemModel *SensorFaceController::availableFacesModel()
738 {
739  if (d->availableFacesModel) {
740  return d->availableFacesModel;
741  }
742 
743  d->availableFacesModel = new FacesModel(this);
744  return d->availableFacesModel;
745 }
746 
747 QAbstractItemModel *SensorFaceController::availablePresetsModel()
748 {
749  if (d->availablePresetsModel) {
750  return d->availablePresetsModel;
751  }
752 
753  d->availablePresetsModel = new PresetsModel(this);
754 
755  return d->availablePresetsModel;
756 }
757 
759 {
760  if (d->faceConfigLoader) {
761  d->faceConfigLoader->load();
762  }
763 
764  d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("totalSensors")), [this] (const QJsonArray &resolvedSensors) {
765  d->totalSensors = resolvedSensors;
766  Q_EMIT totalSensorsChanged();
767  });
768  d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("lowPrioritySensorIds")), [this] (const QJsonArray &resolvedSensors) {
769  d->lowPrioritySensorIds = resolvedSensors;
770  Q_EMIT lowPrioritySensorIdsChanged();
771  });
772  d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("highPrioritySensorIds")), [this] (const QJsonArray &resolvedSensors) {
773  d->highPrioritySensorIds = resolvedSensors;
774  Q_EMIT highPrioritySensorIdsChanged();
775  });
776 
777  //Force to re-read all the values
778  setFaceId(d->appearanceGroup.readEntry("chartFace", QStringLiteral("org.kde.ksysguard.textonly")));
779  titleChanged();
780  sensorColorsChanged();
781 }
782 
784 {
785  if (preset.isEmpty()) {
786  return;
787  }
788 
789  auto presetPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"));
790 
791  presetPackage.setPath(preset);
792 
793  if (!presetPackage.isValid()) {
794  return;
795  }
796 
797  if (presetPackage.metadata().value(QStringLiteral("X-Plasma-RootPath")) != QStringLiteral("org.kde.plasma.systemmonitor")) {
798  return;
799  }
800 
801  KDesktopFile df(presetPackage.path() + QStringLiteral("metadata.desktop"));
802 
803  auto c = KSharedConfig::openConfig(presetPackage.filePath("config", QStringLiteral("faceproperties")), KConfig::SimpleConfig);
804  const KConfigGroup presetGroup(c, QStringLiteral("Config"));
805  const KConfigGroup colorsGroup(c, QStringLiteral("SensorColors"));
806 
807  // Load the title
808  setTitle(df.readName());
809 
810  //Remove the "custon" value from presets models
811  if (d->availablePresetsModel &&
812  d->availablePresetsModel->data(d->availablePresetsModel->index(0, 0), PresetsModel::PluginIdRole).toString().isEmpty()) {
813  d->availablePresetsModel->removeRow(0);
814  }
815 
816  setTotalSensors(d->readSensors(presetGroup, QStringLiteral("totalSensors")));
817  setHighPrioritySensorIds(d->readSensors(presetGroup, QStringLiteral("highPrioritySensorIds")));
818  setLowPrioritySensorIds(d->readSensors(presetGroup, QStringLiteral("lowPrioritySensorIds")));
819 
820  setFaceId(presetGroup.readEntry(QStringLiteral("chartFace"), QStringLiteral("org.kde.ksysguard.piechart")));
821 
822  colorsGroup.copyTo(&d->colorsGroup);
823  emit sensorColorsChanged();
824 
825  if (d->faceConfigLoader) {
826  KConfigGroup presetGroup(KSharedConfig::openConfig(presetPackage.filePath("FaceProperties"), KConfig::SimpleConfig), QStringLiteral("FaceConfig"));
827 
828  for (const QString &key : presetGroup.keyList()) {
829  KConfigSkeletonItem *item = d->faceConfigLoader->findItemByName(key);
830  if (item) {
831  if (item->property().type() == QVariant::StringList) {
832  item->setProperty(presetGroup.readEntry(key, QStringList()));
833  } else {
834  item->setProperty(presetGroup.readEntry(key));
835  }
836  d->faceConfigLoader->save();
837  d->faceConfigLoader->read();
838  }
839  }
840  }
841 }
842 
844 {
845  QString pluginName = QStringLiteral("org.kde.plasma.systemmonitor.") + title().simplified().replace(QLatin1Char(' '), QStringLiteral("")).toLower();
846  int suffix = 0;
847 
848  auto presetPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"));
849 
850  presetPackage.setPath(pluginName);
851  if (presetPackage.isValid()) {
852  do {
853  presetPackage.setPath(QString());
854  presetPackage.setPath(pluginName + QString::number(++suffix));
855  } while (presetPackage.isValid());
856 
857  pluginName += QString::number(suffix);
858  }
859 
860  QTemporaryDir dir;
861  if (!dir.isValid()) {
862  return;
863  }
864 
865  KConfig c(dir.path() % QStringLiteral("/metadata.desktop"));
866 
867  KConfigGroup cg(&c, "Desktop Entry");
868  cg.writeEntry("Name", title());
869  cg.writeEntry("Icon", "ksysguardd");
870  cg.writeEntry("X-Plasma-API", "declarativeappletscript");
871  cg.writeEntry("X-Plasma-MainScript", "ui/main.qml");
872  cg.writeEntry("X-Plasma-Provides", "org.kde.plasma.systemmonitor");
873  cg.writeEntry("X-Plasma-RootPath", "org.kde.plasma.systemmonitor");
874  cg.writeEntry("X-KDE-PluginInfo-Name", pluginName);
875  cg.writeEntry("X-KDE-ServiceTypes", "Plasma/Applet");
876  cg.writeEntry("X-KDE-PluginInfo-Category", "System Information");
877  cg.writeEntry("X-KDE-PluginInfo-License", "LGPL 2.1+");
878  cg.writeEntry("X-KDE-PluginInfo-EnabledByDefault", "true");
879  cg.writeEntry("X-KDE-PluginInfo-Version", "0.1");
880  cg.sync();
881 
882  QDir subDir(dir.path());
883  subDir.mkdir(QStringLiteral("contents"));
884  KConfig faceConfig(subDir.path() % QStringLiteral("/contents/faceproperties"));
885 
886  KConfigGroup configGroup(&faceConfig, "Config");
887  configGroup.writeEntry(QStringLiteral("totalSensors"), QJsonDocument(totalSensors()).toJson(QJsonDocument::Compact));
888  configGroup.writeEntry(QStringLiteral("highPrioritySensorIds"), QJsonDocument(highPrioritySensorIds()).toJson(QJsonDocument::Compact));
889  configGroup.writeEntry(QStringLiteral("lowPrioritySensorIds"), QJsonDocument(lowPrioritySensorIds()).toJson(QJsonDocument::Compact));
890 
891  KConfigGroup colorsGroup(&faceConfig, "SensorColors");
892  d->colorsGroup.copyTo(&colorsGroup);
893  colorsGroup.sync();
894 
895  configGroup = KConfigGroup(&faceConfig, "FaceConfig");
896  if (d->faceConfigLoader) {
897  const auto &items = d->faceConfigLoader->items();
898  for (KConfigSkeletonItem *item : items) {
899  configGroup.writeEntry(item->key(), item->property());
900  }
901  }
902  configGroup.sync();
903 
904  auto *job = presetPackage.install(dir.path());
905 
906  connect(job, &KJob::finished, this, [this, pluginName] () {
907  d->availablePresetsModel->reload();
908  });
909 }
910 
912 {
913  auto presetPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), pluginId);
914 
915  if (presetPackage.metadata().value(QStringLiteral("X-Plasma-RootPath")) != QStringLiteral("org.kde.plasma.systemmonitor")) {
916  return;
917  }
918 
919  QDir root(presetPackage.path());
920  root.cdUp();
921  auto *job = presetPackage.uninstall(pluginId, root.path());
922 
923  connect(job, &KJob::finished, this, [this] () {
924  d->availablePresetsModel->reload();
925  });
926 }
927 
929 {
930  return d->shouldSync;
931 }
932 
934 {
935  d->shouldSync = sync;
936  if (d->faceConfiguration) {
937  d->faceConfiguration->setAutosave(sync);
938  }
939  if (!d->shouldSync && d->syncTimer->isActive()) {
940  d->syncTimer->stop();
941  }
942 }
943 
944 
945 #include "moc_SensorFaceController.cpp"
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
SensorFaceController(KConfigGroup &config, QQmlEngine *engine)
Creates a new SensorFaceController.
virtual void setProperty(const QVariant &p)=0
QString value(const QString &key, const QString &defaultValue=QString()) const
bool sync() override
qreal volume()
QString name(const QVariant &location)
virtual void completeCreate()
Q_INVOKABLE void reloadConfig()
Reload the configuration.
QList< QQmlError > errors() const const
bool shouldSync() const
Whether the controller should sync configuration changes.
std::optional< QSqlQuery > query(const QString &queryStatement)
const QString path() const
Q_INVOKABLE void uninstallPreset(const QString &pluginId)
Uninstall a specific preset.
virtual QHash< int, QByteArray > roleNames() const const
QString simplified() const const
void writeEntry(const QString &key, const QVariant &value, WriteConfigFlags pFlags=Normal)
bool isValid() const const
Q_INVOKABLE void savePreset()
Save the current configuration as a preset.
QList< KPluginMetaData > findPackages(const QString &packageFormat, const QString &packageRoot=QString(), std::function< bool(const KPluginMetaData &)> filter=std::function< bool(const KPluginMetaData &)>())
QList::reverse_iterator rbegin()
virtual void setData(const QVariant &value, int role)
KSharedConfigPtr config()
static PackageLoader * self()
const QList< QKeySequence > & reload()
void timeout()
QString number(int n, int base)
QVariantMap sensorColors
Maps sensorIds to colors that can be used when a color for something relating to a specific sensor is...
QString title
A title for the face.
void finished(KJob *job)
static QList< Device > listFromQuery(const Predicate &predicate, const QString &parentUdi=QString())
QObject * createWithInitialProperties(const QVariantMap &initialProperties, QQmlContext *context)
void setPath(const QString &path)
bool showTitle
Whether the title should be displayed or if it should be hidden instead.
bool isEmpty() const const
Q_INVOKABLE void loadPreset(const QString &preset)
Loads a specific preset.
QJsonArray highPrioritySensorIds
Sensors that should always be shown in the face.
void deleteLater()
void copyTo(KConfigBase *other, WriteConfigFlags pFlags=Normal) const
virtual QObject * beginCreate(QQmlContext *publicContext)
An object to query the daemon for a list of sensors and their metadata.
Definition: SensorQuery.h:38
QList< KPluginMetaData > listPackages(const QString &packageFormat, const QString &packageRoot=QString())
bool cdUp()
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
void append(const QJsonValue &value)
QString toLower() const const
QString path() const const
void setParent(QObject *parent)
QList::reverse_iterator rend()
QVariant fromValue(const T &value)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
bool contains(const T &value) const const
bool isEmpty() const const
QString & replace(int position, int n, QChar after)
void setContextObject(QObject *object)
QJsonArray totalSensors
Sensors that are typically used to display a total in some way or form.
void insert(int i, const T &value)
bool mkdir(const QString &dirName) const const
QJsonArray lowPrioritySensorIds
Secondary list of sensors.
Package loadPackage(const QString &packageFormat, const QString &packagePath=QString())
KJob * install(const QString &sourcePackage, const QString &packageRoot=QString())
Base for sensor faces.
Definition: SensorFace_p.h:43
void setShouldSync(bool sync)
Specifies if the controller should automatically sync configuration changes.
QString filePath(const QByteArray &key, const QString &filename=QString()) const
QVariant::Type type() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
QObject * parent() const const
virtual QVariant property() const =0
T readEntry(const QString &key, const T &aDefault) const
void valueChanged(const QString &key, const QVariant &value)
Q_EMITQ_EMIT
QString pluginId() const
bool isValid() const const
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Mar 4 2021 23:09:25 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.