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 }