Refine Framework monitoring history and configurable tray telemetry
This commit is contained in:
@@ -42,10 +42,14 @@ private Q_SLOTS:
|
||||
QVERIFY(!batteryRate({{"watts", 12.3}, {"state", "Unknown"}}));
|
||||
put(battery + "charge_full", "4500000"); put(battery + "charge_full_design", "5000000");
|
||||
put(battery + "voltage_min_design", "16000000");
|
||||
put(battery + "charge_now", "3000000");
|
||||
QCOMPARE(batteryStatus(root.path())["remainingMWh"].toDouble(), 48000.);
|
||||
QCOMPARE(batteryStatus(root.path())["fullMWh"].toDouble(), 72000.);
|
||||
QCOMPARE(batteryStatus(root.path())["health"].toDouble(), 90.);
|
||||
QVERIFY(batteryStatus(root.path())["capacityEstimated"].toBool());
|
||||
put(battery + "energy_full", "75000000"); put(battery + "energy_full_design", "75000000");
|
||||
put(battery + "energy_now", "56250000");
|
||||
QCOMPARE(batteryStatus(root.path())["remainingMWh"].toDouble(), 56250.);
|
||||
QCOMPARE(batteryStatus(root.path())["fullMWh"].toDouble(), 75000.);
|
||||
QCOMPARE(batteryStatus(root.path())["health"].toDouble(), 100.);
|
||||
QVERIFY(!batteryStatus(root.path()).contains("capacityEstimated"));
|
||||
|
||||
@@ -15,10 +15,222 @@
|
||||
#include "traypage.h"
|
||||
#include "colorbutton.h"
|
||||
#include <QMessageBox>
|
||||
#include <QSlider>
|
||||
#include "graphdata.h"
|
||||
#include "tooltip.h"
|
||||
#include "processusage.h"
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
|
||||
class WindowTest : public QObject {
|
||||
Q_OBJECT
|
||||
private Q_SLOTS:
|
||||
void hoverFormatting() {
|
||||
const QVector<Sensor> sensors{{"cros_ec/peci-temp", "CPU", {}, "°C"}, {"spd5118/temp1", "Memory", {}, "°C"},
|
||||
{"nvme/Composite", "NVMe", {}, "°C"}};
|
||||
const auto temperatures = tooltipSensors(sensors);
|
||||
QCOMPARE(temperatures[0].id, sensors[0].id); QVERIFY(temperatures[3].id.isEmpty());
|
||||
const QMap<QString, double> readings{{"cpu-usage", 12.3}, {"fan", 2500}, {"fan-duty", 40}, {sensors[0].id, 47.}, {sensors[1].id, 38.}, {sensors[2].id, 41.}};
|
||||
QVariantMap settings{{"tray/hover/topApp", true}};
|
||||
auto text = trayTooltip(settings, readings, temperatures, "Battery 80%", {"Firefox: 5.4 %"});
|
||||
QCOMPARE(text, QString("CPU usage: 12.3 %\n\u2003Firefox: 5.4 %\nBattery 80%\nFan speed: 2500 RPM (40 %)\nTemperatures:\n\u2003CPU 47°C\n\u2003RAM 38°C\n\u2003NVMe 41°C"));
|
||||
const QStringList apps{"Firefox: 5.4 %", "Dolphin: 2.0 %", "Konsole: 1.0 %"};
|
||||
for (int count = 0; count <= 3; ++count) {
|
||||
settings["tray/hover/topApps"] = count;
|
||||
const auto popup = trayTooltip(settings, readings, temperatures, {}, apps);
|
||||
for (int i = 0; i < 3; ++i) QCOMPARE(popup.contains(apps[i]), i < count);
|
||||
}
|
||||
settings["tray/hover/cpu"] = false; settings["tray/hover/battery"] = false; settings["tray/hover/fan"] = false;
|
||||
settings["tray/hover/temperature/memory"] = false; settings["tray/hover/temperature/nvme"] = false;
|
||||
QCOMPARE(trayTooltip(settings, readings, temperatures, {}, apps), QString("CPU 47°C"));
|
||||
QCOMPARE(trayTooltip(settings, {}, temperatures, {}, {}), QString("CPU —°C"));
|
||||
settings["tray/hover/temperature/cpu"] = false;
|
||||
QVERIFY(trayTooltip(settings, readings, temperatures, {}, {}).isEmpty());
|
||||
QVariantMap battery{{"state", "Discharging"}, {"capacity", 75}, {"watts", 12.5}, {"remainingMWh", 56250}, {"fullMWh", 75000}};
|
||||
QCOMPARE(batteryTooltip(battery, 80, {{"TimeToEmpty", 5400}}), QString("Battery 75% · 56 250 mWh / 75 000 mWh\n\u2003-12.5 W · 1 h 30 min to 0%"));
|
||||
battery["state"] = "Charging";
|
||||
QCOMPARE(batteryTooltip(battery, 100, {{"TimeToFull", 3600}}), QString("Battery 75% · 56 250 mWh / 75 000 mWh\n\u2003+12.5 W · 1 h 0 min to 100%"));
|
||||
battery["charge_full"] = 10000; battery["charge_now"] = 7500; battery["current_now"] = 1000;
|
||||
QCOMPARE(batteryTooltip(battery, 80, {}), QString("Battery 75% · 56 250 mWh / 75 000 mWh\n\u2003+12.5 W · ≈0 h 30 min to 80%"));
|
||||
battery["current_now"] = 0;
|
||||
QVERIFY(batteryTooltip(battery, 80, {}).contains("Time remaining unavailable"));
|
||||
battery["capacity"] = 80;
|
||||
QVERIFY(batteryTooltip(battery, 80, {}).contains("80% limit reached"));
|
||||
QVERIFY(batteryTooltip({}, 100, {}).contains("unavailable"));
|
||||
}
|
||||
void processCpuAccounting() {
|
||||
QTemporaryDir proc;
|
||||
const auto writeStat = [&](int pid, const QString &command, int ticks, int start) {
|
||||
QVERIFY(QDir().mkpath(proc.filePath(QString::number(pid))));
|
||||
QStringList fields; for (int i = 0; i < 20; ++i) fields << "0";
|
||||
fields[0] = "R"; fields[11] = QString::number(ticks); fields[19] = QString::number(start);
|
||||
QFile file(proc.filePath(QString::number(pid) + "/stat")); QVERIFY(file.open(QIODevice::WriteOnly));
|
||||
file.write((QString::number(pid) + " (" + command + ") " + fields.join(' ')).toUtf8());
|
||||
};
|
||||
ProcessUsage usage;
|
||||
writeStat(1, "Test) App", 100, 1); writeStat(2, "Test) App", 50, 2); writeStat(3, "Other", 100, 3);
|
||||
QVERIFY(usage.sample({}, proc.path()).isEmpty());
|
||||
writeStat(1, "Test) App", 110, 1); writeStat(2, "Test) App", 70, 2); writeStat(3, "Other", 125, 3);
|
||||
auto top = usage.sample(100, proc.path()); QCOMPARE(top.size(), 2);
|
||||
QCOMPARE(top[0].name, QString("Test) App")); QCOMPARE(top[0].percent, 30.); // Sum matching app processes, not just the busiest PID.
|
||||
QCOMPARE(top[1].name, QString("Other")); QCOMPARE(top[1].percent, 25.);
|
||||
writeStat(1, "Replacement", 5000, 9); // Reused PID must not look like a CPU spike.
|
||||
writeStat(3, "Other", 130, 3);
|
||||
top = usage.sample(100, proc.path()); QVERIFY(!top.isEmpty());
|
||||
QCOMPARE(top[0].name, QString("Other")); QCOMPARE(top[0].percent, 5.);
|
||||
QVERIFY(QFile::remove(proc.filePath("2/stat"))); // Exit/unreadable stat is an expected race.
|
||||
top = usage.sample(100, proc.path()); QVERIFY(!top.isEmpty()); QCOMPARE(top[0].percent, 0.);
|
||||
// More than three apps: limit the list and rank by usage, with stable name ordering on ties.
|
||||
writeStat(4, "Fourth", 10, 4); writeStat(5, "Fifth", 10, 5);
|
||||
usage.sample(100, proc.path());
|
||||
writeStat(1, "Replacement", 5020, 9); writeStat(3, "Other", 170, 3);
|
||||
writeStat(4, "Fourth", 20, 4); writeStat(5, "Fifth", 30, 5);
|
||||
top = usage.sample(100, proc.path()); QCOMPARE(top.size(), 3);
|
||||
QCOMPARE(top[0].name, QString("Other")); QCOMPARE(top[1].name, QString("Fifth")); QCOMPARE(top[2].name, QString("Replacement"));
|
||||
usage.reset(); QVERIFY(usage.sample(100, proc.path()).isEmpty());
|
||||
}
|
||||
void trayOptionHierarchy() {
|
||||
QTemporaryDir root; QSettings settings(root.filePath("tray.ini"), QSettings::IniFormat);
|
||||
TrayPage page(settings, {{"cpu-usage", "CPU", {}, "%"}, {"cros_ec/peci-temp", "CPU temperature", {}, "°C"}});
|
||||
page.show();
|
||||
auto *mode = page.findChild<QComboBox *>("trayDisplayMode");
|
||||
auto *graph = page.findChild<QWidget *>("trayGraphOptions");
|
||||
auto *overflow = page.findChild<QWidget *>("trayOverflowOptions");
|
||||
auto *outside = page.findChild<QComboBox *>("trayOutside");
|
||||
QVERIFY(graph->isHidden());
|
||||
mode->setCurrentIndex(mode->findData("graph")); QVERIFY(graph->isVisible()); QVERIFY(overflow->isVisible());
|
||||
outside->setCurrentIndex(outside->findData("hide")); QVERIFY(overflow->isHidden());
|
||||
outside->setCurrentIndex(outside->findData("clamp")); QVERIFY(overflow->isVisible());
|
||||
auto *cpu = page.findChild<QCheckBox *>("hover/cpu");
|
||||
auto *app = page.findChild<QSpinBox *>("hover/topApps");
|
||||
QCOMPARE(app->minimum(), 0); QCOMPARE(app->maximum(), 3);
|
||||
const auto saved = page.draft();
|
||||
app->setValue(3); cpu->setChecked(false); QVERIFY(!app->isEnabled());
|
||||
QCOMPARE(app->value(), 3); // Retain the choice when its parent is disabled.
|
||||
QVERIFY(!page.findChild<QCheckBox *>("hover/temperature/memory")->isEnabled());
|
||||
page.load(saved); QVERIFY(cpu->isChecked()); QVERIFY(app->isEnabled()); QCOMPARE(app->value(), 0);
|
||||
mode->setCurrentIndex(mode->findData("number")); QVERIFY(graph->isHidden());
|
||||
QVERIFY(cpu->isVisible()); QVERIFY(settings.allKeys().isEmpty()); // Staged until Save and Apply.
|
||||
settings.setValue("tray/hover/topApp", true);
|
||||
TrayPage migrated(settings, {{"cpu-usage", "CPU", {}, "%"}});
|
||||
QCOMPARE(migrated.findChild<QSpinBox *>("hover/topApps")->value(), 1);
|
||||
QVERIFY(!migrated.draft().contains("tray/hover/topApp"));
|
||||
QCOMPARE(tooltipAppCount({{"tray/hover/topApps", 3}, {"tray/hover/topApp", false}}), 3);
|
||||
}
|
||||
void liveProcessSampling() {
|
||||
CpuUsage cpu; ProcessUsage processes;
|
||||
cpu.sample(readText("/proc/stat"));
|
||||
QVERIFY(processes.sample(cpu.totalDelta()).isEmpty());
|
||||
QTest::qWait(200);
|
||||
QVERIFY(cpu.sample(readText("/proc/stat")));
|
||||
const auto apps = processes.sample(cpu.totalDelta());
|
||||
QVERIFY(!apps.isEmpty()); QVERIFY(apps.size() <= 3);
|
||||
double previous = 100;
|
||||
for (const auto &app : apps) {
|
||||
QVERIFY(!app.name.isEmpty()); QVERIFY(std::isfinite(app.percent) && app.percent >= 0 && app.percent <= previous);
|
||||
previous = app.percent;
|
||||
}
|
||||
}
|
||||
void timeWeightedColumns() {
|
||||
const auto connected = [](const QPointF &, const QPointF &) { return true; };
|
||||
const QVector<QPointF> points{{0, 0}, {2, 10}, {10, 10}};
|
||||
auto columns = timeAverages(points.begin(), points.end(), 0, 10, 2, connected);
|
||||
QCOMPARE(columns.size(), 2);
|
||||
QCOMPARE(columns[0].mean, 8.); QCOMPARE(columns[0].maximum, 10.);
|
||||
QCOMPARE(columns[1].mean, 10.);
|
||||
columns = timeAverages(points.begin(), points.end(), 1, 3, 1, connected);
|
||||
QCOMPARE(columns[0].mean, 8.75); // Segment clipped and weighted at both pixel edges.
|
||||
columns = timeAverages(points.begin(), points.end(), 0, 10, 7, connected);
|
||||
double integral = 0;
|
||||
for (const auto &column : columns) integral += column.mean * (column.end - column.begin);
|
||||
QVERIFY(std::abs(integral - 90) < 1e-10); // Fractional boundaries preserve the total area.
|
||||
columns = timeAverages(points.begin(), points.end(), -10, 20, 1, connected);
|
||||
QCOMPARE(columns[0].mean, 9.); // Missing history contributes no artificial zeroes.
|
||||
QCOMPARE(columns[0].begin, 0.); QCOMPARE(columns[0].end, 10.);
|
||||
const QVector<QPointF> ramp{{0, -10}, {10, 10}};
|
||||
columns = timeAverages(ramp.begin(), ramp.end(), 0, 10, 100, connected);
|
||||
QCOMPARE(columns.size(), 100);
|
||||
for (const auto &column : columns) {
|
||||
QVERIFY(std::abs(column.mean - (column.begin + column.end - 10)) < 1e-10);
|
||||
QVERIFY(std::abs(column.maximum - (column.end * 2 - 10)) < 1e-10);
|
||||
}
|
||||
const QVector<QPointF> gaps{{0, 0}, {2, 10}, {3, NAN}, {4, 10}, {6, 0}};
|
||||
columns = timeAverages(gaps.begin(), gaps.end(), 0, 6, 1, connected);
|
||||
QCOMPARE(columns.size(), 2); QVERIFY(columns[1].startsRun);
|
||||
QCOMPARE(columns[0].mean, 5.); QCOMPARE(columns[1].mean, 5.);
|
||||
const auto line = averageLine(columns);
|
||||
QVERIFY(std::isnan(line[3].y())); // Never bridge a gap, even inside one pixel.
|
||||
columns = timeAverages(points.begin(), points.end(), 0, 10, 1,
|
||||
[](const QPointF &, const QPointF &b) { return b.x() < 10; });
|
||||
QCOMPARE(columns.size(), 1); QCOMPARE(columns[0].end, 2.); // Explicit suspend break.
|
||||
}
|
||||
void dayHistoryAndStretch() {
|
||||
Chart chart("MHz"); chart.addSeries("hidden");
|
||||
const qint64 start = 100000000;
|
||||
for (int i = 0; i <= 7200; ++i) chart.sample({{"hidden", double(i)}}, 500, start + i * 500);
|
||||
QCOMPARE(chart.history("hidden").size(), 7201); // Not capped at 600, even when hidden.
|
||||
chart.setSelected("hidden", true);
|
||||
QVERIFY(chart.readingAt(start + 500).contains("hidden: 1 MHz"));
|
||||
const auto end = double(start + 3600000);
|
||||
chart.setHistoryWindow(historyRetentionMs, true);
|
||||
QCOMPARE(chart.timeRange(), qMakePair(double(start), end));
|
||||
chart.setHistoryWindow(historyRetentionMs, false);
|
||||
QCOMPARE(chart.timeRange(), qMakePair(end - historyRetentionMs, end));
|
||||
chart.setHistoryWindow(300000, true);
|
||||
QCOMPARE(chart.timeRange(), qMakePair(end - 300000, end));
|
||||
QCOMPARE(chart.history("hidden", end - 10000).size(), 22); // Includes the clipping boundary.
|
||||
chart.sample({{"hidden", 1}}, 500, start + historyRetentionMs + 1000);
|
||||
const auto retained = chart.history("hidden");
|
||||
QCOMPARE(retained.first().x(), double(start + 1000));
|
||||
chart.setHistoryWindow(historyRetentionMs, true);
|
||||
QCOMPARE(chart.timeRange().first, double(start + 1000));
|
||||
}
|
||||
void monitorHistorySettings() {
|
||||
QTemporaryDir config; qputenv("XDG_CONFIG_HOME", config.path().toUtf8());
|
||||
{
|
||||
Window window;
|
||||
auto *slider = window.findChild<QSlider *>("monitorHistory"); QVERIFY(slider);
|
||||
auto *stretch = window.findChild<QCheckBox *>("monitorStretch"); QVERIFY(stretch);
|
||||
QCOMPARE(slider->value(), 1000); QVERIFY(stretch->isChecked());
|
||||
slider->setValue(0); stretch->setChecked(false);
|
||||
for (auto *chart : window.findChildren<Chart *>())
|
||||
QCOMPARE(chart->timeRange().second - chart->timeRange().first, 300000.);
|
||||
slider->setValue(500);
|
||||
QSettings settings("fedora-tools", "framework-laptop-tools");
|
||||
const int minutes = settings.value("monitor/historyMinutes").toInt();
|
||||
QVERIFY(minutes >= 84 && minutes <= 86); // Geometric, not arithmetic, midpoint.
|
||||
}
|
||||
Window restored;
|
||||
QCOMPARE(restored.findChild<QSlider *>("monitorHistory")->value(), 500);
|
||||
QVERIFY(!restored.findChild<QCheckBox *>("monitorStretch")->isChecked());
|
||||
QSettings settings("fedora-tools", "framework-laptop-tools");
|
||||
settings.setValue("tray/historyMs", 86400000);
|
||||
TrayPage tray(settings, {{"cpu-usage", "CPU usage", {}, "%"}});
|
||||
QCOMPARE(tray.iconStyle().historyMs, qint64(60000)); // Retired choices use the new default.
|
||||
}
|
||||
void denseHistorySoftensPeaks() {
|
||||
Chart chart("%"); chart.resize(600, 200); chart.addSeries("sensor"); chart.setSelected("sensor", true);
|
||||
const qint64 start = 100000000;
|
||||
// A half-second spike is faint, not an opaque excursion of the mean line.
|
||||
for (int i = 0; i <= 172800; ++i)
|
||||
chart.sample({{"sensor", i == 43200 ? 100. : 10.}}, 500, start + i * 500);
|
||||
QCOMPARE(chart.history("sensor").size(), 172801);
|
||||
QVERIFY(chart.readingAt(start + 43200 * 500).contains("sensor: 100 %"));
|
||||
const auto image = chart.grab().toImage();
|
||||
const auto shot = qEnvironmentVariable("FRAMEWORK_TOOLS_AVERAGE_SCREENSHOT");
|
||||
if (!shot.isEmpty()) QVERIFY(image.save(shot));
|
||||
bool peak = false;
|
||||
for (int y = 15; y < 60; ++y) for (int x = 100; x < 550; ++x) {
|
||||
const auto colour = image.pixelColor(x, y);
|
||||
QVERIFY(colour != chart.color("sensor"));
|
||||
peak |= colour.blue() > colour.red() + 10 && colour.green() > colour.red() + 5;
|
||||
}
|
||||
QVERIFY(peak);
|
||||
chart.setHistoryWindow(300000, true);
|
||||
const auto recent = chart.grab().toImage();
|
||||
for (int y = 15; y < 60; ++y) for (int x = 100; x < 550; ++x)
|
||||
QVERIFY(recent.pixelColor(x, y) != chart.color("sensor"));
|
||||
}
|
||||
void logicalTicks() {
|
||||
const auto freq = AxisTicks::covering(0, 4681, 4);
|
||||
QCOMPARE(freq.minimum, 0.); QCOMPARE(freq.maximum, 6000.); QCOMPARE(freq.step, 2000.);
|
||||
@@ -50,7 +262,8 @@ private Q_SLOTS:
|
||||
chart.setSelected("rate", false);
|
||||
QVERIFY(!chart.readingAt(112000).contains("Battery rate"));
|
||||
QVERIFY(chart.readingAt(200000).contains("Charge level: —"));
|
||||
for (int i = 0; i < 601; ++i) chart.sample({}, 30000, 200000 + i * 30000);
|
||||
chart.sample({}, 30000, historyRetentionMs + 200000);
|
||||
chart.sample({}, 30000, historyRetentionMs + 230000);
|
||||
QVERIFY(!chart.readingAt(106000).contains("Asleep")); // Annotations expire with samples.
|
||||
}
|
||||
void clickableLegend() {
|
||||
@@ -86,8 +299,9 @@ private Q_SLOTS:
|
||||
}
|
||||
void trayBoundsAndAppearance() {
|
||||
TrayStyle style; style.borderColor = Qt::white; style.backgroundColor = Qt::black;
|
||||
style.historyMs = 10;
|
||||
style.lineColor = Qt::green; style.fillColor = Qt::blue; style.outsideColor = Qt::red; style.fill = false;
|
||||
auto render = [&] { return telemetryIcon(true, {{0, 120}, {10, 120}}, "%", style).pixmap(64, 64).toImage(); };
|
||||
auto render = [&] { return telemetryIcon(true, {{0, 120}, {10, 120}}, "%", style, 10).pixmap(64, 64).toImage(); };
|
||||
auto image = render();
|
||||
QCOMPARE(image.pixelColor(32, 1), QColor(Qt::white));
|
||||
QCOMPARE(image.pixelColor(32, 3), QColor(Qt::red));
|
||||
@@ -97,10 +311,55 @@ private Q_SLOTS:
|
||||
style.clamp = true; style.overflowColor = false; image = render(); QCOMPARE(image.pixelColor(32, 1), QColor(Qt::green));
|
||||
QVERIFY(trayPlotRect(true).width() < trayPlotRect(false).width());
|
||||
style.fill = true;
|
||||
image = telemetryIcon(true, {{0, 50}, {10, 50}}, "%", style).pixmap(64, 64).toImage();
|
||||
image = telemetryIcon(true, {{0, 50}, {10, 50}}, "%", style, 10).pixmap(64, 64).toImage();
|
||||
QCOMPARE(image.pixelColor(32, 50), QColor(Qt::blue));
|
||||
QCOMPARE(image.pixelColor(32, 10).alpha(), 0);
|
||||
}
|
||||
void trayHistoryWindow() {
|
||||
TrayStyle style; style.borderColor = Qt::white; style.backgroundColor = Qt::black;
|
||||
style.lineColor = Qt::green; style.fillColor = Qt::blue; style.fill = false;
|
||||
style.historyMs = 60000;
|
||||
const QVector<QPointF> points{{0, 50}, {60000, 50}, {90000, 50}};
|
||||
auto render = [&](qint64 now) { return telemetryIcon(true, points, "%", style, now).pixmap(64, 64).toImage(); };
|
||||
auto image = render(120000);
|
||||
QCOMPARE(image.pixelColor(10, 32), QColor(Qt::green)); // Segment crossing the left boundary is clipped.
|
||||
QCOMPARE(image.pixelColor(50, 32), QColor(Qt::black)); // No extrapolation from the latest sample to now.
|
||||
QCOMPARE(image.pixelColor(1, 32), QColor(Qt::white)); // Clipping preserves the border.
|
||||
image = render(240000);
|
||||
QCOMPARE(image.pixelColor(32, 32), QColor(Qt::black)); // All readings have expired from the view.
|
||||
style.historyMs = 120000;
|
||||
image = render(90000);
|
||||
QCOMPARE(image.pixelColor(5, 32), QColor(Qt::black)); // Unavailable older history stays blank.
|
||||
QCOMPARE(image.pixelColor(32, 32), QColor(Qt::green));
|
||||
style.fill = true;
|
||||
image = telemetryIcon(true, {{0, 50}, {30000, 50}, {60000, NAN}, {90000, 50}, {120000, 50}}, "%", style, 120000).pixmap(64, 64).toImage();
|
||||
QCOMPARE(image.pixelColor(10, 50), QColor(Qt::blue));
|
||||
QCOMPARE(image.pixelColor(32, 50), QColor(Qt::black)); // Sleep/data gaps remain gaps.
|
||||
const auto number = telemetryIcon(false, points, "%", style, 120000).pixmap(64, 64).toImage();
|
||||
style.historyMs = 10000;
|
||||
QCOMPARE(telemetryIcon(false, points, "%", style, 120000).pixmap(64, 64).toImage(), number);
|
||||
QCOMPARE(image.pixelColor(60, 32), QColor(Qt::green));
|
||||
}
|
||||
void traySoftensPeaks() {
|
||||
TrayStyle style; style.border = false; style.backgroundColor = Qt::black;
|
||||
style.lineColor = Qt::green; style.fill = false; style.historyMs = 300000;
|
||||
QVector<QPointF> points;
|
||||
for (int i = 0; i <= 600; ++i) points.append({double(i * 500), i == 300 ? 100. : 10.});
|
||||
const auto image = telemetryIcon(true, points, "%", style, 300000).pixmap(64, 64).toImage();
|
||||
const auto shot = qEnvironmentVariable("FRAMEWORK_TOOLS_AVERAGE_SCREENSHOT");
|
||||
if (!shot.isEmpty()) QVERIFY(image.save(shot + ".tray.png"));
|
||||
bool band = false;
|
||||
for (int y = 4; y < 30; ++y) for (int x = 20; x < 45; ++x) {
|
||||
const auto colour = image.pixelColor(x, y);
|
||||
QVERIFY(colour != QColor(Qt::green));
|
||||
band |= colour.green() > 0 && colour.green() < 100;
|
||||
}
|
||||
QVERIFY(band);
|
||||
bool mean = false;
|
||||
for (int y = 50; y < 62; ++y) for (int x = 5; x < 59; ++x)
|
||||
mean |= image.pixelColor(x, y) == QColor(Qt::green);
|
||||
QVERIFY(mean);
|
||||
}
|
||||
void sharedHistoryAndTrayScales() {
|
||||
Chart chart("MHz"); chart.addSeries("cpu"); chart.addSeries("gpu");
|
||||
chart.sample({{"cpu", 1000}, {"gpu", 500}}, 1000, 1000);
|
||||
@@ -114,6 +373,13 @@ private Q_SLOTS:
|
||||
QTemporaryDir root; QSettings settings(root.filePath("tray.ini"), QSettings::IniFormat);
|
||||
TrayPage page(settings, {{"cpu", "CPU", {}, "MHz"}, {"temp", "CPU temperature", {}, "°C"}, {"rate", "Battery rate", {}, "W"}});
|
||||
const auto original = page.draft();
|
||||
auto *history = page.findChild<QComboBox *>("trayHistory"); QVERIFY(history);
|
||||
QCOMPARE(page.iconStyle().historyMs, qint64(60000));
|
||||
QCOMPARE(history->count(), 7);
|
||||
const QList<int> spans{10000, 15000, 20000, 30000, 60000, 120000, 300000};
|
||||
for (int i = 0; i < spans.size(); ++i) QCOMPARE(history->itemData(i).toInt(), spans[i]);
|
||||
history->setCurrentIndex(history->findData(300000));
|
||||
QCOMPARE(page.iconStyle().historyMs, qint64(300000));
|
||||
auto *mode = page.findChild<QComboBox *>("trayDisplayMode"); mode->setCurrentIndex(1);
|
||||
auto *metric = page.findChild<QComboBox *>("trayMetric");
|
||||
auto *minimum = page.findChild<QDoubleSpinBox *>("trayMinimum"); auto *maximum = page.findChild<QDoubleSpinBox *>("trayMaximum");
|
||||
@@ -130,6 +396,7 @@ private Q_SLOTS:
|
||||
if (!shot.isEmpty()) { page.resize(700, 780); page.show(); QTest::qWait(10); QVERIFY(page.grab().save(shot)); }
|
||||
QVERIFY(settings.allKeys().isEmpty()); // Editing never writes persistent settings.
|
||||
page.load(original);
|
||||
QCOMPARE(history->currentData().toInt(), 60000);
|
||||
QCOMPARE(mode->currentIndex(), 0); QCOMPARE(metric->currentIndex(), 0);
|
||||
QVERIFY(!settings.contains("tray/scales/rate/minimum"));
|
||||
}
|
||||
@@ -310,24 +577,49 @@ private Q_SLOTS:
|
||||
QTRY_VERIFY(save->isEnabled()); QVERIFY(bar->isHidden());
|
||||
auto *fast = window.findChild<QComboBox *>("fastInterval");
|
||||
auto *mode = window.findChild<QComboBox *>("trayDisplayMode");
|
||||
auto *history = window.findChild<QComboBox *>("trayHistory");
|
||||
auto *autostart = window.findChild<QCheckBox *>("loginAutostart");
|
||||
auto *hoverCpu = window.findChild<QCheckBox *>("hover/cpu");
|
||||
auto *hoverApps = window.findChild<QSpinBox *>("hover/topApps");
|
||||
auto *trayIcon = window.findChild<QSystemTrayIcon *>();
|
||||
const QString path = config.path() + "/autostart/se.ajpanton.framework-laptop-tools.desktop";
|
||||
fast->setCurrentIndex(fast->findData(4000));
|
||||
QVERIFY(!bar->isHidden()); tabs->setCurrentIndex(5); mode->setCurrentIndex(1);
|
||||
history->setCurrentIndex(history->findData(300000));
|
||||
autostart->setChecked(true);
|
||||
hoverCpu->setChecked(false);
|
||||
hoverApps->setValue(2);
|
||||
QVERIFY(trayIcon->toolTip().startsWith("CPU ")); // Unsaved edits do not alter the live tooltip.
|
||||
QCOMPARE(settings.value("sampling/fast").toInt(), 1000);
|
||||
QVERIFY(!settings.contains("tray/mode")); QVERIFY(!QFile::exists(path));
|
||||
QVERIFY(!settings.contains("tray/historyMs"));
|
||||
tabs->setCurrentIndex(0); QVERIFY(!bar->isHidden());
|
||||
QTRY_VERIFY(undo->isEnabled()); undo->click();
|
||||
QVERIFY(bar->isHidden()); QCOMPARE(fast->currentData().toInt(), 1000); QCOMPARE(mode->currentIndex(), 0);
|
||||
QVERIFY(!autostart->isChecked()); QVERIFY(!QFile::exists(path));
|
||||
QVERIFY(hoverCpu->isChecked());
|
||||
QCOMPARE(hoverApps->value(), 0);
|
||||
QCOMPARE(history->currentData().toInt(), 60000);
|
||||
fast->setCurrentIndex(fast->findData(2000)); mode->setCurrentIndex(2); autostart->setChecked(true);
|
||||
history->setCurrentIndex(history->findData(60000));
|
||||
hoverCpu->setChecked(false);
|
||||
hoverApps->setValue(3);
|
||||
QTRY_VERIFY(save->isEnabled()); save->click();
|
||||
QVERIFY(bar->isHidden()); QCOMPARE(settings.value("sampling/fast").toInt(), 2000);
|
||||
QCOMPARE(settings.value("tray/mode").toString(), QString("number")); QVERIFY(QFile::exists(path));
|
||||
QCOMPARE(settings.value("tray/historyMs").toInt(), 60000);
|
||||
QVERIFY(!settings.value("tray/hover/cpu").toBool());
|
||||
QCOMPARE(settings.value("tray/hover/topApps").toInt(), 3);
|
||||
QVERIFY(!trayIcon->toolTip().startsWith("CPU "));
|
||||
hoverCpu->setChecked(true);
|
||||
hoverApps->setValue(1);
|
||||
history->setCurrentIndex(history->findData(30000));
|
||||
fast->setCurrentIndex(fast->findData(500)); mode->setCurrentIndex(1);
|
||||
QTRY_VERIFY(undo->isEnabled()); undo->click();
|
||||
QCOMPARE(fast->currentData().toInt(), 2000); QCOMPARE(mode->currentIndex(), 2); QVERIFY(bar->isHidden());
|
||||
QCOMPARE(history->currentData().toInt(), 60000);
|
||||
QVERIFY(!hoverCpu->isChecked());
|
||||
QCOMPARE(hoverApps->value(), 3);
|
||||
// Hover is a timestamp shared by every graph, not the same screen coordinate.
|
||||
const auto charts = window.findChildren<Chart *>(); QCOMPARE(charts.size(), 4);
|
||||
const qint64 time = QDateTime::currentMSecsSinceEpoch() - 1000;
|
||||
|
||||
Reference in New Issue
Block a user