diff --git a/Cargo.lock b/Cargo.lock index 67cdc4d..bc7e17c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -239,6 +239,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -263,6 +269,16 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "io-lifetimes" version = "1.0.3" @@ -273,6 +289,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + [[package]] name = "js-sys" version = "0.3.60" @@ -615,6 +637,12 @@ dependencies = [ "owned_ttf_parser", ] +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + [[package]] name = "same-file" version = "1.0.6" @@ -636,6 +664,39 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.5" @@ -770,6 +831,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "unsafe-libyaml" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" + [[package]] name = "vec_map" version = "0.8.2" @@ -1032,6 +1099,8 @@ dependencies = [ "nix 0.26.1", "rcalc_lib", "rusttype", + "serde", + "serde_yaml", "shlex", "unicode-segmentation", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index 9580b5a..84c3f05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,6 @@ rcalc_lib = "1.0" #alsa = { version = "0.7", optional = true } dbus = "0.9" libpulse-binding = "2.26" + +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.9" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..2aa18ef --- /dev/null +++ b/src/config.rs @@ -0,0 +1,299 @@ +use serde::{self, Deserialize, Serialize}; + +use std::collections::HashMap; +use std::default::Default; +use std::sync::{Arc, Mutex}; + +use crate::{ + event::Events, + fonts::FontMap, + widgets::{ + Backlight, Battery, Calendar, Clock, Date, HorizontalLayout, IndexedLayout, Interface, + InvertedHorizontalLayout, Layout, Line, Margin, PulseAudio, VerticalLayout, Widget as RealWidget, + }, +}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub enum Widget { + Margin { + margins: (u32, u32, u32, u32), + widget: Box, + }, + HorizontalLayout(Vec), + InvertedHorizontalLayout(Vec), + VerticalLayout(Vec), + Line(u32), + Clock { + font: Option, + font_size: f32, + }, + Date { + font: Option, + font_size: f32, + }, + Calendar { + font_primary: Option, + font_secondary: Option, + font_size: f32, + sections_x: i32, + sections_y: i32, + }, + Launcher { + font: Option, + font_size: f32, + }, + Battery { + font: Option, + font_size: f32, + }, + Backlight { + device: Option, + font: Option, + font_size: f32, + }, + PulseAudio { + font: Option, + font_size: f32, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Config { + pub font_paths: Option>, + pub widget: Option, +} + +impl Default for Config { + fn default() -> Config { + Config{ + font_paths: None, + widget: None, + } + } +} + +fn leak_or_default(v: Option, d: &'static str) -> &'static str { + match v { + Some(v) => Box::leak(v.into_boxed_str()), + None => d, + } +} + +impl Default for Widget { + fn default() -> Widget { + Self::v2() + } +} + +impl Widget { + pub fn v1() -> Widget { + Widget::Margin { + margins: (20, 20, 20, 20), + widget: Box::new(Widget::VerticalLayout(vec![ + Widget::HorizontalLayout(vec![ + Widget::VerticalLayout(vec![ + Widget::Date { + font: None, + font_size: 48., + }, + Widget::Clock { + font: None, + font_size: 256., + }, + ]), + Widget::Margin { + margins: (88, 0, 0, 0), + widget: Box::new(Widget::VerticalLayout(vec![ + Widget::Margin { + margins: (0, 0, 0, 8), + widget: Box::new(Widget::Battery { + font: None, + font_size: 24., + }), + }, + Widget::Margin { + margins: (0, 0, 0, 8), + widget: Box::new(Widget::Backlight { + device: None, + font: None, + font_size: 24., + }), + }, + Widget::Margin { + margins: (0, 0, 0, 8), + widget: Box::new(Widget::PulseAudio { + font: None, + font_size: 24., + }), + }, + ])), + }, + ]), + Widget::Calendar { + font_primary: None, + font_secondary: None, + font_size: 36., + sections_x: 3, + sections_y: 1, + }, + Widget::Launcher { + font: None, + font_size: 32., + }, + ])), + } + } + + pub fn v2() -> Widget { + Widget::VerticalLayout(vec![ + Widget::HorizontalLayout(vec![ + Widget::Clock { + font: None, + font_size: 128., + }, + Widget::Margin { + margins: (16, 16, 0, 0), + widget: Box::new(Widget::Date { + font: None, + font_size: 48., + }), + }, + Widget::VerticalLayout(vec![ + Widget::Margin { + margins: (16, 8, 8, 0), + widget: Box::new(Widget::Battery { + font: None, + font_size: 24., + }), + }, + Widget::Margin { + margins: (16, 8, 8, 0), + widget: Box::new(Widget::Backlight { + device: None, + font: None, + font_size: 24., + }), + }, + Widget::Margin { + margins: (16, 8, 8, 0), + widget: Box::new(Widget::PulseAudio { + font: None, + font_size: 24., + }), + }, + ]), + ]), + Widget::Line(1), + Widget::InvertedHorizontalLayout(vec![ + Widget::Calendar { + font_primary: None, + font_secondary: None, + font_size: 24., + sections_x: 1, + sections_y: -1, + }, + Widget::Launcher { + font: None, + font_size: 32., + }, + ]), + ]) + } + + pub fn construct_widgets( + self, + v: &mut Vec>, + mut fm: &mut FontMap, + events: &Arc>, + ) { + match self { + Widget::Margin { widget, .. } => widget.construct_widgets(v, fm, events), + Widget::HorizontalLayout(widgets) => widgets + .into_iter() + .for_each(|w| w.construct_widgets(v, fm, events)), + Widget::InvertedHorizontalLayout(widgets) => widgets + .into_iter() + .for_each(|w| w.construct_widgets(v, fm, events)), + Widget::VerticalLayout(widgets) => widgets + .into_iter() + .for_each(|w| w.construct_widgets(v, fm, events)), + Widget::Line(width) => v.push(Box::new(Line::new(width))), + Widget::Clock { font, font_size } => v.push(Box::new(Clock::new( + &mut fm, + leak_or_default(font, "sans"), + font_size, + ))), + Widget::Date { font, font_size } => v.push(Box::new(Date::new( + &mut fm, + leak_or_default(font, "sans"), + font_size, + ))), + Widget::Calendar { + font_primary, + font_secondary, + font_size, + sections_x, + sections_y, + } => v.push(Box::new(Calendar::new( + &mut fm, + leak_or_default(font_primary, "monospace"), + leak_or_default(font_secondary, "sans"), + font_size, + sections_x, + sections_y, + ))), + Widget::Launcher { font, font_size } => v.push(Box::new(Interface::new( + events.clone(), + &mut fm, + leak_or_default(font, "sans"), + font_size, + ))), + Widget::Battery { font, font_size } => v.push(Box::new(Battery::new( + events.clone(), + &mut fm, + leak_or_default(font, "sans"), + font_size, + ))), + Widget::Backlight { + device, + font, + font_size, + } => v.push(Box::new(Backlight::new( + leak_or_default(device, "intel_backlight"), + &mut fm, + leak_or_default(font, "sans"), + font_size, + ))), + Widget::PulseAudio { font, font_size } => v.push(Box::new(PulseAudio::new( + events.clone(), + &mut fm, + Box::leak(font.unwrap_or_else(|| "sans".to_string()).into_boxed_str()), + font_size, + ))), + } + } + + pub fn construct_layout(&self, idx: &mut usize) -> Box { + match self { + Widget::Margin { margins, widget } => { + Margin::new(widget.construct_layout(idx), *margins) + } + Widget::HorizontalLayout(widgets) => { + HorizontalLayout::new(widgets.iter().map(|w| w.construct_layout(idx)).collect()) + } + Widget::InvertedHorizontalLayout(widgets) => InvertedHorizontalLayout::new( + widgets.iter().map(|w| w.construct_layout(idx)).collect(), + ), + Widget::VerticalLayout(widgets) => { + VerticalLayout::new(widgets.iter().map(|w| w.construct_layout(idx)).collect()) + } + _ => IndexedLayout::new({ + let i = *idx; + *idx += 1; + i + }), + } + } +} diff --git a/src/main.rs b/src/main.rs index 1d1a619..e1f6783 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod buffer; mod color; +mod config; mod draw; mod event; mod fonts; @@ -8,6 +9,8 @@ mod state; mod utils; mod widgets; +use std::fs::File; + use wayland_client::{Connection, QueueHandle, WaylandSource}; use calloop::{ @@ -17,13 +20,11 @@ use calloop::{ use chrono::{Duration, Local, Timelike}; use buffer::BufferView; +use config::{Config, Widget as ConfigWidget}; use event::{Event, Events}; use fonts::{FontMap, MaybeFontMap}; use state::{OperationMode, State}; -use widgets::{ - Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, - Interface, InvertedHorizontalLayout, Layout, Line, Margin, PulseAudio, VerticalLayout, Widget, -}; +use widgets::{Geometry, Widget}; use std::{env, rc::Rc, thread}; @@ -115,77 +116,31 @@ fn main() { let mut fm = FontMap::new(); - // TODO: Cache in a file - fm.add_font_path( - "sans", - "/usr/share/fonts/noto/NotoSans-Regular.ttf".to_string(), - ); - fm.add_font_path( - "monospace", - "/usr/share/fonts/noto/NotoSansMono-Regular.ttf".to_string(), - ); - - let (widgets, layout): (Vec>, Rc>) = match version { - UIVersion::V1 => { - let clock = Box::new(Clock::new(&mut fm, "sans", 256.)); - let calendar = Box::new(Calendar::new(&mut fm, "monospace", "sans", 36.0, 3, 1)); - let date = Box::new(Date::new(&mut fm, "sans", 48.)); - let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); - let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); - let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); - let audio = Box::new(PulseAudio::new(events.clone(), &mut fm, "sans", 24.)); - - let v: Vec> = - vec![clock, date, battery, backlight, audio, calendar, launcher]; - let l = Rc::new(Margin::new( - VerticalLayout::new(vec![ - HorizontalLayout::new(vec![ - VerticalLayout::new(vec![IndexedLayout::new(1), IndexedLayout::new(0)]), - Margin::new( - VerticalLayout::new(vec![ - Margin::new(IndexedLayout::new(2), (0, 0, 0, 8)), - Margin::new(IndexedLayout::new(3), (0, 0, 0, 8)), - Margin::new(IndexedLayout::new(4), (0, 0, 0, 8)), - ]), - (88, 0, 0, 0), - ), - ]), - IndexedLayout::new(5), - IndexedLayout::new(6), - ]), - (20, 20, 20, 20), - )); - (v, l) - } - UIVersion::V2 => { - let clock = Box::new(Clock::new(&mut fm, "sans", 128.)); - let calendar = Box::new(Calendar::new(&mut fm, "monospace", "sans", 24.0, 1, -1)); - let date = Box::new(Date::new(&mut fm, "sans", 48.)); - let line = Box::new(Line::new(1)); - let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); - let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); - let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); - let audio = Box::new(PulseAudio::new(events.clone(), &mut fm, "sans", 24.)); - let v: Vec> = vec![ - clock, date, battery, backlight, audio, line, calendar, launcher, - ]; - let l = Rc::new(VerticalLayout::new(vec![ - HorizontalLayout::new(vec![ - IndexedLayout::new(0), - Margin::new(IndexedLayout::new(1), (16, 16, 0, 0)), - VerticalLayout::new(vec![ - Margin::new(IndexedLayout::new(2), (16, 8, 8, 0)), - Margin::new(IndexedLayout::new(3), (16, 8, 8, 0)), - Margin::new(IndexedLayout::new(4), (16, 8, 8, 0)), - ]), - ]), - IndexedLayout::new(5), - InvertedHorizontalLayout::new(vec![IndexedLayout::new(6), IndexedLayout::new(7)]), - ])); - (v, l) - } + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + let config: Config = match File::open(format!("{}/.config/wldash/config.yml", home)) { + Ok(f) => serde_yaml::from_reader(f).unwrap(), + Err(_) => Default::default(), }; + let widget = match config.widget { + Some(w) => w, + None => match version { + UIVersion::V1 => ConfigWidget::v1(), + UIVersion::V2 => ConfigWidget::v2(), + }, + }; + + if let Some(font_paths) = config.font_paths { + for (key, value) in font_paths.into_iter() { + fm.add_font_path(Box::leak(key.into_boxed_str()), value); + } + } + + let layout = Rc::new(widget.construct_layout(&mut 0)); + let mut widgets: Vec> = Vec::new(); + widget.construct_widgets(&mut widgets, &mut fm, &events); + + let font_thread = thread::Builder::new() .name("fontloader".to_string()) .spawn(move || {