Files
fedora-tools/framework-laptop-tools/src/cpupage.cpp
T

164 lines
9.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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);
}