Prevent small race when both track and artist change
[xsetrootd.git] / src / main.rs
CommitLineData
12bd87b0 1use dbus::arg;
baedbc7e
JC
2use dbus::blocking::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged;
3use dbus::blocking::Connection;
4use dbus::message::Message;
1e52c28e 5use std::collections::HashMap;
42fef3a7 6use std::process::Command;
1e52c28e 7use std::sync::{Arc, Mutex};
bbc46eb1 8use std::thread;
baedbc7e
JC
9use std::time::Duration;
10
12bd87b0
JC
11// Custom signal type impl
12// Mostly copied from library examples
13#[derive(Debug)]
14pub struct ComJacobCasperMailUnreadCount {
15 pub count: u32,
16}
17
18impl arg::AppendAll for ComJacobCasperMailUnreadCount {
19 fn append(&self, iter: &mut arg::IterAppend) {
20 arg::RefArg::append(&self.count, iter);
21 }
22}
23
24impl arg::ReadAll for ComJacobCasperMailUnreadCount {
25 fn read(iter: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
26 Ok(ComJacobCasperMailUnreadCount {
27 count: iter.read()?,
28 })
29 }
30}
31
32impl dbus::message::SignalArgs for ComJacobCasperMailUnreadCount {
33 const NAME: &'static str = "UnreadCount";
34 const INTERFACE: &'static str = "com.jacobcasper.Mail";
35}
36
bbc46eb1
JC
37fn get_local_time_string() -> std::string::String {
38 use chrono::prelude::*;
39 let local_time = chrono::prelude::Local::now();
40 return format!(
6336104a 41 "{}-{}-{:02} {}:{:02}",
bbc46eb1
JC
42 local_time.year(),
43 local_time.month(),
44 local_time.day(),
45 local_time.hour(),
46 local_time.minute(),
47 );
48}
49
ddaab88f 50fn update_map(arc_map: Arc<Mutex<HashMap<String, String>>>, key: &str, val: &str) {
1e52c28e
JC
51 let clone_arc = arc_map.clone();
52 let mut map = clone_arc.lock().unwrap();
ddaab88f 53 map.insert(String::from(key), String::from(val));
142f5a1c
JC
54}
55
56fn update_xsetroot(arc_map: Arc<Mutex<HashMap<String, String>>>) {
57 let clone_arc = arc_map.clone();
58 let map = clone_arc.lock().unwrap();
42fef3a7
JC
59 let _ = Command::new("xsetroot")
60 .arg("-name")
61 .arg(format!(
7e7a1f74 62 "[{playback_status} {title} - {artist}] | ✉ {unread_count} | {date_time}",
6d3ac36b 63 playback_status = map.get("playback_status").unwrap_or(&String::from("🔈")),
42fef3a7
JC
64 title = map.get("title").unwrap_or(&String::from("")),
65 artist = map.get("artist").unwrap_or(&String::from("")),
66 unread_count = map.get("unread_count").unwrap_or(&String::from("?")),
bbc46eb1 67 date_time = map.get("date_time").unwrap_or(&get_local_time_string()),
42fef3a7
JC
68 ))
69 .spawn();
1e52c28e
JC
70}
71
baedbc7e 72fn main() -> Result<(), Box<dyn std::error::Error>> {
1e52c28e 73 let arc_locked_xset_map = Arc::new(Mutex::new(HashMap::new()));
baedbc7e
JC
74 let conn = Connection::new_session()?;
75
12bd87b0
JC
76 let mail_proxy = conn.with_proxy(
77 "com.jacobcasper.Mail",
78 "/com/jacobcasper/Mail/Unread",
79 Duration::from_millis(5000),
80 );
81
82 let mail_match_map = arc_locked_xset_map.clone();
83 let _ = mail_proxy.match_signal(
84 move |m: ComJacobCasperMailUnreadCount, _: &Connection, _: &Message| {
85 update_map(
86 mail_match_map.clone(),
87 "unread_count",
88 m.count.to_string().as_str(),
89 );
142f5a1c 90 update_xsetroot(mail_match_map.clone());
12bd87b0
JC
91 true
92 },
93 );
94
95 let spotify_proxy = conn.with_proxy(
baedbc7e
JC
96 "org.mpris.MediaPlayer2.spotify",
97 "/org/mpris/MediaPlayer2",
98 Duration::from_millis(5000),
99 );
100
16683530 101 let spotify_match_map = arc_locked_xset_map.clone();
12bd87b0 102 let _ = spotify_proxy.match_signal(
1e52c28e 103 move |pc: PropertiesPropertiesChanged, _: &Connection, _: &Message| {
be7f5d4d
JC
104 match pc.changed_properties.get("Metadata") {
105 Some(variant) => {
106 variant.0.as_iter().and_then(|mut iter| {
107 // Iterating over a RefArg goes key, value, key, value... Insane honestly.
108 while let Some(key) = iter.next() {
109 let key_str = key.as_str()?;
110 let value = iter.next();
79b46cd5 111
be7f5d4d
JC
112 match key_str {
113 "xesam:artist" => {
114 // Variant holding a variant that should just be a Vec<&str> I
115 // believe. This is _the recommended_ way to do this by the author.
116 let inner_value =
117 value?.as_iter()?.next()?.as_iter()?.next()?;
118 let artist = inner_value.as_str()?;
119 update_map(spotify_match_map.clone(), "artist", artist);
120 }
121 "xesam:title" => {
122 let title = value?.as_iter()?.next()?.as_str()?;
123 update_map(spotify_match_map.clone(), "title", title);
124 }
125 _ => (),
79b46cd5 126 }
79b46cd5 127 }
142f5a1c 128 update_xsetroot(spotify_match_map.clone());
be7f5d4d
JC
129 Some(())
130 });
131 }
132 None => (),
133 }
134 match &pc.changed_properties.get("PlaybackStatus") {
135 Some(variant) => {
136 let playback_status = &variant.0;
137 update_map(
138 spotify_match_map.clone(),
139 "playback_status",
140 match &*playback_status.as_str().unwrap_or("") {
141 "Playing" => "🔊",
142 _ => "🔈",
143 },
144 );
142f5a1c 145 update_xsetroot(spotify_match_map.clone());
be7f5d4d
JC
146 }
147 None => (),
148 }
baedbc7e
JC
149 true
150 },
151 );
152
bbc46eb1
JC
153 let date_time_map = arc_locked_xset_map.clone();
154 let _ = thread::spawn(move || -> Result<(), std::time::SystemTimeError> {
155 let mut last_run_at = std::time::SystemTime::now();
156 loop {
157 let sys_time = std::time::SystemTime::now();
158 let elapsed_time = sys_time.duration_since(last_run_at)?;
159 if elapsed_time.as_secs() > 60 {
160 last_run_at = sys_time;
161 update_map(
162 date_time_map.clone(),
163 "date_time",
164 get_local_time_string().as_str(),
165 );
142f5a1c 166 update_xsetroot(date_time_map.clone());
bbc46eb1
JC
167 }
168 thread::sleep(Duration::from_millis(5000));
169 }
170 });
171
baedbc7e
JC
172 loop {
173 conn.process(Duration::from_millis(1000))?;
174 }
175}