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

src/bin/cce-power-apply.rs (6.1K)

  1 //! cce-power-apply — the root side of the Power page's modes and their
  2 //! adapter-state assignment.
  3 //!
  4 //! The System Interface's Power page keeps a named set of levers per power
  5 //! mode plus a table saying which mode runs plugged in and which on battery
  6 //! (`/etc/cce/power.kdl`, see `cce_settings::power_plan`). Something has to
  7 //! write those levers into sysfs as root whenever the charger comes or goes,
  8 //! with no session and no prompt — that is this binary:
  9 //!
 10 //! - `apply [ac|battery]` — apply the mode assigned to the current (or the
 11 //!   named) adapter state. Run by `cce-power-apply.service`, which udev
 12 //!   starts when the Mains supply appears at boot or flips online/offline
 13 //!   (`udev/90-cce-power-apply.rules`). Per-lever failures are logged and do
 14 //!   not fail the run: a missing NVIDIA driver must not hide the CPU profile
 15 //!   that did land.
 16 //! - `apply-mode <mode>` — apply one mode by name, whatever is plugged in.
 17 //! - `set <mode> <lever> <value|unset>` — record one lever on a mode and,
 18 //!   when that mode is the one running, apply it now. Run by the Power page
 19 //!   under pkexec, the app's standard privileged path.
 20 //! - `assign <ac|battery> <mode>` — point an adapter state at a mode and,
 21 //!   when that state is the live one, apply the mode now.
 22 //! - `show` — print the plan and the live adapter state.
 23 //!
 24 //! Installed to `/usr/bin` by `ccebuild install-system` (the udev rule and
 25 //! the unit name that path); `ccebuild install` also drops a copy in
 26 //! `~/.local/bin`, which the page falls back to under pkexec before the root
 27 //! side is installed.
 28 
 29 use cce_settings::power_plan::{
 30     apply_lever, apply_mode, current_source, Lever, Mode, PowerPlan, Source, PLAN_PATH,
 31 };
 32 
 33 fn usage() -> ! {
 34     eprintln!(
 35         "usage: cce-power-apply apply [ac|battery]\n       \
 36                 cce-power-apply apply-mode <mode>\n       \
 37                 cce-power-apply set <mode> <lever> <value|unset>\n       \
 38                 cce-power-apply assign <ac|battery> <mode>\n       \
 39                 cce-power-apply show\n\
 40          modes:  {}\n\
 41          levers: {}",
 42         Mode::ALL.iter().map(|m| m.key()).collect::<Vec<_>>().join(" "),
 43         Lever::ALL.iter().map(|l| l.key()).collect::<Vec<_>>().join(" ")
 44     );
 45     std::process::exit(2)
 46 }
 47 
 48 fn load_plan() -> Result<PowerPlan, i32> {
 49     PowerPlan::load().map_err(|e| {
 50         eprintln!("cce-power-apply: {}", e);
 51         1
 52     })
 53 }
 54 
 55 /// Apply one mode and report each lever. `what` names what asked for it —
 56 /// the adapter state, or the mode itself — so the journal says why.
 57 fn run_mode(plan: &PowerPlan, mode: Mode, what: &str) -> i32 {
 58     let results = apply_mode(plan, mode);
 59     if results.is_empty() {
 60         println!("cce-power-apply: {} runs {}, which sets nothing", what, mode.key());
 61         return 0;
 62     }
 63     for (lever, result) in &results {
 64         match result {
 65             Ok(()) => println!("{} [{}]: {} = {}", what, mode.key(), lever.key(), plan.get(mode, *lever).unwrap_or("")),
 66             Err(e) => eprintln!("cce-power-apply: {} [{}] {}: {}", what, mode.key(), lever.key(), e),
 67         }
 68     }
 69     0
 70 }
 71 
 72 fn cmd_apply(forced: Option<&str>) -> i32 {
 73     let source = match forced {
 74         None => current_source(),
 75         Some(s) => Source::parse(s).unwrap_or_else(|| usage()),
 76     };
 77     let plan = match load_plan() {
 78         Ok(p) => p,
 79         Err(code) => return code,
 80     };
 81     run_mode(&plan, plan.assigned(source), source.key())
 82 }
 83 
 84 fn cmd_apply_mode(rest: &[String]) -> i32 {
 85     let [mode] = rest else { usage() };
 86     let mode = Mode::parse(mode).unwrap_or_else(|| usage());
 87     let plan = match load_plan() {
 88         Ok(p) => p,
 89         Err(code) => return code,
 90     };
 91     run_mode(&plan, mode, "mode")
 92 }
 93 
 94 fn cmd_set(rest: &[String]) -> i32 {
 95     let [mode, lever, value] = rest else { usage() };
 96     let mode = Mode::parse(mode).unwrap_or_else(|| usage());
 97     let lever = Lever::parse(lever).unwrap_or_else(|| usage());
 98     let value: Option<&str> = if value == "unset" { None } else { Some(value.as_str()) };
 99     let mut plan = match load_plan() {
100         Ok(p) => p,
101         Err(code) => return code,
102     };
103     if let Err(e) = plan.put(mode, lever, value) {
104         eprintln!("cce-power-apply: {}", e);
105         return 2;
106     }
107     if let Err(e) = plan.save() {
108         eprintln!("cce-power-apply: writing {}: {}", PLAN_PATH, e);
109         return 1;
110     }
111     // Only the mode the machine is running right now touches sysfs; editing
112     // any other mode is a plan change and nothing more.
113     if mode == plan.assigned(current_source()) {
114         if let Some(v) = value {
115             if let Err(e) = apply_lever(lever, v) {
116                 eprintln!("cce-power-apply: {} {}: {}", mode.key(), lever.key(), e);
117                 return 1;
118             }
119         }
120     }
121     0
122 }
123 
124 fn cmd_assign(rest: &[String]) -> i32 {
125     let [source, mode] = rest else { usage() };
126     let source = Source::parse(source).unwrap_or_else(|| usage());
127     let mode = Mode::parse(mode).unwrap_or_else(|| usage());
128     let mut plan = match load_plan() {
129         Ok(p) => p,
130         Err(code) => return code,
131     };
132     plan.assign(source, mode);
133     if let Err(e) = plan.save() {
134         eprintln!("cce-power-apply: writing {}: {}", PLAN_PATH, e);
135         return 1;
136     }
137     // Reassigning the live adapter state hands the machine to another mode
138     // now, not at the next unplug.
139     if source == current_source() {
140         return run_mode(&plan, mode, source.key());
141     }
142     0
143 }
144 
145 fn cmd_show() -> i32 {
146     match load_plan() {
147         Ok(plan) => {
148             print!("{}", plan.to_kdl());
149             let source = current_source();
150             println!("// live: {} running {}", source.key(), plan.assigned(source).key());
151             0
152         }
153         Err(code) => code,
154     }
155 }
156 
157 fn main() {
158     let args: Vec<String> = std::env::args().skip(1).collect();
159     let code = match args.first().map(String::as_str) {
160         Some("apply") => cmd_apply(args.get(1).map(String::as_str)),
161         Some("apply-mode") => cmd_apply_mode(&args[1..]),
162         Some("set") => cmd_set(&args[1..]),
163         Some("assign") => cmd_assign(&args[1..]),
164         Some("show") => cmd_show(),
165         _ => usage(),
166     };
167     std::process::exit(code);
168 }