diff --git a/src/config.rs b/src/config.rs index afa2ad0..5515046 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,48 +22,97 @@ pub struct Config { pub indicator_thickness: u32, #[arg(long, value_parser = util::parse_blur_effect)] + #[serde( + deserialize_with = "util::deserialize_blur_effect", + serialize_with = "util::serialize_blur_effect", + default + )] pub effect_blur: Option<(u32, u32)>, #[arg(long, value_parser = util::parse_vignette_effect)] + #[serde( + deserialize_with = "util::deserialize_vignette_effect", + serialize_with = "util::serialize_vignette_effect", + default + )] pub effect_vignette: Option<(f32, f32)>, #[arg(long)] + #[serde(default)] pub effect_pixelate: Option, #[arg(long)] + #[serde(default)] pub effect_swirl: Option, #[arg(long)] + #[serde(default)] pub effect_melting: Option, #[arg(long, default_value = "785412", value_parser = util::parse_hex_color)] + #[serde( + deserialize_with = "util::deserialize_hex_color", + serialize_with = "util::serialize_hex_color" + )] pub ring_color: (f64, f64, f64, f64), #[arg(long, default_value = "4EAC41", value_parser = util::parse_hex_color)] + #[serde( + deserialize_with = "util::deserialize_hex_color", + serialize_with = "util::serialize_hex_color" + )] pub key_hl_color: (f64, f64, f64, f64), #[arg(long, default_value = "4EAC41", value_parser = util::parse_hex_color)] + #[serde( + deserialize_with = "util::deserialize_hex_color", + serialize_with = "util::serialize_hex_color" + )] pub caps_lock_key_hl_color: (f64, f64, f64, f64), #[arg(long, default_value = "DB3300", value_parser = util::parse_hex_color)] + #[serde( + deserialize_with = "util::deserialize_hex_color", + serialize_with = "util::serialize_hex_color" + )] pub caps_lock_bs_hl_color: (f64, f64, f64, f64), #[arg(long, default_value = "E5A445", value_parser = util::parse_hex_color)] + #[serde( + deserialize_with = "util::deserialize_hex_color", + serialize_with = "util::serialize_hex_color" + )] pub caps_lock_color: (f64, f64, f64, f64), #[arg(long, default_value = "E5A445", value_parser = util::parse_hex_color)] + #[serde( + deserialize_with = "util::deserialize_hex_color", + serialize_with = "util::serialize_hex_color" + )] pub caps_lock_text_color: (f64, f64, f64, f64), #[arg(long, action = clap::ArgAction::SetTrue, default_value_t = true)] pub show_caps_lock_text: bool, #[arg(long, default_value = "00000000", value_parser = util::parse_hex_color)] + #[serde( + deserialize_with = "util::deserialize_hex_color", + serialize_with = "util::serialize_hex_color" + )] pub line_color: (f64, f64, f64, f64), #[arg(long, default_value = "00000088", value_parser = util::parse_hex_color)] + #[serde( + deserialize_with = "util::deserialize_hex_color", + serialize_with = "util::serialize_hex_color" + )] pub inside_color: (f64, f64, f64, f64), #[arg(long, default_value = "00000000", value_parser = util::parse_hex_color)] + #[serde( + deserialize_with = "util::deserialize_hex_color", + serialize_with = "util::serialize_hex_color" + )] pub separator_color: (f64, f64, f64, f64), #[arg(long, default_value = "2")] @@ -107,34 +156,44 @@ pub struct Config { pub show_keyboard_layout: bool, #[arg(long)] + #[serde(default)] pub image: Option, #[arg(long)] + #[serde(default)] pub wifi_icon: Option, #[arg(long)] + #[serde(default)] pub bluetooth_icon: Option, #[arg(long)] + #[serde(default)] pub battery_icon: Option, #[arg(long)] + #[serde(default)] pub media_prev_icon: Option, #[arg(long)] + #[serde(default)] pub media_stop_icon: Option, #[arg(long)] + #[serde(default)] pub media_play_icon: Option, #[arg(long)] + #[serde(default)] pub media_pause_icon: Option, #[arg(long)] + #[serde(default)] pub media_next_icon: Option, /// Apply a pre-defined theme preset #[arg(long)] + #[serde(default)] pub theme: Option, } @@ -159,111 +218,25 @@ impl Config { if config_path.exists() { if let Ok(file_content) = std::fs::read_to_string(&config_path) { - if let Ok(table) = toml::from_str::(&file_content) { + if let Ok(file_table) = toml::from_str::(&file_content) { log::debug!("Loaded configuration from {:?}", config_path); - let merge_bool = |val: &mut bool, key: &str| { - if !is_cli(key) { - if let Some(toml::Value::Boolean(b)) = table.get(key) { - *val = *b; + // Convert current config to a TOML table to facilitate merging + if let Ok(mut config_table) = toml::Value::try_from(config.clone()) { + if let Some(config_table) = config_table.as_table_mut() { + for (key, value) in file_table { + if !is_cli(&key) { + config_table.insert(key, value); + } } - } - }; - let merge_u32 = |val: &mut u32, key: &str| { - if !is_cli(key) { - if let Some(toml::Value::Integer(i)) = table.get(key) { - *val = *i as u32; + // Convert back to Config struct + if let Ok(new_config) = + toml::Value::Table(config_table.clone()).try_into::() + { + config = new_config; } } - }; - - let merge_f32 = |val: &mut f32, key: &str| { - if !is_cli(key) { - if let Some(toml::Value::Float(f)) = table.get(key) { - *val = *f as f32; - } else if let Some(toml::Value::Integer(i)) = table.get(key) { - *val = *i as f32; - } - } - }; - - let merge_string = |val: &mut String, key: &str| { - if !is_cli(key) { - if let Some(toml::Value::String(s)) = table.get(key) { - *val = s.clone(); - } - } - }; - - merge_bool(&mut config.screenshots, "screenshots"); - merge_bool(&mut config.clock, "clock"); - merge_bool(&mut config.indicator, "indicator"); - merge_u32(&mut config.indicator_radius, "indicator_radius"); - merge_u32(&mut config.indicator_thickness, "indicator_thickness"); - merge_f32(&mut config.grace, "grace"); - merge_f32(&mut config.fade_in, "fade_in"); - merge_string(&mut config.pam_service, "pam_service"); - merge_bool(&mut config.show_media, "show_media"); - merge_bool(&mut config.show_battery, "show_battery"); - merge_bool(&mut config.show_network, "show_network"); - merge_bool(&mut config.show_bluetooth, "show_bluetooth"); - merge_bool(&mut config.show_album_art, "show_album_art"); - merge_bool(&mut config.hide_password, "hide_password"); - merge_bool(&mut config.show_keyboard_layout, "show_keyboard_layout"); - - if !is_cli("image") { - if let Some(toml::Value::String(s)) = table.get("image") { - config.image = Some(std::path::PathBuf::from(s)); - } - } - - if !is_cli("wifi_icon") { - if let Some(toml::Value::String(s)) = table.get("wifi_icon") { - config.wifi_icon = Some(s.clone()); - } - } - - if !is_cli("bluetooth_icon") { - if let Some(toml::Value::String(s)) = table.get("bluetooth_icon") { - config.bluetooth_icon = Some(s.clone()); - } - } - - if !is_cli("battery_icon") { - if let Some(toml::Value::String(s)) = table.get("battery_icon") { - config.battery_icon = Some(s.clone()); - } - } - - if !is_cli("media_prev_icon") { - if let Some(toml::Value::String(s)) = table.get("media_prev_icon") { - config.media_prev_icon = Some(s.clone()); - } - } - - if !is_cli("media_stop_icon") { - if let Some(toml::Value::String(s)) = table.get("media_stop_icon") { - config.media_stop_icon = Some(s.clone()); - } - } - - if !is_cli("media_play_icon") { - if let Some(toml::Value::String(s)) = table.get("media_play_icon") { - config.media_play_icon = Some(s.clone()); - } - } - - if !is_cli("media_pause_icon") { - if let Some(toml::Value::String(s)) = table.get("media_pause_icon") { - config.media_pause_icon = Some(s.clone()); - } - } - - if !is_cli("media_next_icon") { - if let Some(toml::Value::String(s)) = table.get("media_next_icon") { - config.media_next_icon = Some(s.clone()); - } } } } diff --git a/src/util.rs b/src/util.rs index 470fab3..ce4926b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Deserializer, Serializer}; + pub fn parse_hex_color(s: &str) -> Result<(f64, f64, f64, f64), String> { let s = s.trim_start_matches('#'); let len = s.len(); @@ -19,6 +21,34 @@ pub fn parse_hex_color(s: &str) -> Result<(f64, f64, f64, f64), String> { Ok((r, g, b, a)) } +pub fn deserialize_hex_color<'de, D>(deserializer: D) -> Result<(f64, f64, f64, f64), D::Error> +where + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + parse_hex_color(&s).map_err(serde::de::Error::custom) +} + +pub fn serialize_hex_color( + color: &(f64, f64, f64, f64), + serializer: S, +) -> Result +where + S: Serializer, +{ + let (r, g, b, a) = color; + let r = (r * 255.0) as u8; + let g = (g * 255.0) as u8; + let b = (b * 255.0) as u8; + let a = (a * 255.0) as u8; + + if a == 255 { + serializer.serialize_str(&format!("{:02x}{:02x}{:02x}", r, g, b)) + } else { + serializer.serialize_str(&format!("{:02x}{:02x}{:02x}{:02x}", r, g, b, a)) + } +} + pub fn parse_blur_effect(s: &str) -> Result<(u32, u32), String> { let parts: Vec<&str> = s.split('x').collect(); if parts.len() != 2 { @@ -29,6 +59,32 @@ pub fn parse_blur_effect(s: &str) -> Result<(u32, u32), String> { Ok((radius, times)) } +pub fn deserialize_blur_effect<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s = Option::::deserialize(deserializer)?; + match s { + Some(s) => parse_blur_effect(&s) + .map(Some) + .map_err(serde::de::Error::custom), + None => Ok(None), + } +} + +pub fn serialize_blur_effect( + val: &Option<(u32, u32)>, + serializer: S, +) -> Result +where + S: Serializer, +{ + match val { + Some((radius, times)) => serializer.serialize_str(&format!("{}x{}", radius, times)), + None => serializer.serialize_none(), + } +} + pub fn parse_vignette_effect(s: &str) -> Result<(f32, f32), String> { let parts: Vec<&str> = s.split(':').collect(); if parts.len() != 2 { @@ -38,3 +94,29 @@ pub fn parse_vignette_effect(s: &str) -> Result<(f32, f32), String> { let factor = parts[1].parse().map_err(|_| "Invalid factor")?; Ok((base, factor)) } + +pub fn deserialize_vignette_effect<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s = Option::::deserialize(deserializer)?; + match s { + Some(s) => parse_vignette_effect(&s) + .map(Some) + .map_err(serde::de::Error::custom), + None => Ok(None), + } +} + +pub fn serialize_vignette_effect( + val: &Option<(f32, f32)>, + serializer: S, +) -> Result +where + S: Serializer, +{ + match val { + Some((base, factor)) => serializer.serialize_str(&format!("{}:{}", base, factor)), + None => serializer.serialize_none(), + } +}