1use std::fmt;
2use std::fs;
3use std::sync::OnceLock;
4use std::sync::atomic::{AtomicUsize, Ordering};
5
6use zbus::Result;
7use zbus::blocking::Connection;
8use zbus::proxy;
9
10#[proxy(
11 interface = "net.hadess.PowerProfiles",
12 default_service = "net.hadess.PowerProfiles",
13 default_path = "/net/hadess/PowerProfiles"
14)]
15trait PowerProfiles {
16 #[zbus(property)]
17 fn active_profile(&self) -> Result<String>;
18}
19
20static POWER_PROFILES_PROXY: OnceLock<PowerProfilesProxyBlocking<'static>> = OnceLock::new();
21static RETRIES: AtomicUsize = AtomicUsize::new(0);
22const MAX_RETRIES: usize = 10;
23
24#[derive(Debug, Copy, Clone, Eq, PartialEq)]
25pub enum PowerProfile {
26 Powersave,
27 Balanced,
28 Performance,
29 Unknown,
30}
31
32impl fmt::Display for PowerProfile {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 match self {
35 PowerProfile::Powersave => "powersave",
36 PowerProfile::Balanced => "balanced",
37 PowerProfile::Performance => "performance",
38 PowerProfile::Unknown => "unknown",
39 }
40 .fmt(f)
41 }
42}
43
44fn read_energy_profile() -> PowerProfile {
45 let energy_pref_path = "/sys/devices/system/cpu/cpufreq/policy0/energy_performance_preference";
46 let scaling_governor_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_governor";
47
48 fs::read_to_string(energy_pref_path)
49 .ok()
50 .or_else(|| fs::read_to_string(scaling_governor_path).ok())
51 .map(|s| match s.trim_end() {
52 "power" | "balance_power" | "powersave" => PowerProfile::Powersave,
53 "balance_performance" => PowerProfile::Balanced,
54 "performance" => PowerProfile::Performance,
55 _ => PowerProfile::Unknown,
56 })
57 .unwrap_or(PowerProfile::Unknown)
58}
59
60pub fn fetch_power_profile(no_ppd: bool) -> PowerProfile {
61 fn parse_profile(profile: &str) -> PowerProfile {
62 match profile {
63 "power-saver" => PowerProfile::Powersave,
64 "balanced" => PowerProfile::Balanced,
65 "performance" => PowerProfile::Performance,
66 _ => PowerProfile::Unknown,
67 }
68 }
69
70 if no_ppd {
71 return read_energy_profile();
72 }
73 let proxy = POWER_PROFILES_PROXY.get();
74 if let Some(proxy) = proxy {
75 proxy.active_profile().map_or_else(
76 |e| {
77 log::debug!("failed to fetch the active power profile from ppd: {e}");
78 read_energy_profile()
79 },
80 |profile| parse_profile(&profile),
81 )
82 } else {
83 let retries = RETRIES.fetch_add(1, Ordering::Relaxed);
84 if retries < MAX_RETRIES {
85 let proxy = Connection::system().map(Box::new).map(Box::leak).map(|bus|
86 PowerProfilesProxyBlocking::new(bus).unwrap());
88 match proxy {
89 Ok(proxy) => match proxy.active_profile() {
90 Ok(profile) => {
91 let _ = POWER_PROFILES_PROXY.set(proxy);
92 parse_profile(&profile)
93 }
94 Err(e) => {
95 log::debug!("failed to communicate with ppd (retry {retries}): {e}");
96 read_energy_profile()
97 }
98 },
99 Err(e) => {
100 log::debug!("failed to communicate with dbus (retry {retries}): {e}");
101 read_energy_profile()
102 }
103 }
104 } else {
105 read_energy_profile()
106 }
107 }
108}