Skip to main content

auto_cpufreq/
power_helper.rs

1// src/power_helper.rs
2
3use crate::core::CODEBERG;
4use crate::tlp_stat_parser::TLPStatusParser;
5use anyhow::{Context, Result};
6use std::fs;
7use std::path::Path;
8use std::process::{Command, Stdio};
9
10pub fn does_command_exist(cmd: &str) -> bool {
11    Command::new("which")
12        .arg(cmd)
13        .stdout(Stdio::null())
14        .stderr(Stdio::null())
15        .status()
16        .map(|status| status.success())
17        .unwrap_or(false)
18}
19
20lazy_static::lazy_static! {
21    pub static ref BLUETOOTHCTL_EXISTS: bool = does_command_exist("bluetoothctl");
22    pub static ref POWERPROFILESCTL_EXISTS: bool = does_command_exist("powerprofilesctl");
23    pub static ref SYSTEMCTL_EXISTS: bool = does_command_exist("systemctl");
24    pub static ref TLP_STAT_EXISTS: bool = does_command_exist("tlp-stat");
25    pub static ref TUNED_STAT_EXISTS: bool = does_command_exist("tuned");
26}
27
28pub fn header() {
29    println!("\n------------------------- auto-cpufreq: Power helper -------------------------\n");
30}
31
32pub fn warning() {
33    println!("\n----------------------------------- Warning -----------------------------------\n");
34}
35
36pub fn footer() {
37    println!("\n{}\n", "-".repeat(79));
38}
39
40pub fn gnome_power_status() -> Result<bool> {
41    if !*SYSTEMCTL_EXISTS {
42        return Ok(false);
43    }
44
45    let status = Command::new("systemctl")
46        .args(["is-active", "--quiet", "power-profiles-daemon"])
47        .status()
48        .context("Failed to check GNOME power profiles daemon status")?;
49
50    Ok(status.success())
51}
52
53pub fn tlp_service_detect() -> Result<()> {
54    if !*TLP_STAT_EXISTS {
55        return Ok(());
56    }
57
58    let output = Command::new("tlp-stat")
59        .arg("-s")
60        .output()
61        .context("Failed to run tlp-stat")?;
62
63    let status_output = String::from_utf8_lossy(&output.stdout);
64    let tlp_status = TLPStatusParser::new(&status_output);
65
66    if tlp_status.is_enabled() {
67        warning();
68        println!("Detected you are running a TLP service!");
69        println!(
70            "This daemon might interfere with auto-cpufreq which can lead to unexpected results."
71        );
72        println!(
73            "We strongly encourage you to remove TLP unless you really know what you are doing."
74        );
75    }
76
77    Ok(())
78}
79
80pub fn gnome_power_detect() -> Result<()> {
81    if !*SYSTEMCTL_EXISTS {
82        return Ok(());
83    }
84
85    if gnome_power_status()? {
86        warning();
87        println!("Detected running GNOME Power Profiles daemon service!");
88        println!("\nThis daemon might interfere with auto-cpufreq and will be automatically");
89        println!("disabled when auto-cpufreq daemon is installed and");
90        println!("it will be re-enabled after auto-cpufreq is removed.");
91        println!("Steps to perform this action using auto-cpufreq: power_helper script:");
92        println!("git clone {}.git", CODEBERG);
93        println!("python3 -m auto_cpufreq.power_helper --gnome_power_disable");
94        println!("\nReference: {}#configuring-auto-cpufreq", CODEBERG);
95    }
96
97    Ok(())
98}
99
100pub fn gnome_power_detect_install() -> Result<()> {
101    if !*SYSTEMCTL_EXISTS {
102        return Ok(());
103    }
104
105    if gnome_power_status()? {
106        warning();
107        println!("Detected running GNOME Power Profiles daemon service!");
108        println!("\nThis daemon might interfere with auto-cpufreq and has been disabled.\n");
109        println!("This daemon is not automatically disabled in \"monitor\" mode and");
110        println!("will be enabled after auto-cpufreq daemon is removed.");
111    }
112
113    Ok(())
114}
115
116pub fn gnome_power_stop_live() -> Result<()> {
117    if !*SYSTEMCTL_EXISTS {
118        return Ok(());
119    }
120
121    if gnome_power_status()? && *POWERPROFILESCTL_EXISTS {
122        Command::new("powerprofilesctl")
123            .args(["set", "balanced"])
124            .status()?;
125
126        Command::new("systemctl")
127            .args(["stop", "power-profiles-daemon"])
128            .status()?;
129    }
130
131    Ok(())
132}
133
134pub fn tuned_stop_live() -> Result<()> {
135    if *SYSTEMCTL_EXISTS && *TUNED_STAT_EXISTS {
136        Command::new("systemctl").args(["stop", "tuned"]).status()?;
137    }
138
139    Ok(())
140}
141
142pub fn gnome_power_start_live() -> Result<()> {
143    if *SYSTEMCTL_EXISTS {
144        Command::new("systemctl")
145            .args(["start", "power-profiles-daemon"])
146            .status()?;
147    }
148
149    Ok(())
150}
151
152pub fn tuned_start_live() -> Result<()> {
153    if *SYSTEMCTL_EXISTS && *TUNED_STAT_EXISTS {
154        Command::new("systemctl")
155            .args(["start", "tuned"])
156            .status()?;
157    }
158
159    Ok(())
160}
161
162pub fn gnome_power_svc_enable() -> Result<()> {
163    if !*SYSTEMCTL_EXISTS {
164        return Ok(());
165    }
166
167    println!("* Enabling GNOME power profiles\n");
168
169    Command::new("systemctl")
170        .args(["unmask", "power-profiles-daemon"])
171        .status()
172        .context("Failed to unmask power-profiles-daemon")?;
173
174    Command::new("systemctl")
175        .args(["enable", "--now", "power-profiles-daemon"])
176        .status()
177        .context("Failed to enable power-profiles-daemon")?;
178
179    Ok(())
180}
181
182pub fn tuned_svc_enable() -> Result<()> {
183    if !*SYSTEMCTL_EXISTS || !*TUNED_STAT_EXISTS {
184        return Ok(());
185    }
186
187    println!("* Enabling TuneD\n");
188
189    Command::new("systemctl")
190        .args(["unmask", "tuned"])
191        .status()
192        .context("Failed to unmask tuned")?;
193
194    Command::new("systemctl")
195        .args(["enable", "--now", "tuned"])
196        .status()
197        .context("Failed to enable tuned")?;
198
199    Ok(())
200}
201
202pub fn gnome_power_svc_status() -> Result<()> {
203    if !*SYSTEMCTL_EXISTS {
204        return Ok(());
205    }
206
207    println!("* GNOME power profiles status");
208    Command::new("systemctl")
209        .args(["status", "power-profiles-daemon"])
210        .status()
211        .context("Failed to get GNOME power profiles status")?;
212
213    Ok(())
214}
215
216pub fn set_bluetooth_auto_enable(value: bool) -> Result<bool> {
217    let btconf = Path::new("/etc/bluetooth/main.conf");
218    let setting = format!("AutoEnable={}", if value { "true" } else { "false" });
219
220    let content = fs::read_to_string(btconf).context("Failed to read bluetooth config")?;
221
222    let lines: Vec<&str> = content.lines().collect();
223    let mut new_lines = Vec::new();
224    let mut in_policy_section = false;
225    let mut found_and_set = false;
226
227    for line in lines {
228        let stripped = line.trim();
229
230        if stripped.starts_with('[') {
231            if in_policy_section && !found_and_set {
232                new_lines.push(setting.clone());
233                found_and_set = true;
234            }
235            in_policy_section = stripped.to_lowercase() == "[policy]";
236            new_lines.push(line.to_string());
237            continue;
238        }
239
240        if in_policy_section {
241            if !stripped.starts_with('#') && stripped.starts_with("AutoEnable=") {
242                new_lines.push(setting.clone());
243                found_and_set = true;
244                continue;
245            }
246            if stripped.starts_with('#') {
247                let uncommented = stripped.trim_start_matches('#').trim();
248                if uncommented.starts_with("AutoEnable=") {
249                    new_lines.push(setting.clone());
250                    found_and_set = true;
251                    continue;
252                }
253            }
254        }
255
256        new_lines.push(line.to_string());
257    }
258
259    if in_policy_section && !found_and_set {
260        new_lines.push(setting.clone());
261        found_and_set = true;
262    }
263
264    if !found_and_set {
265        new_lines.push(String::new());
266        new_lines.push("[Policy]".to_string());
267        new_lines.push(setting);
268    }
269
270    fs::write(btconf, new_lines.join("\n")).context("Failed to write bluetooth config")?;
271
272    Ok(true)
273}
274
275pub fn bluetooth_disable() -> Result<()> {
276    if !*BLUETOOTHCTL_EXISTS {
277        println!("* Turn off bluetooth on boot [skipping] (package providing bluetooth access is not present)");
278        return Ok(());
279    }
280
281    println!("* Turn off Bluetooth on boot (only)!");
282    println!("  If you want bluetooth enabled on boot run: auto-cpufreq --bluetooth_boot_on");
283
284    if !set_bluetooth_auto_enable(false)? {
285        println!("\nERROR:\nWas unable to turn off bluetooth on boot");
286    }
287
288    Ok(())
289}
290
291pub fn bluetooth_enable() -> Result<()> {
292    if !*BLUETOOTHCTL_EXISTS {
293        println!("* Turn on bluetooth on boot [skipping] (package providing bluetooth access is not present)");
294        return Ok(());
295    }
296
297    println!("* Turn on bluetooth on boot");
298
299    if !set_bluetooth_auto_enable(true)? {
300        println!("\nERROR:\nWas unable to turn on bluetooth on boot");
301    }
302
303    Ok(())
304}
305
306pub fn gnome_power_rm_reminder() -> Result<()> {
307    if !*SYSTEMCTL_EXISTS {
308        return Ok(());
309    }
310
311    if !gnome_power_status()? {
312        warning();
313        println!("Detected GNOME Power Profiles daemon service is stopped!");
314        println!("This service will now be enabled and started again.\n");
315    }
316
317    Ok(())
318}