git.lucas.co / cce-system-interface
system settings
git clone https://git.lucas.co/cce-system-interface.git

commit79ceac6ccc0788e857a1b2baed6a6d130f38a026
parent9936eb8d08
authorLucas Galante <[email protected]>
date2026-08-23 11:00
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>) {
     }
 }