1use std::fmt;
2use std::fs;
3use std::sync::atomic::{AtomicUsize, Ordering};
4use std::sync::OnceLock;
5
6use zbus::blocking::Connection;
7use zbus::proxy;
8use zbus::Result;
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 { power: bool },
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 { power: true } => "balanced_power",
37 PowerProfile::Balanced { power: false } => "balanced_performance",
38 PowerProfile::Performance => "performance",
39 PowerProfile::Unknown => "unknown",
40 }
41 .fmt(f)
42 }
43}
44
45fn read_energy_profile() -> PowerProfile {
46 let energy_pref_path = "/sys/devices/system/cpu/cpufreq/policy0/energy_performance_preference";
47 let scaling_governor_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_governor";
48
49 fs::read_to_string(energy_pref_path)
50 .ok()
51 .or_else(|| fs::read_to_string(scaling_governor_path).ok())
52 .map(|s| match s.trim_end() {
53 "power" | "powersave" => PowerProfile::Powersave,
54 "balance_power" => PowerProfile::Balanced { power: true },
55 "balance_performance" => PowerProfile::Balanced { power: false },
56 "performance" => PowerProfile::Performance,
57 _ => PowerProfile::Unknown,
58 })
59 .unwrap_or(PowerProfile::Unknown)
60}
61
62pub fn fetch_power_profile(no_ppd: bool) -> PowerProfile {
63 fn parse_profile(profile: &str) -> PowerProfile {
64 match profile {
65 "power-saver" => PowerProfile::Powersave,
66 "balanced" => PowerProfile::Balanced { power: false },
67 "performance" => PowerProfile::Performance,
68 _ => PowerProfile::Unknown,
69 }
70 }
71
72 if no_ppd {
73 return read_energy_profile();
74 }
75 let proxy = POWER_PROFILES_PROXY.get();
76 if let Some(proxy) = proxy {
77 proxy.active_profile().map_or_else(
78 |e| {
79 log::debug!("failed to fetch the active power profile from ppd: {e}");
80 read_energy_profile()
81 },
82 |profile| parse_profile(&profile),
83 )
84 } else {
85 let retries = RETRIES.fetch_add(1, Ordering::Relaxed);
86 if retries < MAX_RETRIES {
87 let proxy = Connection::system().map(Box::new).map(Box::leak).map(|bus|
88 PowerProfilesProxyBlocking::new(bus).unwrap());
90 match proxy {
91 Ok(proxy) => match proxy.active_profile() {
92 Ok(profile) => {
93 let _ = POWER_PROFILES_PROXY.set(proxy);
94 parse_profile(&profile)
95 }
96 Err(e) => {
97 log::debug!("failed to communicate with ppd (retry {retries}): {e}");
98 read_energy_profile()
99 }
100 },
101 Err(e) => {
102 log::debug!("failed to communicate with dbus (retry {retries}): {e}");
103 read_energy_profile()
104 }
105 }
106 } else {
107 read_energy_profile()
108 }
109 }
110}