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