1use crate::{Result, core::Draw};
3use penrose::{
4 Color, WinId,
5 core::{State, WindowManager},
6 pure::geometry::Rect,
7 x::{Atom, ClientConfig, Prop, WinType, XConn, event::XEvent},
8};
9use std::fmt;
10use tracing::{debug, error, info};
11
12pub mod schedule;
13pub mod widgets;
14
15use schedule::{UpdateSchedule, run_update_schedules};
16use widgets::Widget;
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
20pub enum Position {
21 Top,
23 Bottom,
25}
26
27pub struct PerScreen<X: XConn> {
30 point_size: u8,
31 h: u32,
32 ws: Vec<Box<dyn Widget<X>>>,
33}
34
35impl<X: XConn> fmt::Debug for PerScreen<X> {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.debug_struct("PerScreen")
38 .field("point_size", &self.point_size)
39 .field("h", &self.h)
40 .finish()
41 }
42}
43
44impl<X: XConn> PerScreen<X> {
45 pub fn new(point_size: u8, h: u32, ws: Vec<Box<dyn Widget<X>>>) -> Self {
47 Self { point_size, ws, h }
48 }
49}
50
51#[derive(Debug)]
52enum Widgets<X: XConn> {
53 Shared(PerScreen<X>),
54 PerScreen(Vec<PerScreen<X>>),
55}
56
57impl<X: XConn> Widgets<X> {
58 fn for_screen_mut(&mut self, ix: usize) -> &mut PerScreen<X> {
59 match self {
60 Self::Shared(ps) => ps,
61 Self::PerScreen(pss) => {
62 let ix = if ix >= pss.len() { pss.len() - 1 } else { ix };
63 &mut pss[ix]
64 }
65 }
66 }
67
68 fn for_each_mut<F>(&mut self, n_screens: usize, mut f: F)
69 where
70 F: FnMut(&mut Box<dyn Widget<X>>),
71 {
72 match self {
73 Self::Shared(ps) => ps.ws.iter_mut().for_each(f),
74 Self::PerScreen(pss) => pss
75 .iter_mut()
76 .take(n_screens) .for_each(|ps| ps.ws.iter_mut().for_each(&mut f)),
78 }
79 }
80
81 fn require_draw(&self, n_screens: usize) -> bool {
82 match self {
83 Self::Shared(ps) => ps.ws.iter().any(|w| w.require_draw()),
84 Self::PerScreen(pss) => pss
85 .iter()
86 .take(n_screens) .any(|ps| ps.ws.iter().any(|w| w.require_draw())),
88 }
89 }
90
91 fn update_schedules(&mut self) -> Vec<UpdateSchedule> {
92 match self {
93 Self::Shared(ps) => ps
94 .ws
95 .iter_mut()
96 .filter_map(|w| w.update_schedule())
97 .collect(),
98 Self::PerScreen(pss) => pss
99 .iter_mut()
100 .flat_map(|ps| ps.ws.iter_mut().filter_map(|w| w.update_schedule()))
101 .collect(),
102 }
103 }
104}
105
106#[derive(Debug)]
108pub struct StatusBar<X: XConn> {
109 draw: Draw,
110 position: Position,
111 widgets: Widgets<X>,
112 screens: Vec<(WinId, u32)>,
113 active_screen: usize,
114 font: String,
115 redrawn: bool,
116}
117
118impl<X: XConn> StatusBar<X> {
119 pub fn try_new(
122 position: Position,
123 h: u32,
124 bg: impl Into<Color>,
125 font: &str,
126 point_size: u8,
127 widgets: Vec<Box<dyn Widget<X>>>,
128 ) -> Result<Self> {
129 let bg = bg.into();
130 let draw = Draw::new(font, point_size, bg)?;
131
132 Ok(Self {
133 draw,
134 position,
135 widgets: Widgets::Shared(PerScreen::new(point_size, h, widgets)),
136 screens: vec![],
137 active_screen: 0,
138 font: font.to_string(),
139 redrawn: false,
140 })
141 }
142
143 pub fn try_new_per_screen(
148 position: Position,
149 bg: impl Into<Color>,
150 font: &str,
151 widgets: Vec<PerScreen<X>>,
152 ) -> Result<Self> {
153 let bg = bg.into();
154 let point_size = widgets[0].point_size;
155 let mut draw = Draw::new(font, point_size, bg)?;
156 for &PerScreen { point_size, .. } in widgets.iter() {
157 draw.add_font(font, point_size)?;
158 }
159
160 Ok(Self {
161 draw,
162 position,
163 widgets: Widgets::PerScreen(widgets),
164 screens: vec![],
165 active_screen: 0,
166 font: font.to_string(),
167 redrawn: false,
168 })
169 }
170
171 pub fn add_to(mut self, mut wm: WindowManager<X>) -> WindowManager<X>
177 where
178 X: 'static,
179 {
180 let schedules = self.widgets.update_schedules();
181 if !schedules.is_empty() {
182 run_update_schedules(schedules);
183 }
184
185 wm.state.add_extension(self);
186 wm.state.config.compose_or_set_event_hook(event_hook);
187 wm.state.config.compose_or_set_manage_hook(manage_hook);
188 wm.state.config.compose_or_set_refresh_hook(refresh_hook);
189 wm.state.config.compose_or_set_startup_hook(startup_hook);
190
191 wm
192 }
193
194 fn init_for_screens(&mut self) -> Result<()> {
195 info!("initialising per screen status bar windows");
196 let screen_details = self.draw.conn.screen_details()?;
197
198 self.screens = screen_details
199 .iter()
200 .enumerate()
201 .map(|(i, &Rect { x, y, w, h })| {
202 let bar_h = self.widgets.for_screen_mut(i).h;
203 let y = match self.position {
204 Position::Top => y,
205 Position::Bottom => y + (h - bar_h) as i32,
206 };
207
208 debug!("creating new window");
209 let id = self.draw.new_window(
210 WinType::InputOutput(Atom::NetWindowTypeDock),
211 Rect::new(x, y, w, bar_h),
212 false,
213 )?;
214
215 let data = &[ClientConfig::StackBottom];
216 self.draw.conn.set_client_config(id, data)?;
217
218 debug!(%id, "setting props");
219 let p = Prop::UTF8String(vec!["penrose-statusbar".to_string()]);
220 for atom in &[Atom::NetWmName, Atom::WmName, Atom::WmClass] {
221 self.draw.conn.set_prop(id, atom.as_ref(), p.clone())?;
222 }
223
224 debug!("flushing");
225 self.draw.flush(id)?;
226
227 Ok((id, w))
228 })
229 .collect::<Result<Vec<(WinId, u32)>>>()?;
230
231 Ok(())
232 }
233
234 fn redraw_screen(&mut self, i: usize) -> Result<()> {
237 let (id, w_screen) = self.screens[i];
238 let screen_has_focus = self.active_screen == i;
239 let ps = self.widgets.for_screen_mut(i);
240
241 self.draw.set_font(&self.font, ps.point_size)?;
242 let mut ctx = self.draw.context_for(id)?;
243 ctx.clear()?;
244
245 let mut extents = Vec::new();
246 let mut greedy_indices = Vec::new();
247
248 for (j, w) in ps.ws.iter_mut().enumerate() {
249 extents.push(w.current_extent(&mut ctx, ps.h)?);
250 if w.is_greedy() {
251 greedy_indices.push(j)
252 }
253 }
254
255 let total = extents.iter().map(|(w, _)| w).sum::<u32>();
256 let n_greedy = greedy_indices.len();
257
258 if total < w_screen && n_greedy > 0 {
259 let per_greedy = (w_screen - total) / n_greedy as u32;
260 for i in greedy_indices.iter() {
261 let (w, h) = extents[*i];
262 extents[*i] = (w + per_greedy, h);
263 }
264 }
265
266 let mut x = 0;
267 for (wd, (w, _)) in ps.ws.iter_mut().zip(extents) {
268 wd.draw(&mut ctx, self.active_screen, screen_has_focus, w, ps.h)?;
269 x += w;
270 ctx.set_x_offset(x as i32);
271 }
272
273 self.draw.flush(id)?;
274
275 Ok(())
276 }
277
278 pub fn redraw(&mut self) -> Result<()> {
280 for i in 0..self.screens.len() {
281 self.redraw_screen(i)?;
282 }
283 self.redrawn = true;
284
285 Ok(())
286 }
287
288 fn redraw_if_needed(&mut self) -> Result<()> {
289 if self.widgets.require_draw(self.screens.len()) {
290 self.redraw()?;
291 }
292
293 Ok(())
294 }
295}
296
297pub fn startup_hook<X: XConn + 'static>(state: &mut State<X>, x: &mut X) -> penrose::Result<()> {
299 let s = state.extension::<StatusBar<X>>()?;
300 let mut bar = s.borrow_mut();
301
302 if let Err(e) = bar.init_for_screens() {
303 error!(%e, "unabled to initialise for screens");
304 return Err(penrose::Error::NoScreens);
305 }
306
307 info!("running startup widget hooks");
308 let n_screens = bar.screens.len();
309 bar.widgets.for_each_mut(n_screens, |w| {
310 if let Err(e) = w.on_startup(state, x) {
311 error!(%e, "error running widget startup hook");
312 };
313 });
314
315 if let Err(e) = bar.redraw() {
316 error!(%e, "error redrawing status bar");
317 }
318
319 Ok(())
320}
321
322pub fn refresh_hook<X: XConn + 'static>(state: &mut State<X>, x: &mut X) -> penrose::Result<()> {
324 let s = state.extension::<StatusBar<X>>()?;
325 let mut bar = s.borrow_mut();
326
327 bar.active_screen = state.client_set.current_screen().index();
328 let n_screens = bar.screens.len();
329 bar.widgets.for_each_mut(n_screens, |w| {
330 if let Err(e) = w.on_refresh(state, x) {
331 error!(%e, "error running widget refresh hook");
332 }
333 });
334
335 let res = if !bar.redrawn {
338 bar.redraw()
339 } else {
340 bar.redraw_if_needed()
341 };
342
343 if let Err(e) = res {
344 error!(%e, "error redrawing status bar");
345 }
346
347 Ok(())
348}
349
350pub fn event_hook<X: XConn + 'static>(
352 event: &XEvent,
353 state: &mut State<X>,
354 x: &mut X,
355) -> penrose::Result<bool> {
356 use XEvent::{ConfigureNotify, RandrNotify};
357
358 let s = state.extension::<StatusBar<X>>()?;
359 let mut bar = s.borrow_mut();
360 bar.redrawn = false;
361
362 if matches!(event, RandrNotify) || matches!(event, ConfigureNotify(e) if e.is_root) {
363 info!("screens have changed: recreating status bars");
364 let screens: Vec<_> = bar.screens.drain(..).collect();
365
366 for (id, _) in screens {
367 info!(%id, "removing previous status bar");
368 if let Err(e) = bar.draw.destroy_window_and_surface(id) {
369 error!(%e, "error when removing previous status bar state");
370 }
371 }
372
373 if let Err(e) = bar.init_for_screens() {
374 error!(%e, "unabled to initialise for screens");
375 return Err(penrose::Error::NoScreens);
376 }
377 }
378
379 bar.active_screen = state.client_set.current_screen().index();
380 let n_screens = bar.screens.len();
381 bar.widgets.for_each_mut(n_screens, |w| {
382 if let Err(e) = w.on_event(event, state, x) {
383 error!(%e, "error running widget event hook");
384 };
385 });
386
387 if let Err(e) = bar.redraw_if_needed() {
388 error!(%e, "error redrawing status bar");
389 }
390
391 Ok(true)
392}
393
394pub fn manage_hook<X: XConn + 'static>(
396 id: WinId,
397 state: &mut State<X>,
398 x: &mut X,
399) -> penrose::Result<()> {
400 let s = state.extension::<StatusBar<X>>()?;
401 let mut bar = s.borrow_mut();
402
403 bar.active_screen = state.client_set.current_screen().index();
404 let n_screens = bar.screens.len();
405 bar.widgets.for_each_mut(n_screens, |w| {
406 if let Err(e) = w.on_new_client(id, state, x) {
407 error!(%e, "error running widget manage hook");
408 }
409 });
410
411 if let Err(e) = bar.redraw_if_needed() {
412 error!(%e, "error redrawing status bar");
413 }
414
415 Ok(())
416}