lazybar_core/
bar.rs

1use std::{
2    ops::BitAnd,
3    pin::Pin,
4    rc::Rc,
5    sync::{Arc, Mutex},
6};
7
8use anyhow::{anyhow, Result};
9use csscolorparser::Color;
10use derive_debug::Dbg;
11use lazy_static::lazy_static;
12use lazybar_types::EventResponse;
13use regex::Regex;
14use tokio::{
15    net::UnixStream,
16    sync::{mpsc::UnboundedSender, OnceCell},
17    task::JoinSet,
18};
19use tokio_stream::{Stream, StreamMap};
20use x11rb::{
21    connection::Connection,
22    protocol::{
23        self,
24        xproto::{ConnectionExt, Visualtype, Window},
25    },
26    xcb_ffi::XCBConnection,
27};
28
29use crate::{
30    create_surface, create_window,
31    ipc::{self, ChannelEndpoint},
32    set_wm_properties, Alignment, IpcStream, Margins, PanelDrawFn, PanelHideFn,
33    PanelShowFn, PanelShutdownFn, PanelStream, Position,
34};
35#[cfg(feature = "cursor")]
36use crate::{x::set_cursor, CursorFn};
37
38lazy_static! {
39    static ref REGEX: Regex =
40        Regex::new(r"(?<region>[lcr])(?<idx>\d+).(?<message>.+)").unwrap();
41    #[allow(missing_docs)]
42    pub static ref BAR_INFO: OnceCell<BarInfo> = OnceCell::new();
43}
44
45/// Information about the bar, usually for use in building panels.
46#[derive(Debug, Clone)]
47pub struct BarInfo {
48    /// The X resource id of the bar window
49    pub window: Window,
50    /// The X visual that the bar uses
51    pub visual: Visualtype,
52    /// The width of the bar in pixels
53    pub width: u16,
54    /// The height of the bar in pixels
55    pub height: u16,
56    /// Whether the bar supports transparency
57    pub transparent: bool,
58    /// The background color of the bar
59    pub bg: Color,
60    /// The X11 cursor names associated with the bar
61    #[cfg(feature = "cursor")]
62    pub cursors: Cursors,
63}
64
65/// A set of X11 cursor names.
66#[cfg(feature = "cursor")]
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
68pub struct Cursors {
69    /// The default cursor
70    pub default: &'static str,
71    /// A cursor representing clickability
72    pub click: &'static str,
73    /// A cursor representing scrollability
74    pub scroll: &'static str,
75}
76
77/// The cursor to be displayed.
78///
79/// The X11 cursor name associated with each variant can be set for each bar
80/// using [`BarConfig`][crate::BarConfig]
81#[cfg(feature = "cursor")]
82#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
83pub enum Cursor {
84    /// The default cursor.
85    #[default]
86    Default,
87    /// A cursor indicating that a panel is clickable.
88    Click,
89    /// A cursor indicating that a panel is scrollable.
90    Scroll,
91}
92
93#[cfg(feature = "cursor")]
94impl From<Cursor> for &str {
95    fn from(value: Cursor) -> Self {
96        match value {
97            Cursor::Default => BAR_INFO.get().unwrap().cursors.default,
98            Cursor::Click => BAR_INFO.get().unwrap().cursors.click,
99            Cursor::Scroll => BAR_INFO.get().unwrap().cursors.scroll,
100        }
101    }
102}
103
104#[derive(PartialEq, Eq, Debug)]
105enum CenterState {
106    Center,
107    Left,
108    Right,
109    Unknown,
110}
111
112#[derive(Debug)]
113enum Region {
114    Left,
115    CenterRight,
116    Right,
117    All,
118    Custom { start_x: f64, end_x: f64 },
119}
120
121#[derive(Debug)]
122struct Extents {
123    left: f64,
124    center: (f64, f64),
125    right: f64,
126}
127
128/// Which neighbor(s) a panel depends on to be shown
129///
130/// If a panel is dependent on another panel with non-None dependence, it will
131/// not be shown.
132#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
133pub enum Dependence {
134    /// The panel will always be shown
135    #[default]
136    None,
137    /// The panel will be shown if its left neighbor has a nonzero width
138    Left,
139    /// The panel will be shown if its right neighbor has a nonzero width
140    Right,
141    /// The panel will be shown if both of its neighbors have a nonzero width
142    Both,
143}
144
145/// What to set the cursor to when a panel is hovered.
146#[cfg(feature = "cursor")]
147#[derive(Dbg)]
148pub enum CursorInfo {
149    /// The cursor should be the same across the whole panel. `None` will set
150    /// the cursor the system default.
151    Static(Cursor),
152    /// The cursor can have multiple values within the panel. If this function
153    /// returns `Err(_)` or `Ok(None)`, the cursor will be set to the
154    /// system default.
155    #[dbg(skip)]
156    Dynamic(CursorFn),
157}
158
159#[cfg(feature = "cursor")]
160impl CursorInfo {
161    /// Gets the cursor name.
162    ///
163    /// Either returns the `Static` name or calls the
164    /// `Dynamic` function.
165    pub fn get(&self, event: MouseEvent) -> Result<Cursor> {
166        match self {
167            Self::Static(s) => Ok(*s),
168            Self::Dynamic(f) => f(event),
169        }
170    }
171}
172
173/// Information describing how to draw/redraw a [`Panel`].
174#[derive(Dbg)]
175pub struct PanelDrawInfo {
176    /// The width in pixels of the panel.
177    pub width: i32,
178    /// The height in pixels of the panel.
179    pub height: i32,
180    /// When the panel should be hidden
181    pub dependence: Dependence,
182    /// A [`FnMut`] that draws the panel to the [`cairo::Context`], starting at
183    /// (0, 0). Translating the `Context` is the responsibility of functions in
184    /// this module.
185    #[dbg(placeholder = "..")]
186    pub draw_fn: PanelDrawFn,
187    /// The function to be run when the panel is shown.
188    #[dbg(formatter = "fmt_option")]
189    pub show_fn: Option<PanelShowFn>,
190    /// The function to be run when the panel is hidden.
191    #[dbg(formatter = "fmt_option")]
192    pub hide_fn: Option<PanelHideFn>,
193    /// The function to be run before the panel is destroyed. This function
194    /// should run as quickly as possible because the shutdown functions
195    /// for all panels are held to a time limit.
196    #[dbg(formatter = "fmt_option")]
197    pub shutdown: Option<PanelShutdownFn>,
198    /// Information about how to draw the cursor over this panel.
199    #[cfg(feature = "cursor")]
200    pub cursor_info: CursorInfo,
201    /// Information to be shown when `lazybar-msg` sends a "dump" message.
202    pub dump: String,
203}
204
205fn fmt_option<T>(value: &Option<T>) -> &'static str {
206    match value {
207        Some(_) => "Some(..)",
208        None => "None",
209    }
210}
211
212impl PanelDrawInfo {
213    /// Creates a new [`PanelDrawInfo`] from its components.
214    #[must_use]
215    pub const fn new(
216        dims: (i32, i32),
217        dependence: Dependence,
218        draw_fn: PanelDrawFn,
219        show_fn: Option<PanelShowFn>,
220        hide_fn: Option<PanelHideFn>,
221        shutdown: Option<PanelShutdownFn>,
222        #[cfg(feature = "cursor")] cursor_info: CursorInfo,
223        dump: String,
224    ) -> Self {
225        Self {
226            width: dims.0,
227            height: dims.1,
228            dependence,
229            draw_fn,
230            show_fn,
231            hide_fn,
232            shutdown,
233            #[cfg(feature = "cursor")]
234            cursor_info,
235            dump,
236        }
237    }
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
241enum PanelStatus {
242    Shown,
243    ZeroWidth,
244    Dependent(Dependence),
245}
246
247impl BitAnd for PanelStatus {
248    type Output = Self;
249
250    fn bitand(self, rhs: Self) -> Self::Output {
251        if self == Self::Shown && rhs == Self::Shown {
252            Self::Shown
253        } else {
254            Self::ZeroWidth
255        }
256    }
257}
258
259impl From<&Panel> for PanelStatus {
260    fn from(value: &Panel) -> Self {
261        if value.visible {
262            value.draw_info.as_ref().map_or(Self::ZeroWidth, |d| {
263                match (d.dependence, d.width) {
264                    (Dependence::None, 0) => Self::ZeroWidth,
265                    (Dependence::None, _) => Self::Shown,
266                    (dep, _) => Self::Dependent(dep),
267                }
268            })
269        } else {
270            Self::ZeroWidth
271        }
272    }
273}
274
275/// A button that can be linked to an action for a panel
276///
277/// Note: scrolling direction may be incorrect depending on your configuration
278#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
279pub enum MouseButton {
280    /// The left mouse button
281    #[default]
282    Left,
283    /// The middle mouse button
284    Middle,
285    /// The right mouse button
286    Right,
287    /// Scrolling up
288    ScrollUp,
289    /// Scrolling down
290    ScrollDown,
291}
292
293impl MouseButton {
294    fn try_parse(value: u8, reverse: bool) -> Result<Self> {
295        match value {
296            1 => Ok(Self::Left),
297            2 => Ok(Self::Middle),
298            3 => Ok(Self::Right),
299            4 => {
300                if reverse {
301                    Ok(Self::ScrollUp)
302                } else {
303                    Ok(Self::ScrollDown)
304                }
305            }
306            5 => {
307                if reverse {
308                    Ok(Self::ScrollDown)
309                } else {
310                    Ok(Self::ScrollUp)
311                }
312            }
313            _ => Err(anyhow!("X server provided invalid button")),
314        }
315    }
316}
317
318/// A mouse event that can be passed to a panel
319#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
320pub struct MouseEvent {
321    /// The button that was pressed (or scrolled)
322    pub button: MouseButton,
323    /// The x coordinate of the press, relative to the panel
324    pub x: i16,
325    /// The y coordinate of the press, relative to the bar
326    pub y: i16,
327}
328
329/// An event that can be passed to a panel
330#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
331pub enum Event {
332    /// A mouse event
333    Mouse(MouseEvent),
334    /// A message (typically from another process)
335    Action(Option<String>),
336}
337
338/// A panel on the bar
339#[derive(Debug)]
340pub struct Panel {
341    /// How to draw the panel.
342    pub draw_info: Option<PanelDrawInfo>,
343    /// The current x-coordinate of the panel
344    pub x: f64,
345    /// The name of the panel (taken from the name of the toml table that
346    /// defines it)
347    pub name: &'static str,
348    /// Whether the panel is visible. To set this value on startup, see
349    /// [`PanelCommon`][crate::common::PanelCommon].
350    pub visible: bool,
351    endpoint: Option<Arc<Mutex<ChannelEndpoint<Event, EventResponse>>>>,
352}
353
354impl Panel {
355    /// Create a new panel.
356    #[must_use]
357    pub fn new(
358        draw_info: Option<PanelDrawInfo>,
359        name: &'static str,
360        endpoint: Option<ChannelEndpoint<Event, EventResponse>>,
361        visible: bool,
362    ) -> Self {
363        Self {
364            draw_info,
365            x: 0.0,
366            name,
367            visible,
368            endpoint: endpoint.map(|e| Arc::new(Mutex::new(e))),
369        }
370    }
371}
372
373/// The bar itself.
374///
375/// See [`parser::parse`][crate::parser::parse] for configuration details.
376#[allow(dead_code)]
377#[derive(Dbg)]
378pub struct Bar {
379    pub(crate) name: String,
380    position: Position,
381    pub(crate) conn: Arc<XCBConnection>,
382    screen: usize,
383    window: Window,
384    surface: cairo::XCBSurface,
385    pub(crate) cr: Rc<cairo::Context>,
386    width: i32,
387    height: u16,
388    bg: Color,
389    margins: Margins,
390    extents: Extents,
391    reverse_scroll: bool,
392    pub(crate) left_panels: Vec<Panel>,
393    pub(crate) center_panels: Vec<Panel>,
394    pub(crate) right_panels: Vec<Panel>,
395    #[dbg(placeholder = "..")]
396    pub(crate) streams: StreamMap<Alignment, StreamMap<usize, PanelStream>>,
397    pub(crate) ipc: bool,
398    mapped: bool,
399    center_state: CenterState,
400}
401
402impl Bar {
403    /// Create a new bar, typically from information held by a
404    /// [`BarConfig`][crate::BarConfig].
405    pub fn new(
406        name: &str,
407        position: Position,
408        height: u16,
409        transparent: bool,
410        bg: Color,
411        margins: Margins,
412        reverse_scroll: bool,
413        ipc: bool,
414        monitor: Option<String>,
415        #[cfg(feature = "cursor")] cursors: Cursors,
416    ) -> Result<(Self, IpcStream)> {
417        let (conn, screen, window, width, visual, mon) =
418            create_window(position, height, transparent, &bg, monitor)?;
419
420        BAR_INFO
421            .set(BarInfo {
422                window,
423                visual,
424                width,
425                height,
426                transparent,
427                bg: bg.clone(),
428                #[cfg(feature = "cursor")]
429                cursors,
430            })
431            .unwrap();
432
433        let (result, name) = ipc::init(ipc, name);
434        let ipc_stream: Pin<
435            Box<
436                dyn Stream<
437                    Item = std::result::Result<UnixStream, std::io::Error>,
438                >,
439            >,
440        > = match result {
441            Ok(stream) => {
442                log::info!("IPC initialized");
443                stream
444            }
445            Err(e) => {
446                log::info!("IPC disabled due to an error: {e}");
447                Box::pin(tokio_stream::pending())
448            }
449        };
450
451        set_wm_properties(
452            &conn,
453            window,
454            position,
455            width.into(),
456            height.into(),
457            name.as_str(),
458            &mon,
459        );
460        conn.map_window(window)?;
461        let surface =
462            create_surface(window, visual, width.into(), height.into(), &conn)?;
463        let cr = cairo::Context::new(&surface)?;
464        surface.flush();
465        conn.flush()?;
466
467        Ok((
468            Self {
469                name,
470                position,
471                conn: Arc::new(conn),
472                screen,
473                window,
474                surface,
475                cr: Rc::new(cr),
476                width: width.into(),
477                height,
478                bg,
479                margins,
480                extents: Extents {
481                    left: 0.0,
482                    center: ((width / 2).into(), (width / 2).into()),
483                    right: width.into(),
484                },
485                reverse_scroll,
486                left_panels: Vec::new(),
487                center_panels: Vec::new(),
488                right_panels: Vec::new(),
489                streams: StreamMap::new(),
490                ipc,
491                mapped: true,
492                center_state: CenterState::Center,
493            },
494            ipc_stream,
495        ))
496    }
497
498    /// Calls each panel's shutdown function
499    pub fn shutdown(self) {
500        self.left_panels
501            .into_iter()
502            .chain(self.center_panels)
503            .chain(self.right_panels)
504            .filter_map(|panel| panel.draw_info)
505            .filter_map(|draw_info| draw_info.shutdown)
506            .for_each(|shutdown| shutdown());
507    }
508
509    fn apply_dependence(panels: &[Panel]) -> Vec<PanelStatus> {
510        (0..panels.len())
511            .map(|idx| match PanelStatus::from(&panels[idx]) {
512                PanelStatus::Shown => PanelStatus::Shown,
513                PanelStatus::ZeroWidth => PanelStatus::ZeroWidth,
514                PanelStatus::Dependent(Dependence::Left) => panels
515                    .get(idx - 1)
516                    .map_or(PanelStatus::ZeroWidth, PanelStatus::from),
517                PanelStatus::Dependent(Dependence::Right) => panels
518                    .get(idx + 1)
519                    .map_or(PanelStatus::ZeroWidth, PanelStatus::from),
520                PanelStatus::Dependent(Dependence::Both) => {
521                    panels
522                        .get(idx - 1)
523                        .map_or(PanelStatus::ZeroWidth, PanelStatus::from)
524                        & panels
525                            .get(idx + 1)
526                            .map_or(PanelStatus::ZeroWidth, PanelStatus::from)
527                }
528                PanelStatus::Dependent(Dependence::None) => unreachable!(),
529            })
530            .collect()
531    }
532
533    fn show_panels(&self) {
534        self.left_panels
535            .iter()
536            .chain(self.center_panels.iter())
537            .chain(self.right_panels.iter())
538            .filter_map(|p| p.draw_info.as_ref())
539            .filter_map(|d| d.show_fn.as_ref())
540            .for_each(|f| {
541                if let Err(e) = f() {
542                    log::warn!("showing panel produced an error: {e}");
543                }
544            });
545    }
546
547    fn hide_panels(&self) {
548        self.left_panels
549            .iter()
550            .chain(self.center_panels.iter())
551            .chain(self.right_panels.iter())
552            .filter_map(|p| p.draw_info.as_ref())
553            .filter_map(|d| d.hide_fn.as_ref())
554            .for_each(|f| {
555                if let Err(e) = f() {
556                    log::warn!("hiding panel produced an error: {e}");
557                }
558            });
559    }
560
561    /// Handle an event from the X server.
562    pub fn process_event(&mut self, event: &protocol::Event) -> Result<()> {
563        match event {
564            protocol::Event::Expose(_) => {
565                log::info!(
566                    "Received expose event from X server; redrawing entire bar"
567                );
568                self.redraw_bar()
569            }
570            protocol::Event::ButtonPress(event) => match event.detail {
571                button @ 1..=5 => {
572                    let (x, y) = if event.same_screen {
573                        (event.event_x, event.event_y)
574                    } else {
575                        (event.root_x, event.root_y)
576                    };
577
578                    let panel = self
579                        .left_panels
580                        .iter()
581                        .chain(self.center_panels.iter())
582                        .chain(self.right_panels.iter())
583                        .filter(|p| p.draw_info.is_some())
584                        .find(|p| {
585                            p.x <= x as f64
586                                && p.x
587                                    + p.draw_info.as_ref().unwrap().width as f64
588                                    >= x as f64
589                        });
590
591                    if let Some(p) = panel {
592                        if let Some(e) = &p.endpoint {
593                            let e = e.lock().unwrap();
594                            e.send.send(Event::Mouse(MouseEvent {
595                                button: MouseButton::try_parse(
596                                    button,
597                                    self.reverse_scroll,
598                                )
599                                // this can never fail due to match arm
600                                .unwrap(),
601                                x: x - p.x as i16,
602                                y,
603                            }))?;
604                        }
605                    }
606                    Ok(())
607                }
608                _ => Ok(()),
609            },
610            #[cfg(feature = "cursor")]
611            protocol::Event::MotionNotify(event) => {
612                let (x, y) = if event.same_screen {
613                    (event.event_x, event.event_y)
614                } else {
615                    (event.root_x, event.root_y)
616                };
617
618                let panel = self
619                    .left_panels
620                    .iter()
621                    .chain(self.center_panels.iter())
622                    .chain(self.right_panels.iter())
623                    .filter(|p| p.draw_info.is_some())
624                    .find(|p| {
625                        p.x <= x as f64
626                            && p.x + p.draw_info.as_ref().unwrap().width as f64
627                                >= x as f64
628                    });
629
630                if let Some(panel) = panel {
631                    if let Some(ref draw_info) = panel.draw_info {
632                        if let Ok(cursor) =
633                            draw_info.cursor_info.get(MouseEvent {
634                                button: MouseButton::Left,
635                                x: x - panel.x as i16,
636                                y,
637                            })
638                        {
639                            set_cursor(
640                                self.conn.as_ref(),
641                                self.screen,
642                                cursor,
643                                self.window,
644                            )?;
645                        } else {
646                            set_cursor(
647                                self.conn.as_ref(),
648                                self.screen,
649                                Cursor::Default,
650                                self.window,
651                            )?;
652                        }
653                    } else {
654                        set_cursor(
655                            self.conn.as_ref(),
656                            self.screen,
657                            Cursor::Default,
658                            self.window,
659                        )?;
660                    }
661                } else {
662                    set_cursor(
663                        self.conn.as_ref(),
664                        self.screen,
665                        Cursor::Default,
666                        self.window,
667                    )?;
668                }
669
670                Ok(())
671            }
672            _ => Ok(()),
673        }
674    }
675
676    fn handle_ipc_event(&mut self, message: &str) -> Result<bool> {
677        match message {
678            "quit" => Ok(true),
679            "show" => {
680                self.mapped = true;
681                self.conn.map_window(self.window)?;
682                self.show_panels();
683                Ok(false)
684            }
685            "hide" => {
686                self.mapped = true;
687                self.conn.unmap_window(self.window)?;
688                self.hide_panels();
689                Ok(false)
690            }
691            "toggle" => {
692                if self.mapped {
693                    self.handle_ipc_event("hide")
694                } else {
695                    self.handle_ipc_event("show")
696                }
697            }
698            _ => Ok(false),
699        }
700    }
701
702    fn handle_panel_event(
703        &mut self,
704        message: &str,
705    ) -> Result<(bool, Option<String>)> {
706        if let Some(caps) = REGEX.captures(message) {
707            let region = &caps["region"];
708            let idx = caps["idx"].parse::<usize>()?;
709
710            if let Some(target) = match region {
711                "l" => self.left_panels.get_mut(idx),
712                "c" => self.center_panels.get_mut(idx),
713                "r" => self.right_panels.get_mut(idx),
714                _ => unreachable!(),
715            } {
716                match &caps["message"] {
717                    "show" | "toggle" if !target.visible => {
718                        if let Some(ref draw_info) = target.draw_info {
719                            if let Some(ref f) = draw_info.show_fn {
720                                f()?;
721                            }
722                        }
723                        target.visible = true;
724                    }
725                    "hide" | "toggle" if target.visible => {
726                        if let Some(ref draw_info) = target.draw_info {
727                            if let Some(ref f) = draw_info.hide_fn {
728                                f()?;
729                            }
730                        }
731                        target.visible = false;
732                    }
733                    "dump" => {
734                        if let Some(ref draw_info) = target.draw_info {
735                            return Ok((false, Some(draw_info.dump.clone())));
736                        }
737                    }
738                    message => {
739                        return Err(anyhow!(
740                            "Unknown or invalid message {message}"
741                        ))
742                    }
743                }
744
745                match region {
746                    "l" => self.redraw_left(),
747                    "c" => self.redraw_center_right(true),
748                    "r" => self.redraw_right(true, None),
749                    _ => unreachable!(),
750                }?;
751            }
752        }
753        Ok((false, None))
754    }
755
756    /// Sends a message to the appropriate panel.
757    pub fn send_message(
758        &mut self,
759        message: &str,
760        ipc_set: &mut JoinSet<Result<()>>,
761        ipc_send: UnboundedSender<EventResponse>,
762    ) -> Result<bool> {
763        if let Some(stripped) = message.strip_prefix('#') {
764            let (exit, message) = self.handle_panel_event(stripped)?;
765            ipc_send.send(EventResponse::Ok(message))?;
766            return Ok(exit);
767        }
768
769        let (dest, message) = match message.split_once('.') {
770            Some((panel, message)) => (Some(panel), message),
771            None => (None, message),
772        };
773
774        if let Some(panel) = dest {
775            let mut panels = self
776                .left_panels
777                .iter()
778                .chain(&self.center_panels)
779                .chain(&self.right_panels)
780                .filter(|p| p.name == panel);
781
782            let target = panels.next();
783            let (endpoint, message) = match if target.is_none() {
784                Err(anyhow!("No panel with name {panel} was found"))
785            } else if panels.next().is_some() {
786                Err(anyhow!(
787                    "This panel has multiple instances and cannot be messaged"
788                ))
789            } else if let Some(ref endpoint) = target.unwrap().endpoint {
790                Ok((endpoint.clone(), message.to_string()))
791            } else {
792                Err(anyhow!(
793                    "The target panel has no associated sender and cannot be \
794                     messaged"
795                ))
796            } {
797                Ok(r) => r,
798                Err(e) => {
799                    let err = e.to_string();
800                    ipc_set.spawn_blocking(move || {
801                        Ok(ipc_send.send(EventResponse::Err(err))?)
802                    });
803                    return Err(e);
804                }
805            };
806
807            ipc_set.spawn_blocking(move || {
808                let send = endpoint.lock().unwrap().send.clone();
809                let response =
810                    if let Err(e) = send.send(Event::Action(Some(message))) {
811                        EventResponse::Err(e.to_string())
812                    } else {
813                        endpoint
814                            .lock()
815                            .unwrap()
816                            .recv
817                            .blocking_recv()
818                            .unwrap_or(EventResponse::Ok(None))
819                    };
820                log::trace!("response received");
821
822                ipc_send.send(response)?;
823                log::trace!("response sent");
824
825                Ok(())
826            });
827
828            log::trace!("task spawned");
829
830            Ok(false)
831        } else {
832            self.handle_ipc_event(message)
833        }
834    }
835
836    fn redraw_background(&self, scope: &Region) -> Result<()> {
837        self.cr.save()?;
838        self.cr.set_operator(cairo::Operator::Source);
839        self.cr.set_source_rgba(
840            self.bg.r.into(),
841            self.bg.g.into(),
842            self.bg.b.into(),
843            self.bg.a.into(),
844        );
845        match scope {
846            Region::Left => self.cr.rectangle(
847                0.0,
848                0.0,
849                self.extents.left + self.margins.internal,
850                f64::from(self.height),
851            ),
852            Region::CenterRight => self.cr.rectangle(
853                self.extents.center.0 - self.margins.internal,
854                0.0,
855                f64::from(self.width)
856                    - (self.extents.center.0 - self.margins.internal),
857                f64::from(self.height),
858            ),
859            Region::Right => self.cr.rectangle(
860                self.extents.right - self.margins.internal,
861                0.0,
862                f64::from(self.width)
863                    - (self.extents.right - self.margins.internal),
864                f64::from(self.height),
865            ),
866            Region::All => {
867                self.cr.rectangle(
868                    0.0,
869                    0.0,
870                    f64::from(self.width),
871                    f64::from(self.height),
872                );
873            }
874            Region::Custom { start_x, end_x } => {
875                self.cr.rectangle(
876                    *start_x,
877                    0.0,
878                    end_x - start_x,
879                    f64::from(self.height),
880                );
881            }
882        }
883        self.cr.fill()?;
884        self.cr.restore()?;
885
886        Ok(())
887    }
888
889    /// Handle a change in the content of a panel.
890    pub fn update_panel(
891        &mut self,
892        alignment: Alignment,
893        idx: usize,
894        draw_info: PanelDrawInfo,
895    ) -> Result<()> {
896        let new_width = f64::from(draw_info.width);
897        match alignment {
898            Alignment::Left => {
899                let cur_width = f64::from(
900                    self.left_panels
901                        .get(idx)
902                        .expect("one or more panels have vanished")
903                        .draw_info
904                        .as_ref()
905                        .map_or(0, |i| i.width),
906                );
907
908                self.left_panels
909                    .get_mut(idx)
910                    .expect("one or more panels have vanished")
911                    .draw_info = Some(draw_info);
912
913                if (new_width - cur_width).abs() < f64::EPSILON {
914                    self.redraw_one(alignment, idx)?;
915                } else if new_width - cur_width
916                    + self.extents.left
917                    + self.margins.internal
918                    < self.extents.center.0
919                    && (self.center_state == CenterState::Center
920                        || self.center_state == CenterState::Left)
921                {
922                    self.redraw_left()?;
923                } else {
924                    self.redraw_bar()?;
925                }
926
927                Ok(())
928            }
929            Alignment::Center => {
930                let cur_width = f64::from(
931                    self.center_panels
932                        .get(idx)
933                        .expect("one or more panels have vanished")
934                        .draw_info
935                        .as_ref()
936                        .map_or(0, |i| i.width),
937                );
938
939                self.center_panels
940                    .get_mut(idx)
941                    .expect("one or more panels have vanished")
942                    .draw_info = Some(draw_info);
943
944                if (new_width - cur_width).abs() < f64::EPSILON {
945                    self.redraw_one(alignment, idx)?;
946                } else {
947                    self.redraw_bar()?;
948                }
949
950                Ok(())
951            }
952            Alignment::Right => {
953                let cur_width = f64::from(
954                    self.right_panels
955                        .get(idx)
956                        .expect("one or more panels have vanished")
957                        .draw_info
958                        .as_ref()
959                        .map_or(0, |i| i.width),
960                );
961
962                self.right_panels
963                    .get_mut(idx)
964                    .expect("one or more panels have vanished")
965                    .draw_info = Some(draw_info);
966
967                if (new_width - cur_width).abs() < f64::EPSILON {
968                    self.redraw_one(alignment, idx)?;
969                } else if self.extents.right
970                    - new_width
971                    - cur_width
972                    - self.margins.internal
973                    > self.extents.center.1
974                {
975                    self.redraw_right(true, None)?;
976                } else if (self.extents.right
977                    - self.extents.center.1
978                    - self.margins.internal)
979                    + (self.extents.center.0
980                        - self.extents.left
981                        - self.margins.internal)
982                    > new_width - cur_width
983                {
984                    self.extents.right += new_width - cur_width;
985                    self.redraw_center_right(true)?;
986                } else {
987                    self.redraw_bar()?;
988                }
989
990                self.surface.flush();
991                self.conn.flush()?;
992
993                Ok(())
994            }
995        }
996    }
997
998    fn redraw_one(&self, alignment: Alignment, idx: usize) -> Result<()> {
999        match alignment {
1000            Alignment::Left => {
1001                self.cr.save()?;
1002
1003                let panel = self
1004                    .left_panels
1005                    .get(idx)
1006                    .expect("one or more panels have vanished");
1007                if let Some(draw_info) = &panel.draw_info {
1008                    self.redraw_background(&Region::Custom {
1009                        start_x: panel.x,
1010                        end_x: panel.x + f64::from(draw_info.width),
1011                    })?;
1012                    self.cr.translate(panel.x, 0.0);
1013                    (draw_info.draw_fn)(&self.cr, panel.x)?;
1014                }
1015
1016                self.surface.flush();
1017                self.conn.flush()?;
1018                self.cr.restore()?;
1019
1020                Ok(())
1021            }
1022            Alignment::Center => {
1023                self.cr.save()?;
1024                let panel = self
1025                    .center_panels
1026                    .get(idx)
1027                    .expect("one or more panels have vanished");
1028
1029                if let Some(draw_info) = &self
1030                    .center_panels
1031                    .get(idx)
1032                    .expect("one or more panels have vanished")
1033                    .draw_info
1034                {
1035                    self.redraw_background(&Region::Custom {
1036                        start_x: panel.x,
1037                        end_x: panel.x + f64::from(draw_info.width),
1038                    })?;
1039                    self.cr.translate(panel.x, 0.0);
1040                    (draw_info.draw_fn)(&self.cr, panel.x)?;
1041                }
1042
1043                self.surface.flush();
1044                self.conn.flush()?;
1045                self.cr.restore()?;
1046
1047                Ok(())
1048            }
1049            Alignment::Right => {
1050                self.cr.save()?;
1051                let panel = self
1052                    .right_panels
1053                    .get(idx)
1054                    .expect("one or more panels have vanished");
1055
1056                if let Some(draw_info) = &self
1057                    .right_panels
1058                    .get(idx)
1059                    .expect("one or more panels have vanished")
1060                    .draw_info
1061                {
1062                    self.redraw_background(&Region::Custom {
1063                        start_x: panel.x,
1064                        end_x: panel.x + f64::from(draw_info.width),
1065                    })?;
1066                    self.cr.translate(panel.x, 0.0);
1067                    (draw_info.draw_fn)(&self.cr, panel.x)?;
1068                }
1069
1070                self.surface.flush();
1071                self.conn.flush()?;
1072                self.cr.restore()?;
1073
1074                Ok(())
1075            }
1076        }
1077    }
1078
1079    /// Redraw the entire bar, either as the result of an expose event or
1080    /// because the width of a panel changed.
1081    ///
1082    /// Note: this function is not called for every panel update. If the width
1083    /// doesn't change, only one panel is redrawn, and there are a number of
1084    /// other cases in which we can redraw only the left or right side. See
1085    /// [`Bar::update_panel`] for specifics.
1086    pub fn redraw_bar(&mut self) -> Result<()> {
1087        log::info!("Redrawing entire bar");
1088
1089        self.redraw_background(&Region::All)?;
1090
1091        self.redraw_left()?;
1092        self.redraw_center_right(false)?;
1093
1094        Ok(())
1095    }
1096
1097    fn redraw_left(&mut self) -> Result<()> {
1098        log::info!("Redrawing left");
1099
1100        self.redraw_background(&Region::Left)?;
1101
1102        self.extents.left = self.margins.left;
1103
1104        let statuses = Self::apply_dependence(self.left_panels.as_slice());
1105
1106        for panel in self
1107            .left_panels
1108            .iter_mut()
1109            .enumerate()
1110            .filter(|(idx, _)| {
1111                statuses.get(*idx).unwrap() == &PanelStatus::Shown
1112            })
1113            .map(|(_, panel)| panel)
1114        {
1115            if let Some(draw_info) = &panel.draw_info {
1116                self.cr.save()?;
1117                let x = self.extents.left;
1118                panel.x = x;
1119                self.cr.translate(x, 0.0);
1120                (draw_info.draw_fn)(&self.cr, x)?;
1121                self.extents.left += f64::from(draw_info.width);
1122                self.cr.restore()?;
1123            }
1124        }
1125
1126        self.surface.flush();
1127        self.conn.flush()?;
1128
1129        Ok(())
1130    }
1131
1132    fn redraw_center_right(&mut self, standalone: bool) -> Result<()> {
1133        log::info!("Redrawing center panels");
1134        if standalone {
1135            self.redraw_background(&Region::CenterRight)?;
1136        }
1137
1138        let center_statuses =
1139            Self::apply_dependence(self.center_panels.as_slice());
1140
1141        let center_panels = self
1142            .center_panels
1143            .iter_mut()
1144            .enumerate()
1145            .filter(|(idx, _)| {
1146                center_statuses.get(*idx).unwrap() == &PanelStatus::Shown
1147            })
1148            .map(|(_, panel)| panel)
1149            .collect::<Vec<_>>();
1150
1151        let right_statuses =
1152            Self::apply_dependence(self.right_panels.as_slice());
1153
1154        let right_panels = self
1155            .right_panels
1156            .iter()
1157            .enumerate()
1158            .filter(|(idx, _)| {
1159                right_statuses.get(*idx).unwrap() == &PanelStatus::Shown
1160            })
1161            .map(|(_, panel)| panel);
1162
1163        let center_width = f64::from(
1164            center_panels
1165                .iter()
1166                .filter_map(|p| p.draw_info.as_ref().map(|i| i.width))
1167                .sum::<i32>(),
1168        );
1169
1170        self.extents.right = f64::from(
1171            self.width
1172                - right_panels
1173                    .filter_map(|p| p.draw_info.as_ref().map(|i| i.width))
1174                    .sum::<i32>(),
1175        ) - self.margins.internal;
1176
1177        if center_width
1178            > 2.0f64.mul_add(
1179                -self.margins.internal,
1180                self.extents.right - self.extents.left,
1181            )
1182        {
1183            self.extents.center.0 = self.margins.internal + self.extents.left;
1184            self.extents.center.1 = self.margins.internal + self.extents.left;
1185            self.center_state = CenterState::Unknown;
1186        } else if center_width / 2.0
1187            > self.extents.right
1188                - f64::from(self.width / 2)
1189                - self.margins.internal
1190        {
1191            self.extents.center.0 =
1192                self.extents.right - center_width - self.margins.internal;
1193            self.extents.center.1 =
1194                self.extents.right - center_width - self.margins.internal;
1195            self.center_state = CenterState::Left;
1196        } else if center_width / 2.0
1197            > f64::from(self.width / 2)
1198                - self.extents.left
1199                - self.margins.internal
1200        {
1201            self.extents.center.0 = self.extents.left + self.margins.internal;
1202            self.extents.center.1 = self.extents.left + self.margins.internal;
1203            self.center_state = CenterState::Right;
1204        } else {
1205            self.extents.center.0 =
1206                f64::from(self.width / 2) - center_width / 2.0;
1207            self.extents.center.1 =
1208                f64::from(self.width / 2) - center_width / 2.0;
1209            self.center_state = CenterState::Center;
1210        }
1211
1212        for panel in center_panels {
1213            if let Some(draw_info) = &panel.draw_info {
1214                self.cr.save()?;
1215                let x = self.extents.center.1;
1216                panel.x = x;
1217                self.cr.translate(x, 0.0);
1218                (draw_info.draw_fn)(&self.cr, x)?;
1219                self.extents.center.1 += f64::from(draw_info.width);
1220                self.cr.restore()?;
1221            }
1222        }
1223
1224        self.redraw_right(standalone, Some(right_statuses))?;
1225
1226        self.surface.flush();
1227        self.conn.flush()?;
1228
1229        Ok(())
1230    }
1231
1232    fn redraw_right(
1233        &mut self,
1234        standalone: bool,
1235        statuses: Option<Vec<PanelStatus>>,
1236    ) -> Result<()> {
1237        log::info!("Redrawing right panels");
1238
1239        if standalone {
1240            self.redraw_background(&Region::Right)?;
1241        }
1242
1243        let statuses = statuses.unwrap_or_else(|| {
1244            Self::apply_dependence(self.right_panels.as_slice())
1245        });
1246
1247        let total_width = f64::from(
1248            self.right_panels
1249                .iter()
1250                .enumerate()
1251                .filter(|(idx, _)| {
1252                    statuses.get(*idx).unwrap() == &PanelStatus::Shown
1253                })
1254                .map(|(_, panel)| panel)
1255                .filter_map(|p| p.draw_info.as_ref().map(|i| i.width))
1256                .sum::<i32>(),
1257        ) + self.margins.right;
1258
1259        if total_width > f64::from(self.width) - self.extents.center.1 {
1260            self.extents.right = self.extents.center.1 + self.margins.internal;
1261        } else {
1262            self.extents.right = f64::from(self.width) - total_width;
1263        }
1264
1265        let mut temp = self.extents.right;
1266
1267        for panel in self
1268            .right_panels
1269            .iter_mut()
1270            .enumerate()
1271            .filter(|(idx, _)| {
1272                statuses.get(*idx).unwrap() == &PanelStatus::Shown
1273            })
1274            .map(|(_, panel)| panel)
1275        {
1276            if let Some(draw_info) = &panel.draw_info {
1277                self.cr.save()?;
1278                let x = temp;
1279                panel.x = x;
1280                self.cr.translate(x, 0.0);
1281                (draw_info.draw_fn)(&self.cr, x)?;
1282                temp += f64::from(draw_info.width);
1283                self.cr.restore()?;
1284            }
1285        }
1286
1287        self.surface.flush();
1288        self.conn.flush()?;
1289
1290        Ok(())
1291    }
1292}