lazybar_core/
parser.rs

1use std::{collections::HashMap, path::Path};
2
3use anyhow::{Context, Result, anyhow};
4use config::{Config, File, FileFormat, Value};
5use futures::executor;
6use lazy_static::lazy_static;
7use tokio::sync::OnceCell;
8
9#[cfg(feature = "cursor")]
10use crate::bar::Cursors;
11#[cfg(feature = "battery")]
12use crate::panels::Battery;
13#[cfg(feature = "clock")]
14use crate::panels::Clock;
15#[cfg(feature = "cpu")]
16use crate::panels::Cpu;
17#[cfg(feature = "custom")]
18use crate::panels::Custom;
19#[cfg(feature = "github")]
20use crate::panels::Github;
21#[cfg(feature = "inotify")]
22use crate::panels::Inotify;
23#[cfg(feature = "memory")]
24use crate::panels::Memory;
25#[cfg(feature = "mpd")]
26use crate::panels::Mpd;
27#[cfg(feature = "network")]
28use crate::panels::Network;
29#[cfg(feature = "ping")]
30use crate::panels::Ping;
31#[cfg(feature = "pulseaudio")]
32use crate::panels::Pulseaudio;
33#[cfg(feature = "separator")]
34use crate::panels::Separator;
35#[cfg(feature = "storage")]
36use crate::panels::Storage;
37#[cfg(feature = "systray")]
38use crate::panels::Systray;
39#[cfg(feature = "temp")]
40use crate::panels::Temp;
41#[cfg(feature = "xwindow")]
42use crate::panels::XWindow;
43#[cfg(feature = "xworkspaces")]
44use crate::panels::XWorkspaces;
45use crate::{
46    Alignment, Attrs, BarConfig, Margins, PanelConfig, Position, cleanup,
47    get_table_from_config, remove_string_from_config,
48};
49
50lazy_static! {
51    /// The `attrs` table from the global [`Config`].
52    ///
53    /// This cell is guaranteed to be initialized during the execution of all
54    /// [`PanelConfig::parse`] functions.
55    pub static ref ATTRS: OnceCell<HashMap<String, Value>> =
56        OnceCell::new();
57    /// The `ramps` table from the global [`Config`].
58    ///
59    /// This cell is guaranteed to be initialized during the execution of all
60    /// [`PanelConfig::parse`] functions.
61    pub static ref RAMPS: OnceCell<HashMap<String, Value>> =
62        OnceCell::new();
63    /// The `bgs` table from the global [`Config`].
64    ///
65    /// This cell is guaranteed to be initialized during the execution of all
66    /// [`PanelConfig::parse`] functions.
67    pub static ref BGS: OnceCell<HashMap<String, Value>> =
68        OnceCell::new();
69    /// The `consts` table from the global [`Config`].
70    ///
71    /// This cell is guaranteed to be initialized during the execution of all
72    /// [`PanelConfig::parse`] functions.
73    pub static ref CONSTS: OnceCell<HashMap<String, Value>> =
74        OnceCell::new();
75    /// The `images` table from the global [`Config`].
76    ///
77    /// This cell is guaranteed to be initialized during the execution of all
78    /// [`PanelConfig::parse`] functions.
79    pub static ref IMAGES: OnceCell<HashMap<String, Value>> =
80        OnceCell::new();
81    /// The `highlights` table from the global [`Config`].
82    ///
83    /// This cell is guaranteed to be initialized during the execution of all
84    /// [`PanelConfig::parse`] functions.
85    pub static ref HIGHLIGHTS: OnceCell<HashMap<String, Value>> =
86        OnceCell::new();
87}
88
89/// Parses a bar with a given name from the global [`Config`]
90///
91/// Configuration options:
92/// - `position`: `top` or `bottom`
93/// - `height`: the height in pixels of the bar
94/// - `transparent`: `true` or `false`. If `bg` isn't transparent, the bar won't
95///   be either.
96/// - `bg`: the background color. See [`csscolorparser::parse`].
97/// - `margins`: See [`Margins`]. Keys are `margin_left`, `margin_right`, and
98///   `margin_internal`.
99/// - `reverse_scroll`: `true` or `false`. Whether to reverse scrolling.
100/// - `ipc`: `true` or `false`. Whether to enable inter-process communication.
101/// - `default_attrs`: The default attributes for panels. See [`Attrs::parse`]
102///   for more parsing details.
103/// - `monitor`: The name of the monitor on which the bar should display. You
104///   can use `xrandr --query` to find monitor names in most cases. However,
105///   discovering all monitors is a complicated problem and beyond the scope of
106///   this documentation.
107/// - `cursor_{default, click, scroll}`: The X11 cursor names to use. See
108///   /usr/include/X11/cursorfont.h for some options.
109pub fn parse(bar_name: &str, config: &Path) -> Result<BarConfig> {
110    let config = Config::builder()
111        .add_source(
112            File::new(
113                config.to_str().unwrap_or_else(|| {
114                    log::error!("Invalid config path");
115                    executor::block_on(cleanup::exit(None, false, 101))
116                }),
117                FileFormat::Toml,
118            )
119            .required(true),
120        )
121        .build()
122        .unwrap_or_else(|e| {
123            log::error!("Error parsing config file: {e}");
124            executor::block_on(cleanup::exit(None, false, 101))
125        });
126    log::info!("Read config file");
127
128    ATTRS
129        .set(config.get_table("attrs").unwrap_or_default())
130        .unwrap();
131
132    RAMPS
133        .set(config.get_table("ramps").unwrap_or_default())
134        .unwrap();
135
136    BGS.set(config.get_table("bgs").unwrap_or_default())
137        .unwrap();
138
139    CONSTS
140        .set(config.get_table("consts").unwrap_or_default())
141        .unwrap();
142
143    IMAGES
144        .set(config.get_table("images").unwrap_or_default())
145        .unwrap();
146
147    HIGHLIGHTS
148        .set(config.get_table("highlights").unwrap_or_default())
149        .unwrap();
150
151    let mut bars_table = config
152        .get_table("bars")
153        .context("`bars` doesn't exist or isn't a table")?;
154    log::trace!("got bars table from config");
155
156    let mut bar_table = bars_table
157        .remove(bar_name)
158        .with_context(|| format!("`{bar_name}` doesn't exist"))?
159        .into_table()
160        .with_context(|| format!("`{bar_name}` isn't a table"))?;
161    log::trace!("got bar table {bar_name} from config");
162
163    let bar = BarConfig::builder()
164        .name(bar_name.to_owned())
165        .position({
166            let val = match bar_table
167                .remove("position")
168                .unwrap_or_default()
169                .into_string()
170                .unwrap_or_default()
171                .as_str()
172            {
173                "top" => Position::Top,
174                "bottom" => Position::Bottom,
175                _ => Position::Top,
176            };
177            log::trace!("got bar position: {val:?}");
178            val
179        })
180        .height({
181            let val = bar_table
182                .remove("height")
183                .unwrap_or_default()
184                .into_uint()
185                .unwrap_or(24) as u16;
186            log::trace!("got bar height: {val}");
187            val
188        })
189        .transparent({
190            let val = bar_table
191                .remove("transparent")
192                .unwrap_or_default()
193                .into_bool()
194                .unwrap_or_default();
195            log::trace!("got bar transparency: {val}");
196            val
197        })
198        .bg({
199            let val = bar_table
200                .remove("bg")
201                .unwrap_or_default()
202                .into_string()
203                .unwrap_or_default()
204                .parse()
205                .unwrap_or_default();
206            log::trace!("got bar background: {val}");
207            val
208        })
209        .margins({
210            let val = Margins::new(
211                bar_table
212                    .remove("margin_left")
213                    .unwrap_or_default()
214                    .into_float()
215                    .unwrap_or_default(),
216                bar_table
217                    .remove("margin_internal")
218                    .unwrap_or_default()
219                    .into_float()
220                    .unwrap_or_default(),
221                bar_table
222                    .remove("margin_right")
223                    .unwrap_or_default()
224                    .into_float()
225                    .unwrap_or_default(),
226            );
227            log::trace!("got bar margins: {val:?}");
228            val
229        })
230        .reverse_scroll({
231            let val = bar_table
232                .remove("reverse_scroll")
233                .unwrap_or_default()
234                .into_bool()
235                .unwrap_or_default();
236            log::trace!("got bar reverse scroll: {val}");
237            val
238        })
239        .ipc({
240            let val = bar_table
241                .remove("ipc")
242                .unwrap_or_default()
243                .into_bool()
244                .unwrap_or_default();
245            log::trace!("got bar ipc: {val}");
246            val
247        })
248        .attrs({
249            let val =
250                remove_string_from_config("default_attrs", &mut bar_table)
251                    .map_or_else(Attrs::default, Attrs::parse_global);
252            log::trace!("got bar attrs: {val:?}");
253            val
254        })
255        .monitor({
256            let val = remove_string_from_config("monitor", &mut bar_table);
257            log::trace!("got bar monitor: {val:?}");
258            val
259        })
260        .left(Vec::new())
261        .center(Vec::new())
262        .right(Vec::new());
263
264    #[cfg(feature = "cursor")]
265    let bar = bar.cursors({
266        let val = Cursors {
267            default: remove_string_from_config(
268                "cursor_default",
269                &mut bar_table,
270            )
271            .map::<&'static str, _>(|s| s.leak())
272            .unwrap_or("default"),
273            click: remove_string_from_config("cursor_click", &mut bar_table)
274                .map::<&'static str, _>(|s| s.leak())
275                .unwrap_or("hand2"),
276            scroll: remove_string_from_config("cursor_scroll", &mut bar_table)
277                .map::<&'static str, _>(|s| s.leak())
278                .unwrap_or("sb_v_double_arrow"),
279        };
280        val
281    });
282
283    let mut bar = bar.build()?;
284
285    let mut left_final = Vec::new();
286    let mut center_final = Vec::new();
287    let mut right_final = Vec::new();
288
289    let panels_left = bar_table.remove("panels_left");
290    if let Some(pl) = panels_left {
291        let panel_list =
292            pl.into_array().context("`panels_left` isn't an array")?;
293        for p in panel_list {
294            if let Ok(name) = p.clone().into_string() {
295                log::debug!("Adding left panel {name}");
296                left_final.push(name);
297            } else {
298                log::warn!("Ignoring non-string value {p:?} in `panels_left`");
299            }
300        }
301    }
302
303    let panels_center = bar_table.remove("panels_center");
304    if let Some(pc) = panels_center {
305        let panel_list =
306            pc.into_array().context("`panels_center` isn't an array")?;
307        for p in panel_list {
308            if let Ok(name) = p.clone().into_string() {
309                log::debug!("Adding center panel {name}");
310                center_final.push(name);
311            } else {
312                log::warn!(
313                    "Ignoring non-string value {p:?} in `panels_center`"
314                );
315            }
316        }
317    }
318
319    let panels_right = bar_table.remove("panels_right");
320    if let Some(pr) = panels_right {
321        let panel_list =
322            pr.into_array().context("`panels_right` isn't an array")?;
323        for p in panel_list {
324            if let Ok(name) = p.clone().into_string() {
325                log::debug!("Adding right panel {name}");
326                right_final.push(name);
327            } else {
328                log::warn!("Ignoring non-string value {p:?} in `panels_right`");
329            }
330        }
331    }
332
333    let panels_table = config
334        .get_table("panels")
335        .context("`panels` doesn't exist or isn't a table")?;
336    log::trace!("got panels table");
337
338    // leak panel names so that we can use &'static str instead of String
339    left_final
340        .into_iter()
341        .filter_map(|p| parse_panel(p.leak(), &panels_table, &config))
342        .for_each(|p| bar.add_panel(p, Alignment::Left));
343    log::debug!("left panels added");
344    center_final
345        .into_iter()
346        .filter_map(|p| parse_panel(p.leak(), &panels_table, &config))
347        .for_each(|p| bar.add_panel(p, Alignment::Center));
348    log::debug!("center panels added");
349    right_final
350        .into_iter()
351        .filter_map(|p| parse_panel(p.leak(), &panels_table, &config))
352        .for_each(|p| bar.add_panel(p, Alignment::Right));
353    log::debug!("right panels added");
354
355    Ok(bar)
356}
357
358fn parse_panel(
359    p: &'static str,
360    panels_table: &HashMap<String, Value>,
361    config: &Config,
362) -> Option<Box<dyn PanelConfig>> {
363    if let Some(mut table) = get_table_from_config(p, panels_table) {
364        if let Some(s) = remove_string_from_config("type", &mut table) {
365            log::debug!("parsing {s} panel");
366            return match s.as_str() {
367                #[cfg(feature = "battery")]
368                "battery" => {
369                    Battery::parse(p, &mut table, config)
370                        .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
371                }
372                #[cfg(feature = "clock")]
373                "clock" => {
374                    Clock::parse(p, &mut table, config)
375                        .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
376                }
377                #[cfg(feature = "cpu")]
378                "cpu" => Cpu::parse(p, &mut table, config)
379                    .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p)),
380                #[cfg(feature = "custom")]
381                "custom" => {
382                    Custom::parse(p, &mut table, config)
383                        .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
384                }
385                #[cfg(feature = "github")]
386                "github" => {
387                    Github::parse(p, &mut table, config)
388                        .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
389                }
390                #[cfg(feature = "inotify")]
391                "inotify" => {
392                    Inotify::parse(p, &mut table, config)
393                        .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
394                }
395                #[cfg(feature = "memory")]
396                "memory" => {
397                    Memory::parse(p, &mut table, config)
398                        .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
399                }
400                #[cfg(feature = "mpd")]
401                "mpd" => Mpd::parse(p, &mut table, config)
402                    .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p)),
403                #[cfg(feature = "network")]
404                "network" => {
405                    Network::parse(p, &mut table, config)
406                        .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
407                }
408                #[cfg(feature = "ping")]
409                "ping" => {
410                    Ping::parse(p, &mut table, config)
411                        .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
412                }
413                #[cfg(feature = "pulseaudio")]
414                "pulseaudio" => Pulseaudio::parse(p, &mut table, config)
415                    .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p)),
416                #[cfg(feature = "separator")]
417                "separator" => Separator::parse(p, &mut table, config)
418                    .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p)),
419                #[cfg(feature = "storage")]
420                "storage" => {
421                    Storage::parse(p, &mut table, config)
422                        .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
423                }
424                #[cfg(feature = "systray")]
425                "systray" => {
426                    Systray::parse(p, &mut table, config)
427                        .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
428                }
429                #[cfg(feature = "temp")]
430                "temp" => {
431                    Temp::parse(p, &mut table, config)
432                        .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
433                }
434                #[cfg(feature = "xwindow")]
435                "xwindow" => {
436                    XWindow::parse(p, &mut table, config)
437                        .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
438                }
439                #[cfg(feature = "xworkspaces")]
440                "xworkspaces" => XWorkspaces::parse(p, &mut table, config)
441                    .map::<Box<dyn PanelConfig>, _>(|p| Box::new(p)),
442                s => Err(anyhow!("Unknown panel type {s}")),
443            }
444            .map_err(|e| {
445                log::error!(
446                    "Error encountered while parsing panel {p} (of type {s}): \
447                     {e}"
448                );
449                e
450            })
451            .ok();
452        }
453    }
454    None
455}