From f0930ab2ebba631029db43ad4a77edfce0d8b964 Mon Sep 17 00:00:00 2001 From: emilis Date: Sat, 2 Mar 2024 21:18:21 +0000 Subject: [PATCH] tag rework: tags now can either be standard or other, supports all types better --- src/config.rs | 480 +++++++++++++++++----------------------- src/environ.rs | 67 ++++++ src/hlwm/command.rs | 12 +- src/hlwm/hook.rs | 4 +- src/hlwm/key.rs | 35 ++- src/hlwm/mod.rs | 17 +- src/hlwm/pad.rs | 2 +- src/hlwm/rule.rs | 4 +- src/hlwm/setting.rs | 9 +- src/hlwm/tag.rs | 98 ++++++++ src/hlwm/theme.rs | 2 +- src/hlwm/window.rs | 94 +------- src/main.rs | 13 +- src/{hlwm => }/split.rs | 0 14 files changed, 432 insertions(+), 405 deletions(-) create mode 100644 src/environ.rs create mode 100644 src/hlwm/tag.rs rename src/{hlwm => }/split.rs (100%) diff --git a/src/config.rs b/src/config.rs index ccab6ef..e69ee8a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,6 @@ use std::{ borrow::BorrowMut, - cmp::Ordering, - convert::Infallible, + collections::HashMap, env, fmt::{Debug, Display}, fs::{self}, @@ -13,21 +12,19 @@ use std::{ }; use log::info; -use serde::{ - de::{Expected, Unexpected}, - Deserialize, Serialize, -}; +use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use thiserror::Error; use crate::{ + environ::{self, ActiveKeybinds, EnvironError}, hlwm::{ self, attribute::Attribute, color::Color, command::{CommandError, HlwmCommand}, hook::Hook, - key::{Key, KeyParseError, KeyUnbind, Keybind, MouseButton, Mousebind, MousebindAction}, + key::{Key, KeyUnbind, Keybind, MouseButton, Mousebind, MousebindAction}, rule::{Condition, Consequence, FloatPlacement, Rule, Unrule}, setting::{FrameLayout, Setting, ShowFrameDecoration, SmartFrameSurroundings}, theme::ThemeAttr, @@ -54,10 +51,10 @@ pub enum ConfigError { CommandError(#[from] CommandError), #[error("non-utf8 string error: {0}")] Utf8StringError(#[from] FromUtf8Error), - #[error("failed parsing keybind [{0}]: [{1}]")] - KeyParseError(String, KeyParseError), #[error("failed parsing value from string: {0}")] StringParseError(#[from] StringParseError), + #[error("getting from environment error: [{0}]")] + EnvironError(#[from] EnvironError), } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] @@ -342,23 +339,7 @@ impl Config { info!("loading tag command set"); (&self.tags) .into_iter() - .map(|tag| (tag, tag.key())) - .filter(|(_, key)| key.is_some()) - .map(|(tag, key)| { - let tag_name = tag.to_string(); - let key = key.unwrap(); - [ - HlwmCommand::AddTag(tag_name.clone()), - HlwmCommand::Keybind(Keybind::new( - [self.mod_key, key], - HlwmCommand::UseTag(tag_name.clone()), - )), - HlwmCommand::Keybind(Keybind::new( - [self.mod_key, Key::Shift, key], - HlwmCommand::MoveTag(tag_name), - )), - ] - }) + .map(|tag| tag.to_command_set(self.mod_key)) .flatten() .chain([HlwmCommand::And { separator: Separator::Comma, @@ -378,7 +359,9 @@ impl Config { }, HlwmCommand::MergeTag { tag: "default".to_string(), - target: Some(hlwm::Tag::Name(self.tags.first().unwrap().to_string())), + target: Some(hlwm::TagSelect::Name( + self.tags.first().cloned().unwrap().name().to_string(), + )), }, ], } @@ -387,7 +370,7 @@ impl Config { } /// Create a config gathered from the herbstluftwm configs, - /// using default mouse binds/tags + /// using default mouse binds/attributes set pub fn from_herbstluft() -> Self { fn setting Result>( name: &str, @@ -404,6 +387,7 @@ impl Config { } let client = Client::new(); let default = Config::default(); + let mod_key = setting(Self::MOD_KEY, Key::from_str, default.mod_key); Config { font: client .get_str_attr(Self::FONT.to_string()) @@ -417,7 +401,7 @@ impl Config { font_pango_bold: client .get_str_attr(Self::FONT_PANGO_BOLD.to_string()) .unwrap_or_log(default.font_pango_bold), - mod_key: setting(Self::MOD_KEY, Key::from_str, default.mod_key), + mod_key, services: setting( Self::SERVICES, |v| serde_json::de::from_str(v), @@ -444,26 +428,9 @@ impl Config { .collect() })(), }, - keybinds: Self::active_keybinds(true).unwrap_or_log(default.keybinds), - tags: (|| -> Result, _> { - Result::<_, ConfigError>::Ok({ - let mut tags = client - .tag_status()? - .into_iter() - .map(|tag| { - let tag_result: Result<_, Infallible> = tag.name().parse(); - tag_result.unwrap() - }) - .collect::>(); - tags.sort_by(|lhs: &Tag, rhs| match lhs.partial_cmp(rhs) { - Some(ord) => ord, - None => Ordering::Less, - }); - - tags - }) - })() - .unwrap_or_log(default.tags), + keybinds: environ::active_keybinds(ActiveKeybinds::OmitNamedTagBinds) + .unwrap_or_log(default.keybinds), + tags: Self::active_tags(mod_key).unwrap_or_log(default.tags), rules: (|| -> Result, ConfigError> { Ok( String::from_utf8(client.execute(HlwmCommand::ListRules)?.stdout)? @@ -488,36 +455,75 @@ impl Config { } } - fn active_keybinds(omit_tag_binds: bool) -> Result, ConfigError> { - String::from_utf8(Client::new().execute(HlwmCommand::ListKeybinds)?.stdout)? - .split("\n") - .filter(|i| { - !omit_tag_binds - || match i.split("\t").skip(1).next() { - Some(command) => { - command - != HlwmCommand::UseIndex { - index: Index::Absolute(0), - skip_visible: false, - } - .to_string() - && command - != HlwmCommand::MoveIndex { - index: Index::Absolute(0), - skip_visible: false, - } - .to_string() - && command != HlwmCommand::UseTag(String::new()).to_string() - && command != HlwmCommand::MoveTag(String::new()).to_string() - } - None => false, + fn active_tags(mod_key: Key) -> Result, ConfigError> { + let mut by_tag: HashMap> = HashMap::new(); + environ::active_keybinds(ActiveKeybinds::OnlyNamedTagBinds)? + .into_iter() + .filter(|bind| match bind.command.deref() { + HlwmCommand::UseTag(_) | HlwmCommand::MoveTag(_) => true, + _ => false, + }) + .for_each(|bind| match bind.command.deref() { + HlwmCommand::UseTag(tag) | HlwmCommand::MoveTag(tag) => match by_tag.get_mut(tag) { + Some(coll) => coll.push(bind.clone()), + None => { + by_tag.insert(tag.to_string(), vec![bind]); } + }, + _ => unreachable!(), + }); + Ok(by_tag + .into_iter() + .map(|(name, binds)| { + let standard = binds.len() != 0 + && (&binds).into_iter().all(|bind| match bind.command.deref() { + HlwmCommand::UseTag(_) => { + bind.keys.len() == 2 + && bind.keys[0] == mod_key + && bind.keys[1].is_standard() + } + HlwmCommand::MoveTag(_) => { + bind.keys.len() == 3 + && bind.keys[0] == mod_key + && bind.keys[1] == Key::Shift + && bind.keys[2].is_standard() + } + // Filtered out above + _ => unreachable!(), + }); + + if standard { + let key = *binds.first().unwrap().keys.last().unwrap(); + let key_same = (&binds) + .into_iter() + .all(|bind| bind.keys.last().unwrap().eq(&key)); + if key_same { + // Actually standard + return Tag::standard(name, key); + } + } + + let mut use_keys = None; + let mut move_keys = None; + for bind in binds { + match bind.command.deref() { + HlwmCommand::UseTag(_) => { + if use_keys.is_none() { + use_keys = Some(bind.keys); + } + } + HlwmCommand::MoveTag(_) => { + if move_keys.is_none() { + move_keys = Some(bind.keys); + } + } + // Filtered out above + _ => unreachable!(), + } + } + Tag::other(name, use_keys, move_keys) }) - .map(|row: &str| { - Keybind::from_str(row) - .map_err(|err| ConfigError::KeyParseError(row.to_string(), err)) - }) - .collect::, ConfigError>>() + .collect()) } } @@ -565,198 +571,6 @@ impl Theme { } } -#[derive(Debug, Clone, Copy, PartialEq, Error)] -pub enum InclusiveError { - #[error("out of range")] - OutOfRange, -} - -impl Expected for InclusiveError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - InclusiveError::OutOfRange => write!(f, "out of range"), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Inclusive(u8); - -impl Inclusive<10> { - fn char(&self) -> char { - match self.0 { - 1 => '1', - 2 => '2', - 3 => '3', - 4 => '4', - 5 => '5', - 6 => '6', - 7 => '7', - 8 => '8', - 9 => '9', - 0 => '0', - _ => unreachable!(), - } - } - - fn f_key(&self) -> Option { - match self.0 { - 1 => Some(Key::F1), - 2 => Some(Key::F2), - 3 => Some(Key::F3), - 4 => Some(Key::F4), - 5 => Some(Key::F5), - 6 => Some(Key::F6), - 7 => Some(Key::F7), - 8 => Some(Key::F8), - 9 => Some(Key::F9), - _ => None, - } - } -} - -impl Display for Inclusive { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0.to_string()) - } -} - -impl Deref for Inclusive { - type Target = u8; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl TryFrom for Inclusive { - type Error = InclusiveError; - - fn try_from(value: u8) -> Result { - if value > MAX { - return Err(InclusiveError::OutOfRange); - } - Ok(Inclusive(value)) - } -} - -impl From> for u8 { - fn from(value: Inclusive) -> Self { - value.0 - } -} - -impl Serialize for Inclusive { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - u8::serialize(&self.0, serializer) - } -} - -impl<'de, const MAX: u8> Deserialize<'de> for Inclusive { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let val = u8::deserialize(deserializer)?; - if val > MAX { - return Err(serde::de::Error::invalid_value( - Unexpected::Unsigned(val as u64), - &InclusiveError::OutOfRange, - )); - } - - Ok(Inclusive(val)) - } -} - -impl Inclusive {} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum Tag { - FrontRow(Inclusive<10>), - FunctionRow(Inclusive<10>), - Other(String), -} - -impl Tag { - pub fn key(&self) -> Option { - match self { - Tag::FrontRow(idx) => Some(Key::Char(idx.char())), - Tag::FunctionRow(idx) => Some(match idx.f_key() { - Some(f) => f, - None => return None, - }), - Tag::Other(_) => None, - } - } -} - -impl PartialEq for Tag { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::FrontRow(l0), Self::FrontRow(r0)) => l0 == r0, - (Self::FunctionRow(l0), Self::FunctionRow(r0)) => l0 == r0, - (Self::Other(l0), Self::Other(r0)) => l0 == r0, - _ => false, - } - } -} - -impl PartialOrd for Tag { - fn partial_cmp(&self, other: &Self) -> Option { - match (self, other) { - (Tag::FrontRow(lhs), Tag::FrontRow(rhs)) => Some(lhs.0.cmp(&rhs.0)), - (Tag::FrontRow(_), Tag::FunctionRow(_)) => Some(Ordering::Greater), - (Tag::FrontRow(_), Tag::Other(_)) => Some(Ordering::Greater), - (Tag::FunctionRow(_), Tag::FrontRow(_)) => Some(Ordering::Less), - (Tag::FunctionRow(lhs), Tag::FunctionRow(rhs)) => Some(lhs.0.cmp(&rhs.0)), - (Tag::FunctionRow(_), Tag::Other(_)) => Some(Ordering::Greater), - (Tag::Other(_), Tag::FrontRow(_)) | (Tag::Other(_), Tag::FunctionRow(_)) => { - Some(Ordering::Less) - } - (Tag::Other(_), Tag::Other(_)) => None, - } - } -} - -impl FromStr for Tag { - type Err = Infallible; - - fn from_str(s: &str) -> Result { - let mut chars = s.chars(); - let indicator = match chars.next() { - Some(i) => i, - None => return Ok(Self::Other(s.to_string())), - }; - let number = u8::from_str(&chars.next().unwrap_or_default().to_string()).ok(); - if number.is_none() { - return Ok(Self::Other(s.to_string())); - } - let number = match number.unwrap().try_into() { - Ok(number) => number, - Err(_) => return Ok(Self::Other(s.to_string())), - }; - match indicator { - '.' => Ok(Self::FrontRow(number)), - 'F' => Ok(Self::FunctionRow(number)), - _ => unreachable!(), - } - } -} - -impl Display for Tag { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Tag::FrontRow(tag) => write!(f, ".{tag}"), - Tag::FunctionRow(tag) => write!(f, "F{tag}"), - Tag::Other(tag) => f.write_str(tag), - } - } -} - impl Default for Config { fn default() -> Self { let resize_step = 0.1; @@ -915,17 +729,19 @@ impl Default for Config { arguments: vec![String::from("panel")], }, ], - tags: (1..=5) - .into_iter() - .map(|idx| Tag::FrontRow(idx.try_into().unwrap())) - .chain(vec![ - Tag::FunctionRow(1.try_into().unwrap()), - Tag::FunctionRow(2.try_into().unwrap()), - Tag::FunctionRow(3.try_into().unwrap()), - Tag::FunctionRow(4.try_into().unwrap()), - Tag::FunctionRow(5.try_into().unwrap()), - ]) - .collect(), + tags: [ + Tag::standard(".1", Key::Char('1')), + Tag::standard(".2", Key::Char('2')), + Tag::standard(".3", Key::Char('3')), + Tag::standard(".4", Key::Char('4')), + Tag::standard(".5", Key::Char('5')), + Tag::standard("F1", Key::F1), + Tag::standard("F2", Key::F2), + Tag::standard("F3", Key::F3), + Tag::standard("F4", Key::F4), + Tag::standard("F5", Key::F5), + ] + .into(), mousebinds: vec![ Mousebind::new( mod_key, @@ -1019,6 +835,110 @@ pub struct Service { pub arguments: Vec, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum Tag { + Standard { + name: String, + key: Key, + }, + Other { + name: String, + use_keys: Option>, + move_keys: Option>, + }, +} + +impl Tag { + pub fn standard>(name: S, key: Key) -> Self { + Self::Standard { + key, + name: name.into(), + } + } + + pub fn other, K: IntoIterator>( + name: S, + use_keys: Option, + move_keys: Option, + ) -> Self { + Self::Other { + name: name.into(), + use_keys: use_keys.map(|k| k.into_iter().collect()), + move_keys: move_keys.map(|k| k.into_iter().collect()), + } + } + + pub fn name(&self) -> &str { + match self { + Tag::Standard { name, key: _ } => name.as_str(), + Tag::Other { + name, + use_keys: _, + move_keys: _, + } => name.as_str(), + } + } + + fn use_keybind(&self, mod_key: Key) -> Option { + match self { + Tag::Standard { name, key } => Some(Keybind { + keys: vec![mod_key, *key], + command: Box::new(HlwmCommand::UseTag(name.clone())), + }), + Tag::Other { + name, + use_keys, + move_keys: _, + } => use_keys.clone().map(|use_keys| Keybind { + keys: use_keys, + command: Box::new(HlwmCommand::UseTag(name.clone())), + }), + } + } + + fn move_keybind(&self, mod_key: Key) -> Option { + match self { + Tag::Standard { name, key } => Some(Keybind { + keys: vec![mod_key, Key::Shift, *key], + command: Box::new(HlwmCommand::MoveTag(name.clone())), + }), + Tag::Other { + name, + use_keys: _, + move_keys, + } => move_keys.clone().map(|move_keys| Keybind { + keys: move_keys, + command: Box::new(HlwmCommand::MoveTag(name.clone())), + }), + } + } + + pub fn to_command_set(&self, mod_key: Key) -> Vec { + let mut commands = Vec::with_capacity(3); + commands.push(HlwmCommand::AddTag(self.name().to_string())); + if let Some(keybind) = self.use_keybind(mod_key) { + commands.push(HlwmCommand::Keybind(keybind)); + } + if let Some(keybind) = self.move_keybind(mod_key) { + commands.push(HlwmCommand::Keybind(keybind)); + } + + commands + } +} + +impl From<(S, Key)> for Tag +where + S: Into, +{ + fn from((name, key): (S, Key)) -> Self { + Self::Standard { + name: name.into(), + key, + } + } +} + #[cfg(test)] mod test { use pretty_assertions::assert_eq; diff --git a/src/environ.rs b/src/environ.rs new file mode 100644 index 0000000..182f3ee --- /dev/null +++ b/src/environ.rs @@ -0,0 +1,67 @@ +use std::{str::FromStr, string::FromUtf8Error}; + +use log::debug; +use thiserror::Error; + +use crate::{ + hlwm::{ + command::{CommandError, HlwmCommand}, + key::{KeyParseError, Keybind}, + Client, + }, + split, +}; + +#[derive(Clone, Debug, Error)] +pub enum EnvironError { + #[error("keybind parsing error: [{0}]")] + KeyParseError(#[from] KeyParseError), + #[error("command execution error: [{0}]")] + CommandError(#[from] CommandError), +} + +impl From for EnvironError { + fn from(value: FromUtf8Error) -> Self { + CommandError::UtfError(value).into() + } +} + +pub enum ActiveKeybinds { + All, + OmitNamedTagBinds, + OnlyNamedTagBinds, +} + +pub fn active_keybinds(ty: ActiveKeybinds) -> Result, EnvironError> { + let (use_tag, move_tag) = ( + HlwmCommand::UseTag(String::new()).to_string(), + HlwmCommand::MoveTag(String::new()).to_string(), + ); + String::from_utf8(Client::new().execute(HlwmCommand::ListKeybinds)?.stdout)? + .split("\n") + .map(|l| l.trim()) + .filter(|l| !l.is_empty()) + .filter(|i| { + let parts = split::tab_or_space(*i); + if parts.len() < 2 { + debug!( + "active_keybinds: parts.len() for [{i}] was [{}]; expected >= 2", + parts.len() + ); + return false; + } + let command = parts[1].as_str(); + match ty { + ActiveKeybinds::All => true, + ActiveKeybinds::OmitNamedTagBinds => command != use_tag && command != move_tag, + ActiveKeybinds::OnlyNamedTagBinds => command == use_tag || command == move_tag, + } + }) + .map(|row: &str| { + Ok(Keybind::from_str(row).map_err(|err| { + debug!("row: [{row}], error: [{err}]"); + err + })?) + }) + .collect::, EnvironError>>() +} diff --git a/src/hlwm/command.rs b/src/hlwm/command.rs index eaae01e..9ccd958 100644 --- a/src/hlwm/command.rs +++ b/src/hlwm/command.rs @@ -4,7 +4,7 @@ use strum::IntoEnumIterator; use serde::{de::Expected, Deserialize, Serialize}; use thiserror::Error; -use crate::{gen_parse, hlwm::Client}; +use crate::{gen_parse, hlwm::Client, split}; use super::{ attribute::{Attribute, AttributeError, AttributeOption}, @@ -14,9 +14,9 @@ use super::{ pad::Pad, rule::{Rule, Unrule}, setting::{FrameLayout, Setting, SettingName}, - split, window::Window, - Align, Direction, Index, Monitor, Operator, Separator, StringParseError, Tag, ToCommandString, + Align, Direction, Index, Monitor, Operator, Separator, StringParseError, TagSelect, + ToCommandString, }; #[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq)] @@ -131,7 +131,7 @@ pub enum HlwmCommand { /// If `target` is None, the focused tag will be used MergeTag { tag: String, - target: Option, + target: Option, }, Cycle, Focus(Direction), @@ -848,7 +848,7 @@ mod test { rule::{Condition, Consequence, Rule, RuleOperator}, setting::{Setting, SettingName}, window::Window, - Align, Direction, Tag, ToCommandString, + Align, Direction, TagSelect, ToCommandString, }; use pretty_assertions::assert_eq; @@ -984,7 +984,7 @@ mod test { HlwmCommand::MergeTag { tag: _, target: _ } => ( HlwmCommand::MergeTag { tag: "my_tag".into(), - target: Some(Tag::Name("other_tag".into())), + target: Some(TagSelect::Name("other_tag".into())), }, "merge_tag\tmy_tag\tother_tag".into(), ), diff --git a/src/hlwm/hook.rs b/src/hlwm/hook.rs index ebb6ee4..454677e 100644 --- a/src/hlwm/hook.rs +++ b/src/hlwm/hook.rs @@ -5,7 +5,7 @@ use strum::IntoEnumIterator; use crate::gen_parse; -use super::{command::CommandParseError, window::Window, Monitor, Tag, ToCommandString}; +use super::{command::CommandParseError, window::Window, Monitor, TagSelect, ToCommandString}; #[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumIter, PartialEq)] #[strum(serialize_all = "snake_case")] @@ -38,7 +38,7 @@ pub enum Hook { }, /// The flags (i.e. urgent or filled state) have been changed. TagFlags, - TagAdded(Tag), + TagAdded(TagSelect), TagRenamed { old: String, new: String, diff --git a/src/hlwm/key.rs b/src/hlwm/key.rs index 057d45c..60b92c7 100644 --- a/src/hlwm/key.rs +++ b/src/hlwm/key.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use thiserror::Error; -use crate::hlwm::split; +use crate::split; use super::{ command::{CommandParseError, HlwmCommand}, @@ -43,6 +43,39 @@ pub enum Key { Mouse(MouseButton), } +impl Key { + pub fn is_standard(&self) -> bool { + match self { + Key::Char(_) + | Key::F1 + | Key::F2 + | Key::F3 + | Key::F4 + | Key::F5 + | Key::F6 + | Key::F7 + | Key::F8 + | Key::F9 + | Key::F10 + | Key::F11 + | Key::F12 => true, + _ => false, + } + } +} + +impl From for Key { + fn from(value: MouseButton) -> Self { + Key::Mouse(value) + } +} + +impl From for Key { + fn from(value: char) -> Self { + Key::Char(value) + } +} + impl Serialize for Key { fn serialize(&self, serializer: S) -> Result where diff --git a/src/hlwm/mod.rs b/src/hlwm/mod.rs index d784a50..cfe176b 100644 --- a/src/hlwm/mod.rs +++ b/src/hlwm/mod.rs @@ -18,7 +18,7 @@ use self::{ command::{CommandError, HlwmCommand}, key::KeyParseError, setting::{Setting, SettingName}, - window::TagStatus, + tag::TagStatus, }; pub mod attribute; @@ -32,7 +32,7 @@ mod octal; pub mod pad; pub mod rule; pub mod setting; -mod split; +pub mod tag; pub mod theme; pub mod window; #[macro_use] @@ -128,6 +128,7 @@ impl Client { Ok(lines) } + #[allow(unused)] pub fn tag_status(&self) -> Result, CommandError> { Ok(self .query(HlwmCommand::TagStatus { monitor: None })? @@ -167,12 +168,12 @@ pub enum StringParseError { } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub enum Tag { +pub enum TagSelect { Index(i32), Name(String), } -impl FromStr for Tag { +impl FromStr for TagSelect { type Err = Infallible; fn from_str(s: &str) -> Result { @@ -184,17 +185,17 @@ impl FromStr for Tag { } } -impl Default for Tag { +impl Default for TagSelect { fn default() -> Self { Self::Index(Default::default()) } } -impl std::fmt::Display for Tag { +impl std::fmt::Display for TagSelect { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Tag::Index(i) => write!(f, "{i}"), - Tag::Name(name) => f.write_str(name), + TagSelect::Index(i) => write!(f, "{i}"), + TagSelect::Name(name) => f.write_str(name), } } } diff --git a/src/hlwm/pad.rs b/src/hlwm/pad.rs index aafb723..7a3279e 100644 --- a/src/hlwm/pad.rs +++ b/src/hlwm/pad.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, str::FromStr}; -use crate::hlwm::split; +use crate::split; use super::StringParseError; diff --git a/src/hlwm/rule.rs b/src/hlwm/rule.rs index 66f3370..1d79b70 100644 --- a/src/hlwm/rule.rs +++ b/src/hlwm/rule.rs @@ -8,7 +8,9 @@ use std::{ use serde::{de::Expected, Deserialize, Serialize}; use strum::IntoEnumIterator; -use super::{hlwmbool, hook::Hook, split, StringParseError, ToCommandString}; +use crate::split; + +use super::{hlwmbool, hook::Hook, StringParseError, ToCommandString}; #[derive(Debug, Clone, PartialEq)] pub struct Rule { diff --git a/src/hlwm/setting.rs b/src/hlwm/setting.rs index e86503d..e588682 100644 --- a/src/hlwm/setting.rs +++ b/src/hlwm/setting.rs @@ -3,15 +3,10 @@ use std::str::FromStr; use log::debug; use serde::{Deserialize, Serialize}; -use crate::{gen_parse, hlwm::command::CommandParseError}; +use crate::{gen_parse, hlwm::command::CommandParseError, split}; use strum::IntoEnumIterator; -use super::{ - color::Color, - hlwmbool::ToggleBool, - split::{self}, - StringParseError, ToCommandString, -}; +use super::{color::Color, hlwmbool::ToggleBool, StringParseError, ToCommandString}; #[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq, strum::EnumDiscriminants)] #[strum_discriminants( diff --git a/src/hlwm/tag.rs b/src/hlwm/tag.rs new file mode 100644 index 0000000..84c82b3 --- /dev/null +++ b/src/hlwm/tag.rs @@ -0,0 +1,98 @@ +use std::{ + fmt::{Display, Write}, + str::FromStr, +}; + +use serde::{Deserialize, Serialize}; +use strum::IntoEnumIterator; + +use super::StringParseError; + +pub struct TagStatus { + name: String, + state: TagState, +} + +#[allow(unused)] +impl TagStatus { + pub fn name(&self) -> &str { + &self.name + } + + pub fn state(&self) -> TagState { + self.state + } +} + +impl FromStr for TagStatus { + type Err = StringParseError; + + fn from_str(s: &str) -> Result { + let mut parts = s.chars(); + let state = parts + .next() + .ok_or(StringParseError::UnknownValue)? + .try_into()?; + let name = parts.collect(); + + Ok(Self { name, state }) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, strum::EnumIter)] +pub enum TagState { + Empty, + NotEmpty, + /// The tag contains an urgent window + Urgent, + /// The tag is viewed on the specified MONITOR and it is focused + SameMonitorFocused, + /// The tag is viewed on the specified MONITOR, but this monitor is not focused + SameMonitor, + /// The tag is viewed on a different MONITOR and it is focused + DifferentMonitorFocused, + /// The tag is viewed on a different MONITOR, but this monitor is not focused + DifferentMonitor, +} + +impl TryFrom for TagState { + type Error = StringParseError; + + fn try_from(value: char) -> Result { + Self::iter() + .into_iter() + .find(|i| char::from(i) == value) + .ok_or(StringParseError::UnknownValue) + } +} + +impl FromStr for TagState { + type Err = StringParseError; + + fn from_str(s: &str) -> Result { + Self::iter() + .into_iter() + .find(|i| i.to_string() == s) + .ok_or(StringParseError::UnknownValue) + } +} + +impl From<&TagState> for char { + fn from(value: &TagState) -> Self { + match value { + TagState::SameMonitorFocused => '#', + TagState::SameMonitor => '+', + TagState::DifferentMonitorFocused => '%', + TagState::DifferentMonitor => '-', + TagState::Empty => '.', + TagState::NotEmpty => ':', + TagState::Urgent => '!', + } + } +} + +impl Display for TagState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_char(self.into()) + } +} diff --git a/src/hlwm/theme.rs b/src/hlwm/theme.rs index 158a080..5e3101c 100644 --- a/src/hlwm/theme.rs +++ b/src/hlwm/theme.rs @@ -4,7 +4,7 @@ use serde::{de::Expected, Deserialize, Serialize}; use strum::IntoEnumIterator; use thiserror::Error; -use crate::hlwm::split; +use crate::split; use super::{ attribute::Attribute, diff --git a/src/hlwm/window.rs b/src/hlwm/window.rs index d2e7af9..aa18e64 100644 --- a/src/hlwm/window.rs +++ b/src/hlwm/window.rs @@ -1,7 +1,4 @@ -use std::{ - fmt::{Display, Write}, - str::FromStr, -}; +use std::str::FromStr; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; @@ -60,95 +57,6 @@ impl std::fmt::Display for Window { } } -pub struct TagStatus { - name: String, - state: TagState, -} - -#[allow(unused)] -impl TagStatus { - pub fn name(&self) -> &str { - &self.name - } - - pub fn state(&self) -> TagState { - self.state - } -} - -impl FromStr for TagStatus { - type Err = StringParseError; - - fn from_str(s: &str) -> Result { - let mut parts = s.chars(); - let state = parts - .next() - .ok_or(StringParseError::UnknownValue)? - .try_into()?; - let name = parts.collect(); - - Ok(Self { name, state }) - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, strum::EnumIter)] -pub enum TagState { - Empty, - NotEmpty, - /// The tag contains an urgent window - Urgent, - /// The tag is viewed on the specified MONITOR and it is focused - SameMonitorFocused, - /// The tag is viewed on the specified MONITOR, but this monitor is not focused - SameMonitor, - /// The tag is viewed on a different MONITOR and it is focused - DifferentMonitorFocused, - /// The tag is viewed on a different MONITOR, but this monitor is not focused - DifferentMonitor, -} - -impl TryFrom for TagState { - type Error = StringParseError; - - fn try_from(value: char) -> Result { - Self::iter() - .into_iter() - .find(|i| char::from(i) == value) - .ok_or(StringParseError::UnknownValue) - } -} - -impl FromStr for TagState { - type Err = StringParseError; - - fn from_str(s: &str) -> Result { - Self::iter() - .into_iter() - .find(|i| i.to_string() == s) - .ok_or(StringParseError::UnknownValue) - } -} - -impl From<&TagState> for char { - fn from(value: &TagState) -> Self { - match value { - TagState::SameMonitorFocused => '#', - TagState::SameMonitor => '+', - TagState::DifferentMonitorFocused => '%', - TagState::DifferentMonitor => '-', - TagState::Empty => '.', - TagState::NotEmpty => ':', - TagState::Urgent => '!', - } - } -} - -impl Display for TagState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_char(self.into()) - } -} - #[derive(Clone, Copy, strum::Display)] #[strum(serialize_all = "UPPERCASE")] pub enum WindowType { diff --git a/src/main.rs b/src/main.rs index 63142b5..680fcb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,13 +4,14 @@ use std::path::{Path, PathBuf}; use clap::{Parser, Subcommand}; use config::Config; use log::{error, info}; -use logerr::UnwrapLog; pub mod cmd; mod config; +pub mod environ; mod hlwm; pub mod logerr; mod panel; +pub mod split; #[derive(Parser, Debug, Clone)] #[command(name = "hlctl")] @@ -26,7 +27,8 @@ enum HlctlCommand { /// Save the currently loaded configuration to file #[command(long_about = r#" `save` Tries to find an existing config file. If not present, the default config is used. - Whichever one is loaded, its tags are used. All other values are collected from the environment + Whichever one is loaded, its mousebinds and attribute set are used. + All other values are collected from the environment (or default values) and this new config is saved. The configuration file located at $HOME/.config/herbstluftwm/hlctl.toml"#)] @@ -59,8 +61,8 @@ fn load_config() -> Config { Err(err) => { error!("Could not load config. Error: {err}"); error!(""); - error!("Hint: try calling `hlctl save` to save a default or collected config"); - std::process::exit(1); + error!("Loading default config"); + Config::default() } } } @@ -97,7 +99,8 @@ fn init() { fn merged_config() -> Config { let default = load_config(); let mut collected = Config::from_herbstluft(); - collected.tags = default.tags; + collected.mousebinds = default.mousebinds; + collected.attributes = default.attributes; collected } diff --git a/src/hlwm/split.rs b/src/split.rs similarity index 100% rename from src/hlwm/split.rs rename to src/split.rs