a5b3c32f36ff81ef69710abab028b8bf37619ef3
[xsetrootd.git] / src / main.rs
1 use dbus::arg;
2 use dbus::blocking::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged;
3 use dbus::blocking::Connection;
4 use dbus::message::Message;
5 use std::collections::HashMap;
6 use std::process::Command;
7 use std::sync::{Arc, Mutex};
8 use std::time::Duration;
9
10 // Custom signal type impl
11 // Mostly copied from library examples
12 #[derive(Debug)]
13 pub struct ComJacobCasperMailUnreadCount {
14 pub count: u32,
15 }
16
17 impl arg::AppendAll for ComJacobCasperMailUnreadCount {
18 fn append(&self, iter: &mut arg::IterAppend) {
19 arg::RefArg::append(&self.count, iter);
20 }
21 }
22
23 impl arg::ReadAll for ComJacobCasperMailUnreadCount {
24 fn read(iter: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
25 Ok(ComJacobCasperMailUnreadCount {
26 count: iter.read()?,
27 })
28 }
29 }
30
31 impl dbus::message::SignalArgs for ComJacobCasperMailUnreadCount {
32 const NAME: &'static str = "UnreadCount";
33 const INTERFACE: &'static str = "com.jacobcasper.Mail";
34 }
35
36 fn update_map(arc_map: Arc<Mutex<HashMap<String, String>>>, key: &str, val: &str) {
37 let clone_arc = arc_map.clone();
38 let mut map = clone_arc.lock().unwrap();
39 map.insert(String::from(key), String::from(val));
40 let _ = Command::new("xsetroot")
41 .arg("-name")
42 .arg(format!(
43 "[🔊 {title} - {artist}] | ✉ {unread_count}",
44 title = map.get("title").unwrap_or(&String::from("")),
45 artist = map.get("artist").unwrap_or(&String::from("")),
46 unread_count = map.get("unread_count").unwrap_or(&String::from("?")),
47 ))
48 .spawn();
49 }
50
51 fn main() -> Result<(), Box<dyn std::error::Error>> {
52 let arc_locked_xset_map = Arc::new(Mutex::new(HashMap::new()));
53 let conn = Connection::new_session()?;
54
55 let mail_proxy = conn.with_proxy(
56 "com.jacobcasper.Mail",
57 "/com/jacobcasper/Mail/Unread",
58 Duration::from_millis(5000),
59 );
60
61 let mail_match_map = arc_locked_xset_map.clone();
62 let _ = mail_proxy.match_signal(
63 move |m: ComJacobCasperMailUnreadCount, _: &Connection, _: &Message| {
64 update_map(
65 mail_match_map.clone(),
66 "unread_count",
67 m.count.to_string().as_str(),
68 );
69 true
70 },
71 );
72
73 let spotify_proxy = conn.with_proxy(
74 "org.mpris.MediaPlayer2.spotify",
75 "/org/mpris/MediaPlayer2",
76 Duration::from_millis(5000),
77 );
78
79 let spotify_match_map = arc_locked_xset_map.clone();
80 let _ = spotify_proxy.match_signal(
81 move |pc: PropertiesPropertiesChanged, _: &Connection, _: &Message| {
82 pc.changed_properties["Metadata"]
83 .0
84 .as_iter()
85 .and_then(|mut iter| {
86 // Iterating over a RefArg goes key, value, key, value... Insane honestly.
87 while let Some(key) = iter.next() {
88 let key_str = key.as_str()?;
89 let value = iter.next();
90
91 match key_str {
92 "xesam:artist" => {
93 // Variant holding a variant that should just be a Vec<&str> I
94 // believe. This is _the recommended_ way to do this by the author.
95 let inner_value = value?.as_iter()?.next()?.as_iter()?.next()?;
96 let artist = inner_value.as_str()?;
97 update_map(spotify_match_map.clone(), "artist", artist);
98 }
99 "xesam:title" => {
100 let title = value?.as_iter()?.next()?.as_str()?;
101 update_map(spotify_match_map.clone(), "title", title);
102 }
103 _ => (),
104 }
105 }
106 Some(())
107 });
108 true
109 },
110 );
111
112 loop {
113 conn.process(Duration::from_millis(1000))?;
114 }
115 }