1use serde::Serialize;
2
3#[derive(Debug, Clone, Serialize)]
4pub struct Power {
5 pub source: Source,
6 pub battery_percent: Option<u8>,
7 pub low_power_mode: bool,
8 pub energy_mode: EnergyMode,
9}
10
11#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
12#[serde(rename_all = "lowercase")]
13pub enum Source {
14 Ac,
15 Battery,
16}
17
18#[derive(Debug, Clone, Copy, Serialize)]
19#[serde(rename_all = "lowercase")]
20pub enum EnergyMode {
21 Automatic,
22 Low,
23 High,
24 Unknown,
26}
27
28#[cfg(target_os = "macos")]
29impl Power {
30 pub fn collect() -> Self {
31 let (source, battery_percent) = read_power_source();
32 let low_power_mode = macstate_sys::objc::is_low_power_mode_enabled();
33 let energy_mode = read_energy_mode(source);
34 Self {
35 source,
36 battery_percent,
37 low_power_mode,
38 energy_mode,
39 }
40 }
41}
42
43#[cfg(not(target_os = "macos"))]
44impl Power {
45 pub fn collect() -> Self {
46 Self {
47 source: Source::Ac,
48 battery_percent: None,
49 low_power_mode: false,
50 energy_mode: EnergyMode::Unknown,
51 }
52 }
53}
54
55#[cfg(target_os = "macos")]
56fn read_power_source() -> (Source, Option<u8>) {
57 use macstate_sys::cf::{
58 cfstring_to_string, dict_get_i32, CFArrayGetCount, CFArrayGetValueAtIndex, CFOwned,
59 };
60 use macstate_sys::iokit::{
61 kIOPSACPowerValue, kIOPSCurrentCapacityKey, kIOPSMaxCapacityKey,
62 IOPSCopyPowerSourcesInfo, IOPSCopyPowerSourcesList, IOPSGetPowerSourceDescription,
63 IOPSGetProvidingPowerSourceType,
64 };
65
66 unsafe {
67 let snapshot = match CFOwned::from_create(IOPSCopyPowerSourcesInfo()) {
68 Some(s) => s,
69 None => return (Source::Ac, None),
70 };
71
72 let provider = IOPSGetProvidingPowerSourceType(snapshot.as_ptr());
73 let source = match cfstring_to_string(provider) {
74 Some(s) if s == kIOPSACPowerValue => Source::Ac,
75 Some(_) => Source::Battery,
76 None => Source::Ac,
77 };
78
79 let list = match CFOwned::from_create(IOPSCopyPowerSourcesList(snapshot.as_ptr())) {
80 Some(l) => l,
81 None => return (source, None),
82 };
83
84 let count = CFArrayGetCount(list.as_ptr());
85 let mut percent: Option<u8> = None;
86 for i in 0..count {
87 let ps = CFArrayGetValueAtIndex(list.as_ptr(), i);
88 if ps.is_null() {
89 continue;
90 }
91 let desc = IOPSGetPowerSourceDescription(snapshot.as_ptr(), ps);
92 if desc.is_null() {
93 continue;
94 }
95 let cur = dict_get_i32(desc, kIOPSCurrentCapacityKey);
96 let max = dict_get_i32(desc, kIOPSMaxCapacityKey);
97 if let (Some(cur), Some(max)) = (cur, max) {
98 if max > 0 {
99 let pct = ((cur as f64 / max as f64) * 100.0).round();
100 percent = Some(pct.clamp(0.0, 100.0) as u8);
101 break;
102 }
103 }
104 }
105
106 (source, percent)
107 }
108}
109
110#[cfg(target_os = "macos")]
111fn read_energy_mode(source: Source) -> EnergyMode {
112 use macstate_sys::cf::{dict_get_dict, dict_get_i32, CFOwned};
113 use macstate_sys::iokit::{
114 kIOPMLowPowerModeKey, kIOPSACPowerValue, kIOPSBatteryPowerValue,
115 IOPMCopyActivePMPreferences,
116 };
117
118 unsafe {
119 let prefs = match CFOwned::from_create(IOPMCopyActivePMPreferences()) {
120 Some(p) => p,
121 None => return EnergyMode::Unknown,
122 };
123 let key = match source {
124 Source::Ac => kIOPSACPowerValue,
125 Source::Battery => kIOPSBatteryPowerValue,
126 };
127 let sub = dict_get_dict(prefs.as_ptr(), key);
128 if sub.is_null() {
129 return EnergyMode::Unknown;
130 }
131 match dict_get_i32(sub, kIOPMLowPowerModeKey) {
135 Some(0) => EnergyMode::Automatic,
136 Some(1) => EnergyMode::Low,
137 Some(2) => EnergyMode::High,
138 _ => EnergyMode::Unknown,
139 }
140 }
141}