atomblocks/
lib.rs

1use config::Config;
2use std::{
3    process::{Child, Command, Stdio},
4    sync::Arc,
5    time::{Duration, Instant},
6};
7use x11rb::{
8    connection::Connection,
9    protocol::{
10        xproto::{
11            AtomEnum, ChangeWindowAttributesAux, ConnectionExt, EventMask, PropMode, Property,
12        },
13        Event,
14    },
15    wrapper::ConnectionExt as _,
16};
17
18pub mod atoms;
19pub mod cli;
20pub mod config;
21pub mod error;
22pub mod helpers;
23pub mod types;
24
25pub struct HitMan {
26    xconn: x11rb::rust_connection::RustConnection,
27    root: u32,
28    atoms: atoms::AtomBlocksAtoms,
29}
30impl HitMan {
31    pub fn new() -> types::Result<Self> {
32        let (xconn, root, atoms) = helpers::x11_connect()?;
33        Ok(Self { xconn, root, atoms })
34    }
35    pub fn hit_block(&self, id: u32) -> types::Result<()> {
36        Ok(self
37            .xconn
38            .change_property32(
39                PropMode::APPEND,
40                self.root,
41                self.atoms._ATOMBLOCKS_HIT_QUEUE,
42                AtomEnum::INTEGER,
43                &[id],
44            )?
45            .check()?)
46    }
47}
48
49pub struct AtomBlocks {
50    config: Config,
51    cells: Vec<String>,
52    xconn: Arc<x11rb::rust_connection::RustConnection>,
53    root: u32,
54    atoms: atoms::AtomBlocksAtoms,
55}
56
57impl AtomBlocks {
58    pub fn new(config: Config) -> types::Result<Self> {
59        let (xconn, root, atoms) = helpers::x11_connect()?;
60        xconn.change_window_attributes(
61            root,
62            &ChangeWindowAttributesAux::new().event_mask(EventMask::PROPERTY_CHANGE),
63        )?;
64
65        xconn
66            .change_window_attributes(
67                root,
68                &ChangeWindowAttributesAux::new().event_mask(EventMask::PROPERTY_CHANGE),
69            )
70            .expect("ChangeWindowAttributesAux");
71
72        let capacity = config.block.len();
73        let cells = vec![String::new(); capacity];
74        log::trace!("Allocated {} cells ({})", capacity, cells.len());
75        Ok(Self {
76            config,
77            cells,
78            xconn: Arc::new(xconn),
79            root,
80            atoms,
81        })
82    }
83
84    pub fn run(&mut self) -> types::Result<()> {
85        let (sender, receiver) = std::sync::mpsc::channel::<Vec<usize>>();
86        let sender = Arc::new(sender);
87        let xconn = self.xconn.clone();
88        let x11_atom = self.atoms._ATOMBLOCKS_HIT_QUEUE;
89        let root = self.root;
90        let x11_sender = sender.clone();
91        std::thread::spawn(move || loop {
92            while let Ok(Event::PropertyNotify(event)) = xconn.wait_for_event() {
93                if event.atom != x11_atom || event.window != root || event.state == Property::DELETE
94                {
95                    continue;
96                };
97
98                let Ok(Ok(reply)) = xconn
99                    .get_property(true, root, x11_atom, AtomEnum::INTEGER, 0, 1024)
100                    .map(|v| v.reply())
101                else {
102                    log::warn!("Failed to get property reply: {:?}", event.state);
103                    continue;
104                };
105
106                let Some(values) = reply.value32() else {
107                    log::warn!("Failed to get reply values, continue");
108                    continue;
109                };
110
111                log::info!("Received X11 PropertyNotify");
112                let mut hit_request_blocks = values.map(|v| v as usize).collect::<Vec<usize>>();
113                hit_request_blocks.dedup();
114                hit_request_blocks.iter().for_each(|index| {
115                    x11_sender
116                        .send(vec![*index])
117                        .map_err(|err| {
118                            log::error!("send error: {:?}", err);
119                        })
120                        .ok();
121                });
122            }
123            std::thread::sleep(Duration::from_millis(100));
124        });
125
126        let mut tasks: Vec<Task> = self
127            .config
128            .block
129            .clone()
130            .iter()
131            .map(|block| Task {
132                block: block.clone(),
133                last_run: Instant::now()
134                    - Duration::from_secs_f32(block.interval.unwrap_or_default()),
135            })
136            .collect();
137
138        let x11_sender = sender.clone();
139        std::thread::spawn(move || loop {
140            let mut indexes = Vec::new();
141            for (index, task) in tasks.iter_mut().enumerate() {
142                let Some(interval) = task.block.interval else {
143                    continue;
144                };
145                if (task.last_run + Duration::from_secs_f32(interval)) > Instant::now() {
146                    continue;
147                }
148                task.last_run = Instant::now();
149                indexes.push(index);
150            }
151
152            if !indexes.is_empty() {
153                x11_sender
154                    .send(indexes)
155                    .map_err(|err| {
156                        log::error!("send error: {:?}", err);
157                    })
158                    .ok();
159            }
160
161            std::thread::sleep(Duration::from_millis(100));
162        });
163
164        log::info!("Ready to receive events");
165        while let Ok(indexes) = receiver.recv() {
166            let mut new_cells = self.cells.clone();
167            let mut spawns: Vec<(usize, Child)> = Vec::new();
168
169            for index in indexes {
170                if let Some(block) = self.config.block.get(index) {
171                    log::debug!("Run: {}", block.execute.as_str());
172
173                    if let Ok(output) = Command::new("sh")
174                        .arg("-c")
175                        .stdout(Stdio::piped())
176                        .stderr(Stdio::piped())
177                        .arg(block.execute.as_str())
178                        .spawn()
179                    {
180                        spawns.push((index, output));
181                    }
182                }
183            }
184
185            for (index, child) in spawns {
186                let Some(block) = self.config.block.get(index) else {
187                    continue;
188                };
189                let Ok(output) = child.wait_with_output() else {
190                    continue;
191                };
192                new_cells[index] =
193                    block.print(String::from_utf8_lossy(output.stdout.as_slice()).to_string());
194            }
195
196            if new_cells != self.cells {
197                self.cells = new_cells;
198                self.print();
199            }
200        }
201
202        Ok(())
203    }
204
205    fn print(&self) {
206        log::info!("Updating WM_NAME property...");
207        let result = self.cells.iter().map(|c| c.to_string());
208        let delim = self.config.delimiter.clone().unwrap_or_default();
209        let result = result
210            .into_iter()
211            .filter(|r| !r.is_empty())
212            .collect::<Vec<_>>();
213
214        if let Err(err) = self
215            .xconn
216            .change_property8(
217                PropMode::REPLACE,
218                self.root,
219                AtomEnum::WM_NAME,
220                AtomEnum::STRING,
221                result.join(delim.as_str()).as_bytes(),
222            )
223            .map(|r| r.check())
224        {
225            log::error!("Failed to set WM_NAME property: {}", err);
226        }
227    }
228}
229
230pub struct Task {
231    block: config::Block,
232    last_run: Instant,
233}