diff --git a/Cargo.toml b/Cargo.toml index 84c3f05..34c3fcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,6 @@ edition = "2021" lto = "fat" panic = "abort" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[features] -fontloading = ["fontconfig"] - [dependencies] wayland-client = { version = "0.30", features = ["calloop", "log"] } wayland-protocols = { version = "0.30", features = ["client", "unstable", "staging"] } diff --git a/src/config.rs b/src/config.rs index 2aa18ef..86491ca 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex}; use crate::{ event::Events, - fonts::FontMap, + fonts::{FontMap, find_font}, widgets::{ Backlight, Battery, Calendar, Clock, Date, HorizontalLayout, IndexedLayout, Interface, InvertedHorizontalLayout, Layout, Line, Margin, PulseAudio, VerticalLayout, Widget as RealWidget, @@ -14,7 +14,14 @@ use crate::{ }; #[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "snake_case")] +pub enum OperationMode { + LayerSurface((u32, u32)), + XdgToplevel, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] pub enum Widget { Margin { margins: (u32, u32, u32, u32), @@ -58,38 +65,47 @@ pub enum Widget { }, } +impl Default for OperationMode { + fn default() -> OperationMode { + OperationMode::LayerSurface((1024, 768)) + } +} + #[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "snake_case")] pub struct Config { pub font_paths: Option>, - pub widget: Option, + pub widget: Widget, + pub mode: OperationMode, } impl Default for Config { fn default() -> Config { Config{ font_paths: None, - widget: None, + widget: Default::default(), + mode: Default::default(), } } } -fn leak_or_default(v: Option, d: &'static str) -> &'static str { - match v { - Some(v) => Box::leak(v.into_boxed_str()), - None => d, - } -} +impl Config { + pub fn generate_v1() -> Config { + let has_battery = Battery::detect(); + let has_backlight = Backlight::detect("intel_backlight"); + let sans = find_font("sans"); + let monospace = find_font("monospace"); -impl Default for Widget { - fn default() -> Widget { - Self::v2() - } -} + let mut bars = Vec::new(); + if has_battery { + bars.push(Widget::Margin{ margins: (0, 0, 0, 8), widget: Box::new(Widget::Battery { font: None, font_size: 24. })}); + } + if has_backlight { + bars.push(Widget::Margin{ margins: (0, 0, 0, 8), widget: Box::new(Widget::Backlight { device: Some("intel_backlight".to_string()), font: None, font_size: 24. })}); + } + bars.push(Widget::Margin{ margins: (0, 0, 0, 8), widget: Box::new(Widget::PulseAudio { font: None, font_size: 24. })}); -impl Widget { - pub fn v1() -> Widget { - Widget::Margin { + let widget = Widget::Margin { margins: (20, 20, 20, 20), widget: Box::new(Widget::VerticalLayout(vec![ Widget::HorizontalLayout(vec![ @@ -105,30 +121,7 @@ impl Widget { ]), 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: Box::new(Widget::VerticalLayout(bars)), }, ]), Widget::Calendar { @@ -143,10 +136,88 @@ impl Widget { font_size: 32., }, ])), + }; + + let map = HashMap::from([ + ("sans".to_string(), sans), + ("monospace".to_string(), monospace), + ]); + + Config{ + font_paths: Some(map), + widget: widget, + mode: OperationMode::LayerSurface((1024, 768)), } } - pub fn v2() -> Widget { + pub fn generate_v2(clay: bool) -> Config { + let has_battery = Battery::detect(); + let has_backlight = Backlight::detect("intel_backlight"); + let sans = find_font("sans"); + let monospace = find_font("monospace"); + + let mut bars = Vec::new(); + if has_battery { + bars.push(Widget::Margin{ margins: (16, 8, 8, 0), widget: Box::new(Widget::Battery { font: None, font_size: 24. })}); + } + if has_backlight { + bars.push(Widget::Margin{ margins: (16, 8, 8, 0), widget: Box::new(Widget::Backlight { device: Some("intel_backlight".to_string()), font: None, font_size: 24. })}); + } + bars.push(Widget::Margin{ margins: (16, 8, 8, 0), widget: Box::new(Widget::PulseAudio { font: None, font_size: 24. })}); + + let 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(bars), + ]), + 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., + }, + ]), + ]); + + let map = HashMap::from([ + ("sans".to_string(), sans), + ("monospace".to_string(), monospace), + ]); + + Config{ + font_paths: Some(map), + widget: widget, + mode: if clay { OperationMode::XdgToplevel } else { OperationMode::LayerSurface((1024, 768)) }, + } + } +} + +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 { Widget::VerticalLayout(vec![ Widget::HorizontalLayout(vec![ Widget::Clock { @@ -201,7 +272,9 @@ impl Widget { ]), ]) } +} +impl Widget { pub fn construct_widgets( self, v: &mut Vec>, diff --git a/src/fonts.rs b/src/fonts.rs index 308cbe3..831cfc8 100644 --- a/src/fonts.rs +++ b/src/fonts.rs @@ -9,27 +9,21 @@ use std::{ /// FontRef is used to store Fonts on widgets. pub type FontRef<'a> = &'a rusttype::Font<'a>; -#[cfg(feature = "fontloading")] -mod fc { +#[cfg(feature = "fontconfig")] +pub(crate) fn find_font(name: &str) -> String { use fontconfig::Fontconfig as FontConfig; - /// Acts like fc-match. - /// Given a string, it matches it to a font file and returns its path. - pub(crate) fn from_string(name: &str) -> String { - let fc = FontConfig::new().unwrap(); - fc.find(name, None) - .unwrap() - .path - .to_str() - .unwrap() - .to_string() - } + let fc = FontConfig::new().unwrap(); + fc.find(name, None) + .unwrap() + .path + .to_str() + .unwrap() + .to_string() } -#[cfg(not(feature = "fontloading"))] -mod fc { - pub(crate) fn from_string(_name: &str) -> String { - panic!("fontloading feature not enabled"); - } +#[cfg(not(feature = "fontconfig"))] +pub(crate) fn find_font(_name: &str) -> String { + panic!("fontconfig not enabled so font search not available"); } /// FontLoader is a marker struct that is used to load files @@ -110,7 +104,7 @@ impl FontMap { let path = match self.font_paths.get(font_name) { Some(res) => res, _ => { - let s = fc::from_string(font_name); + let s = find_font(font_name); self.font_paths.insert(font_name, s); self.font_paths.get(font_name).unwrap() } diff --git a/src/main.rs b/src/main.rs index e1f6783..ce57e6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,39 +20,36 @@ use calloop::{ use chrono::{Duration, Local, Timelike}; use buffer::BufferView; -use config::{Config, Widget as ConfigWidget}; +use config::Config; use event::{Event, Events}; use fonts::{FontMap, MaybeFontMap}; -use state::{OperationMode, State}; +use state::State; use widgets::{Geometry, Widget}; use std::{env, rc::Rc, thread}; -enum UIVersion { - V1, - V2, -} - fn main() { let mut args = env::args(); // Skip program name _ = args.next(); - let mut version = UIVersion::V2; - let mut mode = OperationMode::XdgToplevel; loop { match args.next() { - Some(ref s) if s == "layer_shell" => { - let width = args.next().map(|x| x.parse::().ok()).flatten(); - let height = args.next().map(|x| x.parse::().ok()).flatten(); - match (width, height) { - (Some(w), Some(h)) => mode = OperationMode::LayerSurface((w, h)), - _ => panic!("invalid arguments"), + Some(ref s) if s == "generate" => { + let config = match args.next() { + Some(ref s) if s == "v1" => Config::generate_v1(), + Some(ref s) if s == "v2" => Config::generate_v2(false), + Some(ref s) if s == "clay" => Config::generate_v2(true), + None => Config::generate_v2(false), + Some(s) => panic!("unknown argument: {}", s), + }; + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + match File::create(format!("{}/.config/wldash/config.yml", home)) { + Ok(f) => serde_yaml::to_writer(f, &config).unwrap(), + Err(_) => panic!("uh"), } - } - Some(ref s) if s == "xdg_shell" => mode = OperationMode::XdgToplevel, - Some(ref s) if s == "v1" => version = UIVersion::V1, - Some(ref s) if s == "v2" => version = UIVersion::V2, + std::process::exit(0); + }, Some(_) => panic!("unknown argument"), None => break, } @@ -122,24 +119,15 @@ fn main() { 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 layout = Rc::new(config.widget.construct_layout(&mut 0)); let mut widgets: Vec> = Vec::new(); - widget.construct_widgets(&mut widgets, &mut fm, &events); - + config.widget.construct_widgets(&mut widgets, &mut fm, &events); let font_thread = thread::Builder::new() .name("fontloader".to_string()) @@ -150,7 +138,7 @@ fn main() { .unwrap(); let mut state = State::new( - mode, + config.mode, widgets, layout, MaybeFontMap::Waiting(font_thread), diff --git a/src/state.rs b/src/state.rs index d8e0b32..487f7a8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -29,6 +29,7 @@ use wayland_protocols::{ use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1}; use crate::{ + config::OperationMode, buffer::BufferManager, event::{Event, Events, PointerButton, PointerEvent}, fonts::{FontMap, MaybeFontMap}, @@ -36,12 +37,6 @@ use crate::{ widgets::{Geometry, Layout, Widget, WidgetUpdater}, }; -#[derive(Debug)] -pub enum OperationMode { - LayerSurface((u32, u32)), - XdgToplevel, -} - #[derive(Default)] pub struct MainSurface { pub wl_surface: Option, diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index e13a771..de74c7a 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -39,6 +39,14 @@ impl Backlight { dev.update(); BarWidget::new(Box::new(dev), fm, font, size) } + + pub fn detect(path: &str) -> bool { + let path = Path::new("/sys/class/backlight").to_path_buf().join(path); + match read_file_as_u64(path.join("brightness").as_path()) { + Ok(_) => true, + Err(_) => false, + } + } } impl BarWidgetImpl for Backlight { diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 53c1dc3..ef2c1d5 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -2,6 +2,7 @@ use std::{ sync::{Arc, Mutex}, thread, time::Duration, + error::Error, }; use dbus::blocking::{ @@ -46,6 +47,19 @@ struct InnerBattery { dirty: bool, } +fn get_battery_state() -> Result> { + let conn = LocalConnection::new_system()?; + let device_path = "/org/freedesktop/UPower/devices/DisplayDevice"; + let proxy = conn.with_proxy( + "org.freedesktop.UPower", + device_path, + Duration::from_millis(500), + ); + let state: u32 = proxy + .get("org.freedesktop.UPower.Device", "State")?; + Ok(UpowerBatteryState::from_dbus(state as u64)) +} + fn start_monitor(inner: Arc>, events: Arc>) { thread::Builder::new() .name("battmon".to_string()) @@ -131,6 +145,13 @@ impl Battery { start_monitor(battery.inner.clone(), events); BarWidget::new(Box::new(battery), fm, font, size) } + + pub fn detect() -> bool { + match get_battery_state() { + Ok(_) => true, + Err(_) => false, + } + } } impl BarWidgetImpl for Battery {