dwm_statusbar/
lib.rs

1use std::io::Read;
2use std::marker::{Send, Sync};
3use std::os::unix::net::UnixListener;
4use std::sync::mpsc;
5use std::sync::{Arc, Mutex};
6use std::thread;
7use std::time::{Duration, Instant};
8
9const SOCKET_PATH: &str = "/tmp/dwm-statusbar.sock";
10
11type BlockFn = Box<dyn Fn(u32) -> String + Send + Sync>;
12
13pub struct StatusBlock {
14    pub f: BlockFn,
15    pub interval: Duration,
16}
17
18impl StatusBlock {
19    pub fn new(f: BlockFn, int_millis: u64) -> Self {
20        Self {
21            f: Box::new(f),
22            interval: Duration::from_millis(int_millis),
23        }
24    }
25}
26
27#[macro_export]
28macro_rules! blocks {
29    [ $({$f:expr, $int:expr}),* $(,)? ] => {
30        vec![$($crate::StatusBlock::new(Box::new($f), $int)),*]
31    };
32}
33
34pub struct StatusBar {
35    separator: &'static str,
36    windows: Vec<Vec<Option<StatusBlock>>>,
37    // pool: Vec<thread::JoinHandle<()>>,
38    placeholder: Arc<Mutex<Vec<String>>>,
39    window_index: Arc<Mutex<u32>>,
40
41    tx: mpsc::Sender<()>,
42    rx: mpsc::Receiver<()>,
43}
44
45impl StatusBar {
46    pub fn new(
47        separator: &'static str,
48        default_window: u32,
49        windows: Vec<Vec<StatusBlock>>,
50    ) -> Self {
51        let (tx, rx) = mpsc::channel();
52
53        Self {
54            placeholder: Arc::new(Mutex::new(vec![
55                String::new();
56                windows[default_window as usize].len()
57            ])),
58            separator,
59            windows: windows
60                .into_iter()
61                .map(|i| i.into_iter().map(Some).collect())
62                .collect(),
63            // pool: vec![],
64            window_index: Arc::new(Mutex::new(default_window)),
65
66            tx,
67            rx,
68        }
69    }
70
71    /// Spawn threads, start updates
72    pub fn start(&mut self) {
73        for (current_window_idx, window) in self.windows.iter_mut().enumerate() {
74            for (placeholder_index, block) in window.iter_mut().enumerate() {
75                let placeholder = Arc::clone(&self.placeholder);
76                let block = block.take().unwrap();
77                let tx = self.tx.clone();
78                let display_window_index = Arc::clone(&self.window_index);
79
80                thread::spawn(move || {
81                    let mut i = 0;
82                    loop {
83                        if *display_window_index.lock().unwrap() != current_window_idx as u32 {
84                            thread::sleep(block.interval);
85                            continue;
86                        }
87
88                        let start = Instant::now();
89                        let output = (block.f)(i);
90
91                        // prevents panic if the output took way too long
92                        if *display_window_index.lock().unwrap() != current_window_idx as u32 {
93                            thread::sleep(block.interval);
94                            continue;
95                        }
96
97                        placeholder.lock().unwrap()[placeholder_index] = output;
98                        tx.send(()).unwrap();
99
100                        let time = start.elapsed();
101                        if block.interval >= time {
102                            thread::sleep(block.interval - time);
103                        }
104
105                        i += 1;
106                    }
107                });
108            }
109        }
110
111        let max_window_idx = self.windows.len();
112        let window_index = Arc::clone(&self.window_index);
113        let placeholder = Arc::clone(&self.placeholder);
114        let window_sizes = self.windows.iter().map(|i| i.len()).collect::<Vec<_>>();
115        let tx = self.tx.clone();
116
117        thread::spawn(move || {
118            let mut previous_state = vec![vec![]; max_window_idx + 1];
119            let _ = std::fs::remove_file(SOCKET_PATH);
120            let listener = UnixListener::bind(SOCKET_PATH).unwrap();
121
122            loop {
123                println!("Listening for connection");
124                for mut stream in listener.incoming().flatten() {
125                    let mut buf = String::new();
126
127                    if stream.read_to_string(&mut buf).is_err() {
128                        eprintln!("[SocketError] Failed to read");
129                        continue;
130                    };
131
132                    let Ok(window_idx) = buf.trim().parse::<u32>() else {
133                        eprintln!("[SocketError] Invalid input received");
134                        continue;
135                    };
136
137                    if window_idx > max_window_idx as u32
138                        || window_sizes.len() <= window_idx as usize
139                    {
140                        eprintln!("[SocketError] Invalid window index");
141                        continue;
142                    }
143
144                    let mut wi = window_index.lock().unwrap();
145                    let previous_window_idx = *wi;
146                    *wi = window_idx;
147                    drop(wi);
148
149                    let mut placeholder = placeholder.lock().unwrap();
150
151                    // save state before switching to next window
152                    previous_state[previous_window_idx as usize] = placeholder.clone();
153
154                    let ps = &mut previous_state[window_idx as usize];
155                    *placeholder = if !ps.is_empty() {
156                        ps.to_vec()
157                    } else {
158                        vec![String::from("loading..."); window_sizes[window_idx as usize]]
159                    };
160
161                    tx.send(()).unwrap();
162                }
163            }
164        });
165
166        while self.rx.recv().is_ok() {
167            self.update_status();
168        }
169    }
170
171    fn update_status(&self) {
172        let output = self.placeholder.lock().unwrap().join(self.separator);
173        // std::thread::spawn(|| {
174        std::process::Command::new("xsetroot")
175            .arg("-name")
176            .arg(output)
177            .spawn()
178            .unwrap()
179            .wait()
180            .expect("failed on wait");
181        // });
182    }
183}