i3status_rs/blocks/battery/
upower.rs1use tokio::try_join;
2use zbus::fdo::{PropertiesChangedStream, PropertiesProxy};
3use zbus::{Connection, zvariant};
4use zvariant::ObjectPath;
5
6use super::{BatteryDevice, BatteryInfo, BatteryStatus, DeviceName};
7use crate::blocks::prelude::*;
8use crate::util::new_system_dbus_connection;
9
10const DISPLAY_DEVICE_PATH: ObjectPath =
11 ObjectPath::from_static_str_unchecked("/org/freedesktop/UPower/devices/DisplayDevice");
12
13struct DeviceConnection {
14 device_proxy: DeviceProxy<'static>,
15 changes: PropertiesChangedStream,
16}
17
18impl DeviceConnection {
19 async fn new(
20 dbus_conn: &Connection,
21 device: &DeviceName,
22 expected_model: Option<&str>,
23 ) -> Result<Option<Self>> {
24 let device_proxy =
25 if device.exact().is_none_or(|d| d == "DisplayDevice") && expected_model.is_none() {
26 DeviceProxy::builder(dbus_conn)
27 .path(DISPLAY_DEVICE_PATH)
28 .unwrap()
29 .build()
30 .await
31 .error("Failed to create DeviceProxy")?
32 } else {
33 let mut res = None;
34 for path in UPowerProxy::new(dbus_conn)
35 .await
36 .error("Failed to create UPowerProxy")?
37 .enumerate_devices()
38 .await
39 .error("Failed to retrieve UPower devices")?
40 {
41 let proxy = DeviceProxy::builder(dbus_conn)
42 .path(path)
43 .unwrap()
44 .build()
45 .await
46 .error("Failed to create DeviceProxy")?;
47
48 if let Some(expected_model) = &expected_model
50 && let Ok(device_model) = proxy.model().await
51 && !expected_model.eq(&device_model)
52 {
53 continue;
54 }
55 let device_type = proxy.type_().await.error("Failed to get device's type")?;
59 if device_type == 1 {
60 continue;
61 }
62 let name = proxy
63 .native_path()
64 .await
65 .error("Failed to get device's native path")?;
66 if device.matches(&name) {
67 res = Some(proxy);
68 break;
69 }
70 }
71 match res {
72 Some(res) => res,
73 None => return Ok(None),
74 }
75 };
76
77 let changes = PropertiesProxy::builder(dbus_conn)
78 .destination("org.freedesktop.UPower")
79 .unwrap()
80 .path(device_proxy.inner().path().to_owned())
81 .unwrap()
82 .build()
83 .await
84 .error("Failed to create PropertiesProxy")?
85 .receive_properties_changed()
86 .await
87 .error("Failed to create PropertiesChangedStream")?;
88
89 Ok(Some(DeviceConnection {
90 device_proxy,
91 changes,
92 }))
93 }
94}
95
96pub(super) struct Device {
97 dbus_conn: Connection,
98 device: DeviceName,
99 dev_model: Option<String>,
100 device_conn: Option<DeviceConnection>,
101 device_added_stream: DeviceAddedStream,
102 device_removed_stream: DeviceRemovedStream,
103}
104
105impl Device {
106 pub(super) async fn new(device: DeviceName, dev_model: Option<String>) -> Result<Self> {
107 let dbus_conn = new_system_dbus_connection().await?;
108
109 let device_conn = DeviceConnection::new(&dbus_conn, &device, dev_model.as_deref()).await?;
110
111 let upower_proxy = UPowerProxy::new(&dbus_conn)
112 .await
113 .error("Could not create UPowerProxy")?;
114
115 let (device_added_stream, device_removed_stream) = try_join! {
116 upower_proxy.receive_device_added(),
117 upower_proxy.receive_device_removed()
118 }
119 .error("Could not create signal stream")?;
120
121 Ok(Self {
122 dbus_conn,
123 device,
124 dev_model,
125 device_conn,
126 device_added_stream,
127 device_removed_stream,
128 })
129 }
130}
131
132#[async_trait]
133impl BatteryDevice for Device {
134 async fn get_info(&mut self) -> Result<Option<BatteryInfo>> {
135 match &self.device_conn {
136 None => Ok(None),
137 Some(device_conn) => {
138 match try_join! {
139 device_conn.device_proxy.percentage(),
140 device_conn.device_proxy.energy_rate(),
141 device_conn.device_proxy.state(),
142 device_conn.device_proxy.time_to_full(),
143 device_conn.device_proxy.time_to_empty(),
144 } {
145 Err(_) => Ok(None),
146 Ok((capacity, power, state, time_to_full, time_to_empty)) => {
147 let status = match state {
148 1 => BatteryStatus::Charging,
149 2 | 6 => BatteryStatus::Discharging,
150 3 => BatteryStatus::Empty,
151 4 => BatteryStatus::Full,
152 5 => BatteryStatus::NotCharging,
153 _ => BatteryStatus::Unknown,
154 };
155
156 let time_remaining = match status {
157 BatteryStatus::Charging => Some(time_to_full as f64),
158 BatteryStatus::Discharging => Some(time_to_empty as f64),
159 _ => None,
160 };
161
162 Ok(Some(BatteryInfo {
163 status,
164 capacity,
165 power: Some(power),
166 time_remaining,
167 }))
168 }
169 }
170 }
171 }
172 }
173
174 async fn wait_for_change(&mut self) -> Result<()> {
175 match &mut self.device_conn {
176 Some(device_conn) => loop {
177 select! {
178 _ = self.device_added_stream.next() => {},
179 _ = device_conn.changes.next() => {
180 break;
181 },
182 Some(msg) = self.device_removed_stream.next() => {
183 let args = msg.args().unwrap();
184 if args.device().as_ref() == device_conn.device_proxy.inner().path().as_ref() {
185 self.device_conn = None;
186 break;
187 }
188 },
189 }
190 },
191 None => loop {
192 select! {
193 _ = self.device_removed_stream.next() => {},
194 _ = self.device_added_stream.next() => {
195 if let Some(device_conn) =
196 DeviceConnection::new(&self.dbus_conn, &self.device, self.dev_model.as_deref()).await?
197 {
198 self.device_conn = Some(device_conn);
199 break;
200 }
201 },
202 }
203 },
204 }
205
206 Ok(())
207 }
208}
209
210#[zbus::proxy(
211 interface = "org.freedesktop.UPower.Device",
212 default_service = "org.freedesktop.UPower"
213)]
214trait Device {
215 #[zbus(property)]
216 fn energy_rate(&self) -> zbus::Result<f64>;
217
218 #[zbus(property)]
219 fn is_present(&self) -> zbus::Result<bool>;
220
221 #[zbus(property)]
222 fn native_path(&self) -> zbus::Result<String>;
223
224 #[zbus(property)]
225 fn model(&self) -> zbus::Result<String>;
226
227 #[zbus(property)]
228 fn online(&self) -> zbus::Result<bool>;
229
230 #[zbus(property)]
231 fn percentage(&self) -> zbus::Result<f64>;
232
233 #[zbus(property)]
234 fn state(&self) -> zbus::Result<u32>;
235
236 #[zbus(property)]
237 fn time_to_empty(&self) -> zbus::Result<i64>;
238
239 #[zbus(property)]
240 fn time_to_full(&self) -> zbus::Result<i64>;
241
242 #[zbus(property, name = "Type")]
243 fn type_(&self) -> zbus::Result<u32>;
244}
245
246#[zbus::proxy(
247 interface = "org.freedesktop.UPower",
248 default_service = "org.freedesktop.UPower",
249 default_path = "/org/freedesktop/UPower"
250)]
251trait UPower {
252 fn enumerate_devices(&self) -> zbus::Result<Vec<zvariant::OwnedObjectPath>>;
253
254 fn get_display_device(&self) -> zbus::Result<zvariant::OwnedObjectPath>;
255
256 #[zbus(signal)]
257 fn device_added(&self, device: zvariant::OwnedObjectPath) -> zbus::Result<()>;
258
259 #[zbus(signal)]
260 fn device_removed(&self, device: zvariant::OwnedObjectPath) -> zbus::Result<()>;
261
262 #[zbus(property)]
263 fn on_battery(&self) -> zbus::Result<bool>;
264}