1use anyhow::{Context, Result};
6use tracing::debug;
7use zbus::{proxy, Connection};
8
9#[derive(Debug, Clone, Copy, Default)]
11pub enum BusType {
12 Session,
14 System,
16 #[default]
18 Auto,
19}
20
21#[proxy(
23 interface = "org.ht32panel.Daemon1",
24 default_service = "org.ht32panel.Daemon",
25 default_path = "/org/ht32panel/Daemon"
26)]
27trait Daemon1 {
28 fn set_orientation(&self, orientation: &str) -> zbus::Result<()>;
30
31 fn get_orientation(&self) -> zbus::Result<String>;
33
34 fn clear_display(&self, color: &str) -> zbus::Result<()>;
36
37 fn set_face(&self, face: &str) -> zbus::Result<()>;
39
40 fn get_face(&self) -> zbus::Result<String>;
42
43 fn set_led(&self, theme: u8, intensity: u8, speed: u8) -> zbus::Result<()>;
45
46 fn led_off(&self) -> zbus::Result<()>;
48
49 fn get_led_settings(&self) -> zbus::Result<(u8, u8, u8)>;
51
52 fn get_theme(&self) -> zbus::Result<String>;
54
55 fn set_theme(&self, name: &str) -> zbus::Result<()>;
57
58 fn list_themes(&self) -> zbus::Result<Vec<String>>;
60
61 fn list_themes_detailed(&self) -> zbus::Result<Vec<String>>;
63
64 fn list_face_ids(&self) -> zbus::Result<Vec<String>>;
66
67 fn list_faces(&self) -> zbus::Result<Vec<String>>;
69
70 fn list_network_interfaces(&self) -> zbus::Result<Vec<String>>;
72
73 fn list_complications(&self) -> zbus::Result<Vec<(String, String, String, bool)>>;
76
77 fn list_complications_detailed(&self) -> zbus::Result<Vec<String>>;
80
81 fn get_enabled_complications(&self) -> zbus::Result<Vec<String>>;
83
84 fn enable_complication(&self, complication_id: &str) -> zbus::Result<()>;
86
87 fn disable_complication(&self, complication_id: &str) -> zbus::Result<()>;
89
90 fn get_complication_option(
92 &self,
93 complication_id: &str,
94 option_id: &str,
95 ) -> zbus::Result<String>;
96
97 fn set_complication_option(
99 &self,
100 complication_id: &str,
101 option_id: &str,
102 value: &str,
103 ) -> zbus::Result<()>;
104
105 fn get_screen_png(&self) -> zbus::Result<Vec<u8>>;
107
108 fn quit(&self) -> zbus::Result<()>;
110
111 #[zbus(property)]
113 fn connected(&self) -> zbus::Result<bool>;
114
115 #[zbus(property)]
117 fn web_enabled(&self) -> zbus::Result<bool>;
118
119 #[zbus(property)]
121 fn orientation(&self) -> zbus::Result<String>;
122
123 #[zbus(property)]
125 fn led_theme(&self) -> zbus::Result<u8>;
126
127 #[zbus(property)]
129 fn led_intensity(&self) -> zbus::Result<u8>;
130
131 #[zbus(property)]
133 fn led_speed(&self) -> zbus::Result<u8>;
134
135 #[zbus(property)]
137 fn face(&self) -> zbus::Result<String>;
138}
139
140pub struct DaemonClient {
142 proxy: Daemon1Proxy<'static>,
143}
144
145impl DaemonClient {
146 pub async fn connect() -> Result<Self> {
150 Self::connect_with_bus(BusType::Auto).await
151 }
152
153 pub async fn connect_with_bus(bus_type: BusType) -> Result<Self> {
155 let connection = match bus_type {
156 BusType::Session => {
157 debug!("Connecting to session bus");
158 Connection::session()
159 .await
160 .context("Failed to connect to session bus")?
161 }
162 BusType::System => {
163 debug!("Connecting to system bus");
164 Connection::system()
165 .await
166 .context("Failed to connect to system bus")?
167 }
168 BusType::Auto => {
169 if let Ok(conn) = Connection::session().await {
171 debug!("Connected to session bus, checking for daemon service");
172 if Self::service_exists(&conn).await {
173 debug!("Found daemon on session bus");
174 conn
175 } else {
176 debug!("Daemon not on session bus, trying system bus");
177 let sys_conn = Connection::system()
178 .await
179 .context("Failed to connect to system bus")?;
180 if Self::service_exists(&sys_conn).await {
181 debug!("Found daemon on system bus");
182 sys_conn
183 } else {
184 anyhow::bail!("Daemon service not found on session or system bus. Is ht32paneld running?")
186 }
187 }
188 } else {
189 debug!("Session bus unavailable, trying system bus");
190 Connection::system()
191 .await
192 .context("Failed to connect to any D-Bus")?
193 }
194 }
195 };
196
197 let proxy = Daemon1Proxy::new(&connection)
198 .await
199 .context("Failed to create D-Bus proxy")?;
200
201 Ok(Self { proxy })
202 }
203
204 async fn service_exists(conn: &Connection) -> bool {
206 use zbus::fdo::DBusProxy;
207 if let Ok(dbus_proxy) = DBusProxy::new(conn).await {
208 dbus_proxy
209 .name_has_owner("org.ht32panel.Daemon".try_into().unwrap())
210 .await
211 .unwrap_or(false)
212 } else {
213 false
214 }
215 }
216
217 pub async fn set_orientation(&self, orientation: &str) -> Result<()> {
219 self.proxy
220 .set_orientation(orientation)
221 .await
222 .context("Failed to set orientation via D-Bus")
223 }
224
225 pub async fn get_orientation(&self) -> Result<String> {
227 self.proxy
228 .get_orientation()
229 .await
230 .context("Failed to get orientation via D-Bus")
231 }
232
233 pub async fn clear_display(&self, color: &str) -> Result<()> {
235 self.proxy
236 .clear_display(color)
237 .await
238 .context("Failed to clear display via D-Bus")
239 }
240
241 pub async fn set_face(&self, face: &str) -> Result<()> {
243 self.proxy
244 .set_face(face)
245 .await
246 .context("Failed to set face via D-Bus")
247 }
248
249 pub async fn get_face(&self) -> Result<String> {
251 self.proxy
252 .get_face()
253 .await
254 .context("Failed to get face via D-Bus")
255 }
256
257 pub async fn set_led(&self, theme: u8, intensity: u8, speed: u8) -> Result<()> {
259 self.proxy
260 .set_led(theme, intensity, speed)
261 .await
262 .context("Failed to set LED via D-Bus")
263 }
264
265 pub async fn led_off(&self) -> Result<()> {
267 self.proxy
268 .led_off()
269 .await
270 .context("Failed to turn off LED via D-Bus")
271 }
272
273 pub async fn get_led_settings(&self) -> Result<(u8, u8, u8)> {
275 self.proxy
276 .get_led_settings()
277 .await
278 .context("Failed to get LED settings via D-Bus")
279 }
280
281 pub async fn get_theme(&self) -> Result<String> {
283 self.proxy
284 .get_theme()
285 .await
286 .context("Failed to get theme via D-Bus")
287 }
288
289 pub async fn set_theme(&self, name: &str) -> Result<()> {
291 self.proxy
292 .set_theme(name)
293 .await
294 .context("Failed to set theme via D-Bus")
295 }
296
297 pub async fn list_themes(&self) -> Result<Vec<String>> {
299 self.proxy
300 .list_themes()
301 .await
302 .context("Failed to list themes via D-Bus")
303 }
304
305 pub async fn list_themes_detailed(&self) -> Result<Vec<String>> {
307 self.proxy
308 .list_themes_detailed()
309 .await
310 .context("Failed to list themes detailed via D-Bus")
311 }
312
313 pub async fn list_face_ids(&self) -> Result<Vec<String>> {
315 self.proxy
316 .list_face_ids()
317 .await
318 .context("Failed to list face IDs via D-Bus")
319 }
320
321 pub async fn list_faces(&self) -> Result<Vec<String>> {
323 self.proxy
324 .list_faces()
325 .await
326 .context("Failed to list faces via D-Bus")
327 }
328
329 pub async fn list_network_interfaces(&self) -> Result<Vec<String>> {
331 self.proxy
332 .list_network_interfaces()
333 .await
334 .context("Failed to list network interfaces via D-Bus")
335 }
336
337 pub async fn get_screen_png(&self) -> Result<Vec<u8>> {
339 self.proxy
340 .get_screen_png()
341 .await
342 .context("Failed to get screen PNG via D-Bus")
343 }
344
345 pub async fn quit(&self) -> Result<()> {
347 self.proxy
348 .quit()
349 .await
350 .context("Failed to quit daemon via D-Bus")
351 }
352
353 pub async fn is_connected(&self) -> Result<bool> {
355 self.proxy
356 .connected()
357 .await
358 .context("Failed to get connection status via D-Bus")
359 }
360
361 pub async fn is_web_enabled(&self) -> Result<bool> {
363 self.proxy
364 .web_enabled()
365 .await
366 .context("Failed to get web enabled status via D-Bus")
367 }
368
369 pub async fn list_complications(&self) -> Result<Vec<(String, String, String, bool)>> {
372 self.proxy
373 .list_complications()
374 .await
375 .context("Failed to list complications via D-Bus")
376 }
377
378 pub async fn list_complications_detailed(&self) -> Result<Vec<String>> {
381 self.proxy
382 .list_complications_detailed()
383 .await
384 .context("Failed to list complications detailed via D-Bus")
385 }
386
387 pub async fn get_enabled_complications(&self) -> Result<Vec<String>> {
389 self.proxy
390 .get_enabled_complications()
391 .await
392 .context("Failed to get enabled complications via D-Bus")
393 }
394
395 pub async fn enable_complication(&self, complication_id: &str) -> Result<()> {
397 self.proxy
398 .enable_complication(complication_id)
399 .await
400 .context("Failed to enable complication via D-Bus")
401 }
402
403 pub async fn disable_complication(&self, complication_id: &str) -> Result<()> {
405 self.proxy
406 .disable_complication(complication_id)
407 .await
408 .context("Failed to disable complication via D-Bus")
409 }
410
411 pub async fn get_complication_option(
413 &self,
414 complication_id: &str,
415 option_id: &str,
416 ) -> Result<String> {
417 self.proxy
418 .get_complication_option(complication_id, option_id)
419 .await
420 .context("Failed to get complication option via D-Bus")
421 }
422
423 pub async fn set_complication_option(
425 &self,
426 complication_id: &str,
427 option_id: &str,
428 value: &str,
429 ) -> Result<()> {
430 self.proxy
431 .set_complication_option(complication_id, option_id, value)
432 .await
433 .context("Failed to set complication option via D-Bus")
434 }
435}