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 pub static ref ATTRS: OnceCell<HashMap<String, Value>> =
56 OnceCell::new();
57 pub static ref RAMPS: OnceCell<HashMap<String, Value>> =
62 OnceCell::new();
63 pub static ref BGS: OnceCell<HashMap<String, Value>> =
68 OnceCell::new();
69 pub static ref CONSTS: OnceCell<HashMap<String, Value>> =
74 OnceCell::new();
75 pub static ref IMAGES: OnceCell<HashMap<String, Value>> =
80 OnceCell::new();
81 pub static ref HIGHLIGHTS: OnceCell<HashMap<String, Value>> =
86 OnceCell::new();
87}
88
89pub 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 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}