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