system settings
git clone https://git.lucas.co/cce-system-interface.git
feat: move CPU Governor and GPU Power onto the Power page
Both levers lived on System, which is otherwise a read-only status page, and
both had been dead for some time. They drove `pkexec` on a helper script at
`$XDG_DATA_HOME/cce-settings/helpers/…`, and the app's renames left three
directory names that never agreed:
code called ~/.local/share/cce-settings/helpers/ (absent)
scripts live at ~/.local/share/cce-system-interface/helpers/ (present)
polkit authorized ~/.local/share/system-control-interface/… (absent)
So the chain was broken at two links: pkexec was handed a path that does not
exist, and even fixed it would have been refused, since the .policy files
annotate exec.path with the oldest of the three names. `spawn_detached`
discards the error and the handlers set `cpu_powersave = true` optimistically,
so the UI reported a change that never happened. None of those helpers or
policies were ever versioned — `git log --all -- '*powersave*'` is empty — so
a fresh clone never had them at all.
Rather than carry that plumbing across, both are re-expressed in the Power
page's idiom, which needs no external files:
- CPU Governor: choices discovered from `scaling_available_governors` rather
than hardcoded to two, written to every cpu (the EPP rule — a partial write
splits the package), presence-gated on the file existing.
- GPU Power Limit: watts discovered from nvidia-smi (`power.limit`,
`power.default_limit`, `power.min_limit`), gated on nvidia-smi actually
answering. An off-list current value gets its own row, the charge-limit
rule. This host has no nvidia module loaded, so the dropdown correctly does
not render — which is the honest outcome and better than the old page, which
showed a control that could not work.
`write_sysfs` becomes `run_privileged`: the GPU call shells out to nvidia-smi,
so the name should not claim sysfs. section_widgets mirrors both new gates and
the test now walks all six levers down to two as interfaces disappear.
System keeps its read-only half (System, System Actions, CPU, GPU, Battery)
and drops to 5 sections; its two dead `*_info_box` fields — updated on every
refresh but never painted, the view built fresh local ones — go too.
Shadow-verified: Power shows CPU Governor reading the real sysfs value
(Powersave) with GPU absent as gated; System renders its 5 sections; zero
unregistered-root drops on either. The privileged writes are deliberately not
exercised — the shadow shares the real D-Bus session, so the polkit prompt
would land on the user's screen.
Co-Authored-By: Claude <[email protected]>
src/pages/power.rs | 172 ++++++++++++++++++++++++++++++++--
src/pages/system_info.rs | 233 ++---------------------------------------------
2 files changed, 170 insertions(+), 235 deletions(-)
diff --git a/src/pages/power.rs b/src/pages/power.rs
index c5c6b89..0f1956e 100644
--- a/src/pages/power.rs
+++ b/src/pages/power.rs
@@ -44,6 +44,17 @@ pub struct PowerFacts {
pub charge_limit: Option<u32>,
/// intel_pstate no_turbo, inverted to "turbo enabled".
pub turbo: Option<bool>,
+ /// scaling_available_governors, and cpu0's active one. Moved here from the
+ /// System page, which drove it through an unversioned pkexec helper script
+ /// whose path had not survived two renames of this app.
+ pub governors: Vec<String>,
+ pub governor: String,
+ /// NVIDIA power limit in watts: (current, default, minimum). None whenever
+ /// nvidia-smi cannot reach a driver — including a host where the module is
+ /// simply not loaded — which is what gates the dropdown away.
+ pub gpu_limit_w: Option<u32>,
+ pub gpu_default_w: Option<u32>,
+ pub gpu_min_w: Option<u32>,
}
#[derive(Debug, Clone)]
@@ -54,8 +65,12 @@ pub struct PowerState {
pub dd_epp: cce_ui::widget::Adapted<Dropdown>,
pub dd_limit: cce_ui::widget::Adapted<Dropdown>,
pub dd_turbo: cce_ui::widget::Adapted<Dropdown>,
+ pub dd_governor: cce_ui::widget::Adapted<Dropdown>,
+ pub dd_gpu: cce_ui::widget::Adapted<Dropdown>,
/// Sysfs value per charge-limit dropdown row (options are display text).
pub limit_values: Vec<u32>,
+ /// Watts per GPU-limit dropdown row (options are display text).
+ pub gpu_values: Vec<u32>,
}
impl Default for PowerState {
@@ -68,7 +83,10 @@ impl Default for PowerState {
dd_limit: Dropdown::new(vec!["—".to_string()], 0).with_label("Battery Charge Limit"),
dd_turbo: Dropdown::new(vec!["Enabled".to_string(), "Disabled".to_string()], 0)
.with_label("CPU Turbo Boost"),
+ dd_governor: Dropdown::new(vec!["—".to_string()], 0).with_label("CPU Governor"),
+ dd_gpu: Dropdown::new(vec!["—".to_string()], 0).with_label("GPU Power Limit"),
limit_values: Vec::new(),
+ gpu_values: Vec::new(),
}
}
}
@@ -81,6 +99,8 @@ pub enum PowerMessage {
SetEpp(usize),
SetLimit(usize),
SetTurbo(usize),
+ SetGovernor(usize),
+ SetGpuLimit(usize),
}
/// Sysfs tokens travel into a `pkexec sh -c` line, so only the shapes sysfs
@@ -91,10 +111,12 @@ fn sysfs_token_ok(s: &str) -> bool {
!s.is_empty() && s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
}
-/// Root sysfs write via pkexec, the app's standard privileged-action path.
-/// Detached: the polkit prompt runs in its own process, the UI never blocks,
-/// and the watcher's next read reports what actually happened.
-fn write_sysfs(cmd: String) {
+/// Root action via pkexec, the app's standard privileged path. Detached: the
+/// polkit prompt runs in its own process, the UI never blocks, and the
+/// watcher's next read reports what actually happened. Most callers echo into
+/// sysfs; the GPU limit shells out to nvidia-smi, which is why this is not
+/// named for sysfs.
+fn run_privileged(cmd: String) {
let _ = std::process::Command::new("pkexec")
.args(["sh", "-c", &cmd])
.spawn();
@@ -169,6 +191,32 @@ pub async fn fetch_power_state() -> PowerFacts {
f.turbo = read_trim("/sys/devices/system/cpu/intel_pstate/no_turbo").map(|s| s == "0");
+ if let Some(govs) = read_trim("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") {
+ f.governors = govs.split_whitespace().map(String::from).collect();
+ f.governor = read_trim("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor").unwrap_or_default();
+ }
+
+ // One nvidia-smi call for all three watt figures; any failure (no driver,
+ // module not loaded, no card) leaves them None and hides the dropdown.
+ if let Ok(out) = tokio::process::Command::new("nvidia-smi")
+ .args(["--query-gpu=power.limit,power.default_limit,power.min_limit", "--format=csv,noheader,nounits"])
+ .output()
+ .await
+ {
+ if out.status.success() {
+ let text = String::from_utf8_lossy(&out.stdout);
+ if let Some(row) = text.lines().next() {
+ let w: Vec<Option<u32>> = row
+ .split(',')
+ .map(|c| c.trim().parse::<f32>().ok().map(|v| v.round() as u32))
+ .collect();
+ f.gpu_limit_w = w.first().copied().flatten();
+ f.gpu_default_w = w.get(1).copied().flatten();
+ f.gpu_min_w = w.get(2).copied().flatten();
+ }
+ }
+ }
+
f
}
@@ -208,6 +256,33 @@ fn rebuild_options(state: &mut PowerState) {
.unwrap_or(0);
state.limit_values = values;
}
+ if !state.dd_governor.open {
+ state.dd_governor.options = f.governors.iter().map(|g| pretty(g)).collect();
+ state.dd_governor.selected =
+ f.governors.iter().position(|g| *g == f.governor).unwrap_or(0);
+ }
+ if !state.dd_gpu.open {
+ // Default and minimum, plus the current draw when it is neither — the
+ // charge-limit rule: an off-list value gets its own row rather than
+ // silently matching the wrong one.
+ let mut vals: Vec<u32> = Vec::new();
+ if let Some(d) = f.gpu_default_w { vals.push(d); }
+ if let Some(m) = f.gpu_min_w { if !vals.contains(&m) { vals.push(m); } }
+ if let Some(c) = f.gpu_limit_w { if !vals.contains(&c) { vals.push(c); } }
+ state.dd_gpu.options = vals
+ .iter()
+ .map(|w| {
+ if Some(*w) == f.gpu_default_w { format!("{} W (default)", w) }
+ else if Some(*w) == f.gpu_min_w { format!("{} W (minimum)", w) }
+ else { format!("{} W", w) }
+ })
+ .collect();
+ state.dd_gpu.selected = f
+ .gpu_limit_w
+ .and_then(|c| vals.iter().position(|v| *v == c))
+ .unwrap_or(0);
+ state.gpu_values = vals;
+ }
if !state.dd_turbo.open {
state.dd_turbo.selected = if f.turbo.unwrap_or(true) { 0 } else { 1 };
}
@@ -266,6 +341,10 @@ pub fn view(state: &mut PowerState, cx: f32, cy: f32, cw: f32, ch: f32, _root_fo
state.dd_epp.set_row_rect(stack.context.left + 14.0, sec_w - 28.0);
stack.add_widget(&mut state.dd_epp, sec_w - 28.0, 44.0, ctx);
}
+ if !state.facts.governors.is_empty() {
+ state.dd_governor.set_row_rect(stack.context.left + 14.0, sec_w - 28.0);
+ stack.add_widget(&mut state.dd_governor, sec_w - 28.0, 44.0, ctx);
+ }
if state.facts.charge_limit.is_some() {
state.dd_limit.set_row_rect(stack.context.left + 14.0, sec_w - 28.0);
stack.add_widget(&mut state.dd_limit, sec_w - 28.0, 44.0, ctx);
@@ -274,6 +353,10 @@ pub fn view(state: &mut PowerState, cx: f32, cy: f32, cw: f32, ch: f32, _root_fo
state.dd_turbo.set_row_rect(stack.context.left + 14.0, sec_w - 28.0);
stack.add_widget(&mut state.dd_turbo, sec_w - 28.0, 44.0, ctx);
}
+ if !state.gpu_values.is_empty() {
+ state.dd_gpu.set_row_rect(stack.context.left + 14.0, sec_w - 28.0);
+ stack.add_widget(&mut state.dd_gpu, sec_w - 28.0, 44.0, ctx);
+ }
});
final_pc
@@ -291,7 +374,7 @@ pub fn update(state: &mut PowerState, msg: PowerMessage) {
PowerMessage::SetProfile(idx) => {
if let Some(p) = state.facts.profiles.get(idx) {
if sysfs_token_ok(p) {
- write_sysfs(format!("echo {} > /sys/firmware/acpi/platform_profile", p));
+ run_privileged(format!("echo {} > /sys/firmware/acpi/platform_profile", p));
state.facts.profile = p.clone();
}
}
@@ -301,7 +384,7 @@ pub fn update(state: &mut PowerState, msg: PowerMessage) {
if sysfs_token_ok(p) {
// Every core: EPP is per-cpu and a partial write would
// leave the package split across preferences.
- write_sysfs(format!(
+ run_privileged(format!(
"for f in /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference; do echo {} > \"$f\"; done",
p
));
@@ -312,7 +395,7 @@ pub fn update(state: &mut PowerState, msg: PowerMessage) {
PowerMessage::SetLimit(idx) => {
if let Some(v) = state.limit_values.get(idx).copied() {
if (1..=100).contains(&v) {
- write_sysfs(format!(
+ run_privileged(format!(
"for f in /sys/class/power_supply/BAT*/charge_control_end_threshold; do echo {} > \"$f\"; done",
v
));
@@ -320,9 +403,34 @@ pub fn update(state: &mut PowerState, msg: PowerMessage) {
}
}
}
+ PowerMessage::SetGovernor(idx) => {
+ if let Some(g) = state.facts.governors.get(idx) {
+ if sysfs_token_ok(g) {
+ // Every core, like EPP: a partial write leaves the package
+ // split across governors.
+ run_privileged(format!(
+ "for f in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do echo {} > \"$f\"; done",
+ g
+ ));
+ state.facts.governor = g.clone();
+ }
+ }
+ }
+ PowerMessage::SetGpuLimit(idx) => {
+ if let Some(w) = state.gpu_values.get(idx).copied() {
+ // Bounded by what nvidia-smi itself reported, so the number
+ // reaching the shell line cannot be arbitrary.
+ let within = state.facts.gpu_min_w.is_none_or(|m| w >= m)
+ && state.facts.gpu_default_w.is_none_or(|d| w <= d.max(w));
+ if within {
+ run_privileged(format!("nvidia-smi -pl {}", w));
+ state.facts.gpu_limit_w = Some(w);
+ }
+ }
+ }
PowerMessage::SetTurbo(idx) => {
let no_turbo = if idx == 1 { 1 } else { 0 };
- write_sysfs(format!(
+ run_privileged(format!(
"echo {} > /sys/devices/system/cpu/intel_pstate/no_turbo",
no_turbo
));
@@ -346,12 +454,18 @@ impl crate::pages::AppPage for PowerState {
if !self.facts.epps.is_empty() {
ids.push(self.dd_epp.id());
}
+ if !self.facts.governors.is_empty() {
+ ids.push(self.dd_governor.id());
+ }
if self.facts.charge_limit.is_some() {
ids.push(self.dd_limit.id());
}
if self.facts.turbo.is_some() {
ids.push(self.dd_turbo.id());
}
+ if !self.gpu_values.is_empty() {
+ ids.push(self.dd_gpu.id());
+ }
vec![ids]
}
@@ -382,6 +496,12 @@ impl crate::pages::AppPage for PowerState {
if self.dd_turbo.take_change() {
actions.push(AppAction::Power(PowerMessage::SetTurbo(self.dd_turbo.selected)));
}
+ if self.dd_governor.take_change() {
+ actions.push(AppAction::Power(PowerMessage::SetGovernor(self.dd_governor.selected)));
+ }
+ if self.dd_gpu.take_change() {
+ actions.push(AppAction::Power(PowerMessage::SetGpuLimit(self.dd_gpu.selected)));
+ }
}
}
@@ -404,6 +524,11 @@ mod tests {
epp: "balance_power".to_string(),
charge_limit: Some(80),
turbo: Some(true),
+ governors: vec!["performance".into(), "powersave".into()],
+ governor: "powersave".to_string(),
+ gpu_limit_w: Some(80),
+ gpu_default_w: Some(80),
+ gpu_min_w: Some(5),
}
}
@@ -468,11 +593,38 @@ mod tests {
assert_eq!(st.section_widgets(), vec![Vec::new()]);
st.loaded = true;
st.facts = facts();
- // All four interfaces present: all four dropdowns reported.
- assert_eq!(st.section_widgets()[0].len(), 4);
+ rebuild_options(&mut st);
+ // Every interface present: profile, epp, governor, limit, turbo, gpu.
+ assert_eq!(st.section_widgets()[0].len(), 6);
// A host without a charge-limit knob or turbo file reports fewer.
st.facts.charge_limit = None;
st.facts.turbo = None;
+ assert_eq!(st.section_widgets()[0].len(), 4);
+ // No cpufreq governors and no NVIDIA driver: both drop out too. The
+ // GPU gate is gpu_values, which rebuild_options derives from the facts
+ // — the same predicate the view paints on.
+ st.facts.governors.clear();
+ st.facts.gpu_limit_w = None;
+ st.facts.gpu_default_w = None;
+ st.facts.gpu_min_w = None;
+ rebuild_options(&mut st);
assert_eq!(st.section_widgets()[0].len(), 2);
}
+
+ #[test]
+ fn gpu_rows_are_default_min_and_an_off_list_current() {
+ let mut st = PowerState::default();
+ st.loaded = true;
+ st.facts = facts();
+ // Current == default: two rows, no duplicate.
+ rebuild_options(&mut st);
+ assert_eq!(st.gpu_values, vec![80, 5]);
+ assert_eq!(st.dd_gpu.selected, 0);
+ // A current limit that is neither default nor minimum earns its own
+ // row rather than silently selecting the wrong one.
+ st.facts.gpu_limit_w = Some(60);
+ rebuild_options(&mut st);
+ assert_eq!(st.gpu_values, vec![80, 5, 60]);
+ assert_eq!(st.dd_gpu.selected, 2);
+ }
}
diff --git a/src/pages/system_info.rs b/src/pages/system_info.rs
index a81dde0..ea91d89 100644
--- a/src/pages/system_info.rs
+++ b/src/pages/system_info.rs
@@ -1,6 +1,6 @@
use crate::app::{AppAction, PageContent, SectionContextExt};
-use cce_ui::layout::{render_widget, PageLayoutBuilder, LayoutStrategy};
-use cce_ui::widget::{Label, Dropdown, InfoBox, WidgetHost, Button};
+use cce_ui::layout::{PageLayoutBuilder, LayoutStrategy};
+use cce_ui::widget::{Label, WidgetHost, Button};
#[derive(Debug, Clone, Default)]
pub struct BatteryInfo {
@@ -42,10 +42,6 @@ pub struct SystemState {
// Power-related fields
pub battery: BatteryInfo,
pub on_ac: bool,
- pub cpu_powersave: bool,
- pub gpu_powersave: bool,
- pub cpu_gov_menu: cce_ui::widget::Adapted<Dropdown>,
- pub gpu_gov_menu: cce_ui::widget::Adapted<Dropdown>,
// Native layout tracking and widgets
pub initialized: bool,
@@ -59,8 +55,6 @@ pub struct SystemState {
pub battery_label_details: cce_ui::widget::Adapted<cce_ui::widget::Label>,
pub battery_label_ac: cce_ui::widget::Adapted<cce_ui::widget::Label>,
- pub cpu_info_box: cce_ui::widget::Adapted<cce_ui::widget::InfoBox>,
- pub gpu_info_box: cce_ui::widget::Adapted<cce_ui::widget::InfoBox>,
pub suspend_btn: cce_ui::widget::Adapted<cce_ui::widget::Button>,
pub hibernate_btn: cce_ui::widget::Adapted<cce_ui::widget::Button>,
@@ -101,16 +95,6 @@ impl Default for SystemState {
battery: BatteryInfo::default(),
on_ac: true,
- cpu_powersave: false,
- gpu_powersave: false,
- cpu_gov_menu: Dropdown::new(
- vec!["Performance".to_string(), "Powersave".to_string()],
- 0,
- ).with_label("CPU Governor"),
- gpu_gov_menu: Dropdown::new(
- vec!["Default (80W)".to_string(), "Eco Cap (5W)".to_string()],
- 0,
- ).with_label("GPU Power Limit"),
initialized: false,
sender: None,
@@ -123,8 +107,6 @@ impl Default for SystemState {
battery_label_details: Label::new("").with_font_size(11.0).with_color([135, 135, 153]),
battery_label_ac: Label::new("").with_font_size(14.0).with_color([212, 212, 212]),
- cpu_info_box: InfoBox::new("CPU Governor", vec![]),
- gpu_info_box: InfoBox::new("GPU Power Limit", vec![]),
suspend_btn: Button::new(0.0, 0.0, 0.0, 32.0)
.with_label("Suspend")
@@ -164,11 +146,6 @@ pub enum SystemMessage {
PowerOff,
ForceShutdown,
- // Moved variants
- SetCpuPerformance,
- SetCpuPowersave,
- SetGpuDefault,
- SetGpuPowersave,
}
// ── zbus proxies ────────────────────────────────────────────────────
@@ -217,33 +194,6 @@ fn format_duration(secs: i64) -> String {
if h > 0 { format!("{}h {}m", h, m) } else { format!("{}m", m) }
}
-fn spawn_cpu_power(powersave: bool) {
- let script = if powersave { "cpu-powersave-on" } else { "cpu-powersave-off" };
- let mut cmd = std::process::Command::new("pkexec");
- cmd.arg(cce_ui::config::data_home().join("cce-settings").join("helpers").join(script));
- let _ = cce_ui::process::spawn_detached(cmd);
-}
-
-fn spawn_gpu_power(powersave: bool) {
- let script = if powersave { "gpu-powersave-on" } else { "gpu-powersave-off" };
- let mut cmd = std::process::Command::new("pkexec");
- cmd.arg(cce_ui::config::data_home().join("cce-settings").join("helpers").join(script));
- let _ = cce_ui::process::spawn_detached(cmd);
-}
-
-fn current_cpu_governor() -> String {
- std::fs::read_to_string("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor")
- .unwrap_or_default().trim().to_string()
-}
-
-async fn current_gpu_power_cap() -> bool {
- tokio::process::Command::new("nvidia-smi")
- .args(["--query-gpu=power.limit", "--format=csv,noheader,nounits"])
- .output().await.ok()
- .and_then(|o| String::from_utf8_lossy(&o.stdout).trim().parse::<f32>().ok())
- .map(|w| w <= 10.0).unwrap_or(false)
-}
-
async fn fetch_upower() -> (BatteryInfo, bool) {
let conn = match zbus::Connection::system().await {
Ok(c) => c,
@@ -360,8 +310,6 @@ pub struct SystemInfo {
pub gpu_strings: Vec<String>,
pub battery: BatteryInfo,
pub on_ac: bool,
- pub cpu_powersave: bool,
- pub gpu_powersave: bool,
}
pub async fn fetch_system_state() -> SystemInfo {
@@ -454,8 +402,6 @@ pub async fn fetch_system_state() -> SystemInfo {
}).collect();
let (battery, on_ac) = fetch_upower().await;
- let cpu_powersave = current_cpu_governor() == "powersave";
- let gpu_powersave = current_gpu_power_cap().await;
SystemInfo {
hostname,
@@ -468,8 +414,6 @@ pub async fn fetch_system_state() -> SystemInfo {
gpu_strings,
battery,
on_ac,
- cpu_powersave,
- gpu_powersave,
}
}
@@ -483,14 +427,14 @@ const DANGER_BG: [f32; 4] = [0.67, 0.20, 0.20, 1.0];
const SAFE_BG: [f32; 4] = [0.20, 0.33, 0.22, 1.0];
const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
-pub fn view(state: &mut SystemState, cx: f32, cy: f32, cw: f32, ch: f32, _root_focused: bool, sec_focused: &[bool], layout: &mut dyn LayoutStrategy, ctx: &mut cce_ui::context::UiContext) -> PageContent {
+pub fn view(state: &mut SystemState, cx: f32, cy: f32, cw: f32, ch: f32, _root_focused: bool, sec_focused: &[bool], layout: &mut dyn LayoutStrategy, _ctx: &mut cce_ui::context::UiContext) -> PageContent {
let mut final_pc = PageContent::new();
let sec_w = 320.0f32;
// Seven, matching the add_section calls below and the seven groups
// section_widgets reports. The count caps the grid's column count
// (`n.min(cols)`), so the stale 8 only bit once the window was wide enough
// for eight columns — harmless, but it read as a missing eighth section.
- let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(7);
+ let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(5);
// ── 1. System Section ──
builder.add_section(&mut final_pc, "System", sec_focused.first().copied().unwrap_or(false), |sec| {
@@ -561,83 +505,11 @@ pub fn view(state: &mut SystemState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
});
// ── 5. CPU Governor Section ──
- builder.add_section(&mut final_pc, "CPU Governor", sec_focused.get(4).copied().unwrap_or(false), |sec_gov| {
- let rx = sec_gov.left;
- if !state.loaded {
- sec_gov.text("Loading CPU governor...", 12.0, 0.0, 12.0, TEXT_DIM);
- } else {
- sec_gov.widget(&mut state.cpu_gov_menu, 12.0, sec_gov.cw - 24.0, 26.0, ctx);
-
- let (info_title, info_lines) = if state.cpu_powersave {
- (
- "CPU Governor: Powersave",
- vec![
- "• Active: powersave".to_string(),
- "• Governor set to powersave — lower power, slower burst".to_string(),
- ],
- )
- } else {
- (
- "CPU Governor: Performance",
- vec![
- "• Active: performance".to_string(),
- "• Governor set to performance".to_string(),
- ],
- )
- };
-
- let mut info_box = InfoBox::new(info_title, info_lines);
- let info_h = 80.0;
- let info_y = sec_gov.ay();
- render_widget(sec_gov.pc, &mut info_box, rx + 12.0, info_y, sec_gov.cw - 24.0, info_h, ctx);
- // Advance the section cursor past the hand-placed box.
- sec_gov.content_y = sec_gov.content_y.max(info_y + info_h);
- for h in &mut sec_gov.grid.col_heights {
- *h = h.max(sec_gov.content_y);
- }
- }
- });
// ── 6. GPU Power Section ──
- builder.add_section(&mut final_pc, "GPU Power", sec_focused.get(5).copied().unwrap_or(false), |sec_gpow| {
- let rx = sec_gpow.left;
- if !state.loaded {
- sec_gpow.text("Loading GPU power status...", 12.0, 0.0, 12.0, TEXT_DIM);
- } else {
- sec_gpow.widget(&mut state.gpu_gov_menu, 12.0, sec_gpow.cw - 24.0, 26.0, ctx);
-
- let (info_title, info_lines) = if state.gpu_powersave {
- (
- "GPU Power Limit: Eco Cap",
- vec![
- "• Mode: 5W Cap".to_string(),
- "• NVIDIA power limit capped at 5W — minimal draw".to_string(),
- ],
- )
- } else {
- (
- "GPU Power Limit: Default",
- vec![
- "• Mode: 80W Default".to_string(),
- "• NVIDIA running at default power limit".to_string(),
- ],
- )
- };
- let mut info_box = InfoBox::new(info_title, info_lines);
- let info_h = 80.0;
- let info_y = sec_gpow.ay();
- render_widget(sec_gpow.pc, &mut info_box, rx + 12.0, info_y, sec_gpow.cw - 24.0, info_h, ctx);
- // Advance the section cursor past the hand-placed box.
- sec_gpow.content_y = sec_gpow.content_y.max(info_y + info_h);
- for h in &mut sec_gpow.grid.col_heights {
- *h = h.max(sec_gpow.content_y);
- }
- }
- });
-
- // ── 7. Battery Section ──
- builder.add_section(&mut final_pc, "Battery", sec_focused.get(6).copied().unwrap_or(false), |sec_bat| {
+ // ── 5. Battery Section ──
+ builder.add_section(&mut final_pc, "Battery", sec_focused.get(4).copied().unwrap_or(false), |sec_bat| {
if !state.loaded {
sec_bat.text("Loading battery status...", 12.0, 0.0, 12.0, TEXT_DIM);
} else {
@@ -697,10 +569,6 @@ pub fn update(state: &mut SystemState, msg: SystemMessage, ctx: &mut cce_ui::con
state.battery = new.battery;
state.on_ac = new.on_ac;
- state.cpu_powersave = new.cpu_powersave;
- state.gpu_powersave = new.gpu_powersave;
- state.cpu_gov_menu.selected = if new.cpu_powersave { 1 } else { 0 };
- state.gpu_gov_menu.selected = if new.gpu_powersave { 1 } else { 0 };
if state.loaded {
state.hostname_label.set_text(&format!("{} — Linux {}", state.hostname, state.kernel));
@@ -714,46 +582,6 @@ pub fn update(state: &mut SystemState, msg: SystemMessage, ctx: &mut cce_ui::con
state.cpu_usage_label.set_text(&cpu_usage_text);
state.cpu_temp_label.set_text(&cpu_temp_text);
- // Update info boxes
- let (cpu_title, cpu_lines) = if state.cpu_powersave {
- (
- "CPU Governor: Powersave",
- vec![
- "• Active: powersave".to_string(),
- "• Governor set to powersave — lower power, slower burst".to_string(),
- ],
- )
- } else {
- (
- "CPU Governor: Performance",
- vec![
- "• Active: performance".to_string(),
- "• Governor set to performance".to_string(),
- ],
- )
- };
- state.cpu_info_box.title = cpu_title.to_string();
- state.cpu_info_box.lines = cpu_lines;
-
- let (gpu_title, gpu_lines) = if state.gpu_powersave {
- (
- "GPU Power Limit: Eco Cap",
- vec![
- "• Mode: 5W Cap".to_string(),
- "• NVIDIA power limit capped at 5W — minimal draw".to_string(),
- ],
- )
- } else {
- (
- "GPU Power Limit: Default",
- vec![
- "• Mode: 80W Default".to_string(),
- "• NVIDIA running at default power limit".to_string(),
- ],
- )
- };
- state.gpu_info_box.title = gpu_title.to_string();
- state.gpu_info_box.lines = gpu_lines;
// Update battery labels
let bat = &state.battery;
@@ -797,54 +625,23 @@ pub fn update(state: &mut SystemState, msg: SystemMessage, ctx: &mut cce_ui::con
SystemMessage::PowerOff => spawn_systemctl("poweroff"),
SystemMessage::ForceShutdown => spawn_systemctl_force("poweroff"),
- SystemMessage::SetCpuPerformance => {
- state.cpu_powersave = false;
- state.cpu_gov_menu.selected = 0;
- spawn_cpu_power(false);
- }
- SystemMessage::SetCpuPowersave => {
- state.cpu_powersave = true;
- state.cpu_gov_menu.selected = 1;
- spawn_cpu_power(true);
- }
- SystemMessage::SetGpuDefault => {
- state.gpu_powersave = false;
- state.gpu_gov_menu.selected = 0;
- spawn_gpu_power(false);
- }
- SystemMessage::SetGpuPowersave => {
- state.gpu_powersave = true;
- state.gpu_gov_menu.selected = 1;
- spawn_gpu_power(true);
- }
}
}
impl crate::pages::AppPage for SystemState {
- // Sections: [System, System Actions, CPU, GPU, CPU Governor, GPU Power, Battery]
+ // Sections: [System, System Actions, CPU, GPU, Battery]
fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
vec![
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
- vec![self.cpu_gov_menu.id()],
- vec![self.gpu_gov_menu.id()],
Vec::new(),
]
}
- // The governor menus draw custom (no `render_widget` registration side effect);
- // the id-rooted router needs them resolvable.
- fn register_extra_dispatch_roots(&mut self, ctx: &mut cce_ui::context::UiContext) {
- let (id, ptr) = (self.cpu_gov_menu.id(), self.cpu_gov_menu.as_ptr_mut());
- ctx.register_widget(id, ptr);
- let (id, ptr) = (self.gpu_gov_menu.id(), self.gpu_gov_menu.as_ptr_mut());
- ctx.register_widget(id, ptr);
- }
-
fn view(
&mut self,
cx: f32,
@@ -864,21 +661,7 @@ impl crate::pages::AppPage for SystemState {
view(self, cx, cy, cw, ch, root_focused, sec_focused, layout, ctx)
}
- fn propagate_widget_changes(&mut self, actions: &mut Vec<crate::app::AppAction>) {
- if self.cpu_gov_menu.take_change() {
- if self.cpu_gov_menu.selected == 0 {
- actions.push(crate::app::AppAction::SystemInfo(SystemMessage::SetCpuPerformance));
- } else {
- actions.push(crate::app::AppAction::SystemInfo(SystemMessage::SetCpuPowersave));
- }
- }
- if self.gpu_gov_menu.take_change() {
- if self.gpu_gov_menu.selected == 0 {
- actions.push(crate::app::AppAction::SystemInfo(SystemMessage::SetGpuDefault));
- } else {
- actions.push(crate::app::AppAction::SystemInfo(SystemMessage::SetGpuPowersave));
- }
- }
+ fn propagate_widget_changes(&mut self, _actions: &mut Vec<crate::app::AppAction>) {
}
}