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}