164 lines
9.4 KiB
C++
164 lines
9.4 KiB
C++
// SPDX-License-Identifier: MIT
|
||
#include "cpupage.h"
|
||
#include <QFormLayout>
|
||
#include <QHBoxLayout>
|
||
#include <QVBoxLayout>
|
||
#include <QPushButton>
|
||
|
||
QVariantMap CpuPage::Editor::value() const
|
||
{
|
||
QVariantMap result{{"frequencyOverride", frequency->isChecked() && bounds.isEmpty()}, {"minimum", minimum->value()}, {"maximum", maximum->value()},
|
||
{"governor", governor->currentText()}, {"preference", preference->currentText()}};
|
||
if (!bounds.isEmpty()) {
|
||
QVariantMap groups;
|
||
for (auto it = bounds.cbegin(); it != bounds.cend(); ++it) groups[it.key()] = QVariantMap{
|
||
{"frequencyOverride", it->enabled->isChecked()}, {"minimum", it->minimum->value()}, {"maximum", it->maximum->value()}};
|
||
result["bounds"] = groups;
|
||
}
|
||
return result;
|
||
}
|
||
void CpuPage::Editor::setValue(const QVariantMap &value)
|
||
{
|
||
minimum->setValue(value["minimum"].toInt()); maximum->setValue(value["maximum"].toInt());
|
||
frequency->setChecked(value.value("frequencyOverride").toBool());
|
||
minimum->setEnabled(frequency->isChecked()); maximum->setEnabled(frequency->isChecked());
|
||
governor->setCurrentText(value.value("governor", "auto").toString()); preference->setCurrentText(value.value("preference", "auto").toString());
|
||
for (auto it = bounds.cbegin(); it != bounds.cend(); ++it) {
|
||
const auto p = value.contains("bounds") ? value["bounds"].toMap().value(it.key()).toMap() : value;
|
||
it->minimum->setValue(p.value("minimum", it->minimum->minimum()).toInt());
|
||
it->maximum->setValue(p.value("maximum", it->maximum->maximum()).toInt());
|
||
it->enabled->setChecked(p.value("frequencyOverride").toBool());
|
||
it->minimum->setEnabled(it->enabled->isChecked()); it->maximum->setEnabled(it->enabled->isChecked());
|
||
}
|
||
}
|
||
CpuPage::Editor CpuPage::makeEditor(const QString &name)
|
||
{
|
||
Editor editor{new QGroupBox(name), new QCheckBox("Override frequency bounds"),
|
||
new ValueControl, new ValueControl, new QComboBox, new QComboBox, new QLabel};
|
||
editor.frequency->setObjectName("overrideFrequency");
|
||
editor.governor->setObjectName("cpuGovernor"); editor.preference->setObjectName("cpuPreference");
|
||
editor.warning->setWordWrap(true); editor.warning->setObjectName("governorWarning");
|
||
auto *layout = new QFormLayout(editor.group);
|
||
const auto limits = cpuLimits();
|
||
layout->addRow(editor.frequency);
|
||
editor.frequency->setToolTip("When unchecked, this tool does not write frequency limits. Existing limits are left unchanged.");
|
||
for (auto *box : {editor.minimum, editor.maximum}) {
|
||
box->setRange(limits.value("low", 400).toInt(), limits.value("high", 5000).toInt());
|
||
box->setSuffix(" MHz"); box->setSingleStep(100);
|
||
connect(box, &ValueControl::valueChanged, this, [this] { changed(); });
|
||
}
|
||
editor.governor->addItem("auto"); editor.preference->addItem("auto");
|
||
editor.governor->addItems(cpuOptions("scaling_available_governors"));
|
||
editor.preference->addItems(cpuOptions("energy_performance_available_preferences"));
|
||
for (auto *combo : {editor.governor, editor.preference}) {
|
||
combo->setToolTip("Auto leaves this setting to Linux or other controllers; it does not restore a preset. Save and apply commits changes.");
|
||
connect(combo, &QComboBox::currentTextChanged, this, [this] { changed(); });
|
||
}
|
||
layout->addRow("Minimum frequency:", editor.minimum); layout->addRow("Maximum frequency:", editor.maximum);
|
||
const auto groups = cpuPolicyGroups();
|
||
if (!groups.isEmpty()) {
|
||
layout->setRowVisible(editor.frequency, false); layout->setRowVisible(editor.minimum, false); layout->setRowVisible(editor.maximum, false);
|
||
for (const QString &key : {QString("p"), QString("e")}) {
|
||
Editor::Bounds b{new QCheckBox(key == "p" ? "Override P-core frequency bounds" : "Override E-core frequency bounds"), new ValueControl, new ValueControl};
|
||
const auto limits = cpuLimits("/sys", key);
|
||
for (auto *box : {b.minimum, b.maximum}) {
|
||
box->setRange(limits["low"].toInt(), limits["high"].toInt()); box->setSuffix(" MHz"); box->setSingleStep(100);
|
||
connect(box, &ValueControl::valueChanged, this, [this] { changed(); });
|
||
}
|
||
b.enabled->setObjectName("overrideFrequency_" + key);
|
||
layout->addRow(b.enabled); layout->addRow("Minimum frequency:", b.minimum); layout->addRow("Maximum frequency:", b.maximum);
|
||
connect(b.enabled, &QCheckBox::toggled, this, [this, b](bool enabled) { b.minimum->setEnabled(enabled); b.maximum->setEnabled(enabled); changed(); });
|
||
editor.bounds.insert(key, b);
|
||
}
|
||
}
|
||
layout->addRow("Energy preference:", editor.preference);
|
||
auto *row = new QWidget; auto *line = new QHBoxLayout(row); line->setContentsMargins(0, 0, 0, 0);
|
||
editor.governor->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||
line->addWidget(editor.governor); line->addWidget(editor.warning, 1); line->addStretch();
|
||
layout->addRow("CPU governor:", row);
|
||
connect(editor.frequency, &QCheckBox::toggled, this, [this, editor](bool enabled) {
|
||
editor.minimum->setEnabled(enabled); editor.maximum->setEnabled(enabled); changed();
|
||
});
|
||
return editor;
|
||
}
|
||
CpuPage::CpuPage(QWidget *parent) : QWidget(parent)
|
||
{
|
||
auto *layout = new QVBoxLayout(this);
|
||
m_status = new QLabel; m_status->setWordWrap(true); layout->addWidget(m_status);
|
||
m_separate = new QCheckBox("Use separate profiles for battery power and AC");
|
||
m_separate->setObjectName("separateCpuProfiles"); layout->addWidget(m_separate);
|
||
m_separate->setToolTip("Splitting copies settings to both sides. Joining keeps the battery settings, committed only with Save and apply.");
|
||
auto *columns = new QHBoxLayout;
|
||
m_battery = makeEditor("CPU settings"); m_ac = makeEditor("AC power");
|
||
m_battery.group->setObjectName("batteryCpuProfile"); m_ac.group->setObjectName("acCpuProfile");
|
||
columns->addWidget(m_battery.group, 1); columns->addWidget(m_ac.group, 1); layout->addLayout(columns);
|
||
layout->addStretch();
|
||
connect(m_separate, &QCheckBox::toggled, this, &CpuPage::split);
|
||
load({}, true);
|
||
}
|
||
void CpuPage::changed()
|
||
{
|
||
if (!m_loading) {
|
||
// Joining hides the AC draft; it must survive refresh until saved or undone.
|
||
const auto savedAc = m_saved.value(m_saved.value("separate").toBool() ? "ac" : "battery").toMap();
|
||
m_dirty = draft() != m_saved || (m_hasAcDraft && !m_separate->isChecked() && m_ac.value() != savedAc);
|
||
refreshStatus(); Q_EMIT draftChanged();
|
||
}
|
||
}
|
||
void CpuPage::split(bool separate)
|
||
{
|
||
if (separate && !m_hasAcDraft) { m_ac.setValue(m_battery.value()); m_hasAcDraft = true; }
|
||
m_ac.group->setVisible(separate); m_battery.group->setTitle(separate ? "Battery power" : "CPU settings");
|
||
changed();
|
||
}
|
||
QVariantMap CpuPage::draft() const
|
||
{
|
||
QVariantMap config{{"separate", m_separate->isChecked()}, {"battery", m_battery.value()}};
|
||
if (m_separate->isChecked()) config["ac"] = m_ac.value();
|
||
return config;
|
||
}
|
||
void CpuPage::load(const QVariantMap &config, bool force)
|
||
{
|
||
if (m_dirty && !force) return;
|
||
m_loading = true;
|
||
auto saved = normalizeCpuConfig(config);
|
||
if (saved.isEmpty()) {
|
||
const auto limits = cpuLimits();
|
||
saved = {{"separate", false}, {"battery", QVariantMap{{"frequencyOverride", false},
|
||
{"minimum", limits.value("min", 400)}, {"maximum", limits.value("max", 5000)},
|
||
{"governor", "auto"}, {"preference", "auto"}}}};
|
||
}
|
||
m_saved = saved;
|
||
m_battery.setValue(saved["battery"].toMap());
|
||
m_hasAcDraft = saved["separate"].toBool();
|
||
if (m_hasAcDraft) m_ac.setValue(saved["ac"].toMap());
|
||
else m_ac.setValue(saved["battery"].toMap());
|
||
m_separate->setChecked(m_hasAcDraft); split(m_hasAcDraft);
|
||
m_saved = draft(); m_loading = false; m_dirty = false; refreshStatus(); Q_EMIT draftChanged();
|
||
}
|
||
void CpuPage::refreshStatus()
|
||
{
|
||
const auto limits = cpuLimits(); const auto ac = onAcPower();
|
||
m_status->setText(limits.isEmpty() ? "Live CPU readings unavailable." :
|
||
QString("Live: %1\nEnergy preference: %3\nGovernor: %2\nFrequency bounds: %4–%5 MHz (highest across policies); hardware maximum: %6 MHz.%7")
|
||
.arg(ac ? (*ac ? "AC power" : "battery power") : "power source unknown", limits["governor"].toString(), limits["preference"].toString())
|
||
.arg(limits["min"].toInt()).arg(limits["max"].toInt()).arg(limits["high"].toInt())
|
||
.arg(m_dirty ? "\nUnsaved changes." : ""));
|
||
auto warning = [&](Editor &editor, bool active) {
|
||
QString text;
|
||
const auto p = editor.value();
|
||
const QString governor = p["governor"].toString(), pref = p["preference"].toString();
|
||
if (readText("/sys/devices/system/cpu/cpufreq/policy0/scaling_driver") == "intel_pstate") {
|
||
if (governor == "performance" && pref == "auto")
|
||
text = "⚠ Requires performance EPP; may fall back to powersave.";
|
||
else if (governor == "performance" && pref != "performance")
|
||
text = "⚠ Requires performance EPP; will fall back to powersave.";
|
||
else if (governor == "auto" && pref != "auto" && pref != "performance" && active && limits["governor"] == "performance")
|
||
text = "⚠ Current Performance governor blocks this EPP. Select Powersave or leave EPP on Auto.";
|
||
}
|
||
editor.warning->setText(text); editor.warning->setVisible(!text.isEmpty());
|
||
};
|
||
warning(m_battery, !m_separate->isChecked() || (ac && !*ac));
|
||
warning(m_ac, m_separate->isChecked() && ac && *ac);
|
||
}
|