tag rework: tags now can either be standard or other, supports all types better
This commit is contained in:
		
							parent
							
								
									e4bcc52cbe
								
							
						
					
					
						commit
						f0930ab2eb
					
				
							
								
								
									
										480
									
								
								src/config.rs
								
								
								
								
							
							
						
						
									
										480
									
								
								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<T, E: std::error::Error, F: FnOnce(&str) -> Result<T, E>>(
 | 
			
		||||
            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<Vec<Tag>, _> {
 | 
			
		||||
                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::<Vec<_>>();
 | 
			
		||||
                    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<Vec<Rule>, ConfigError> {
 | 
			
		||||
                Ok(
 | 
			
		||||
                    String::from_utf8(client.execute(HlwmCommand::ListRules)?.stdout)?
 | 
			
		||||
| 
						 | 
				
			
			@ -488,36 +455,75 @@ impl Config {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn active_keybinds(omit_tag_binds: bool) -> Result<Vec<Keybind>, 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<Vec<Tag>, ConfigError> {
 | 
			
		||||
        let mut by_tag: HashMap<String, Vec<Keybind>> = 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::<Result<Vec<_>, 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<const MAX: u8>(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<Key> {
 | 
			
		||||
        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<const MAX: u8> Display for Inclusive<MAX> {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        write!(f, "{}", self.0.to_string())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<const MAX: u8> Deref for Inclusive<MAX> {
 | 
			
		||||
    type Target = u8;
 | 
			
		||||
 | 
			
		||||
    fn deref(&self) -> &Self::Target {
 | 
			
		||||
        &self.0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<const MAX: u8> TryFrom<u8> for Inclusive<MAX> {
 | 
			
		||||
    type Error = InclusiveError;
 | 
			
		||||
 | 
			
		||||
    fn try_from(value: u8) -> Result<Self, Self::Error> {
 | 
			
		||||
        if value > MAX {
 | 
			
		||||
            return Err(InclusiveError::OutOfRange);
 | 
			
		||||
        }
 | 
			
		||||
        Ok(Inclusive(value))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<const MAX: u8> From<Inclusive<MAX>> for u8 {
 | 
			
		||||
    fn from(value: Inclusive<MAX>) -> Self {
 | 
			
		||||
        value.0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<const MAX: u8> Serialize for Inclusive<MAX> {
 | 
			
		||||
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
 | 
			
		||||
    where
 | 
			
		||||
        S: serde::Serializer,
 | 
			
		||||
    {
 | 
			
		||||
        u8::serialize(&self.0, serializer)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'de, const MAX: u8> Deserialize<'de> for Inclusive<MAX> {
 | 
			
		||||
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 | 
			
		||||
    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<const MAX: u8> Inclusive<MAX> {}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Serialize, Deserialize)]
 | 
			
		||||
pub enum Tag {
 | 
			
		||||
    FrontRow(Inclusive<10>),
 | 
			
		||||
    FunctionRow(Inclusive<10>),
 | 
			
		||||
    Other(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Tag {
 | 
			
		||||
    pub fn key(&self) -> Option<Key> {
 | 
			
		||||
        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<Ordering> {
 | 
			
		||||
        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<Self, Self::Err> {
 | 
			
		||||
        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<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
pub enum Tag {
 | 
			
		||||
    Standard {
 | 
			
		||||
        name: String,
 | 
			
		||||
        key: Key,
 | 
			
		||||
    },
 | 
			
		||||
    Other {
 | 
			
		||||
        name: String,
 | 
			
		||||
        use_keys: Option<Vec<Key>>,
 | 
			
		||||
        move_keys: Option<Vec<Key>>,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Tag {
 | 
			
		||||
    pub fn standard<S: Into<String>>(name: S, key: Key) -> Self {
 | 
			
		||||
        Self::Standard {
 | 
			
		||||
            key,
 | 
			
		||||
            name: name.into(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn other<S: Into<String>, K: IntoIterator<Item = Key>>(
 | 
			
		||||
        name: S,
 | 
			
		||||
        use_keys: Option<K>,
 | 
			
		||||
        move_keys: Option<K>,
 | 
			
		||||
    ) -> 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<Keybind> {
 | 
			
		||||
        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<Keybind> {
 | 
			
		||||
        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<HlwmCommand> {
 | 
			
		||||
        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<S> From<(S, Key)> for Tag
 | 
			
		||||
where
 | 
			
		||||
    S: Into<String>,
 | 
			
		||||
{
 | 
			
		||||
    fn from((name, key): (S, Key)) -> Self {
 | 
			
		||||
        Self::Standard {
 | 
			
		||||
            name: name.into(),
 | 
			
		||||
            key,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use pretty_assertions::assert_eq;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<FromUtf8Error> for EnvironError {
 | 
			
		||||
    fn from(value: FromUtf8Error) -> Self {
 | 
			
		||||
        CommandError::UtfError(value).into()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub enum ActiveKeybinds {
 | 
			
		||||
    All,
 | 
			
		||||
    OmitNamedTagBinds,
 | 
			
		||||
    OnlyNamedTagBinds,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn active_keybinds(ty: ActiveKeybinds) -> Result<Vec<Keybind>, 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::<Result<Vec<_>, EnvironError>>()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<Tag>,
 | 
			
		||||
        target: Option<TagSelect>,
 | 
			
		||||
    },
 | 
			
		||||
    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(),
 | 
			
		||||
                ),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<MouseButton> for Key {
 | 
			
		||||
    fn from(value: MouseButton) -> Self {
 | 
			
		||||
        Key::Mouse(value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<char> for Key {
 | 
			
		||||
    fn from(value: char) -> Self {
 | 
			
		||||
        Key::Char(value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Serialize for Key {
 | 
			
		||||
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
 | 
			
		||||
    where
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Vec<TagStatus>, 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<Self, Self::Err> {
 | 
			
		||||
| 
						 | 
				
			
			@ -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),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
use std::{fmt::Display, str::FromStr};
 | 
			
		||||
 | 
			
		||||
use crate::hlwm::split;
 | 
			
		||||
use crate::split;
 | 
			
		||||
 | 
			
		||||
use super::StringParseError;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Self, Self::Err> {
 | 
			
		||||
        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<char> for TagState {
 | 
			
		||||
    type Error = StringParseError;
 | 
			
		||||
 | 
			
		||||
    fn try_from(value: char) -> Result<Self, Self::Error> {
 | 
			
		||||
        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, Self::Err> {
 | 
			
		||||
        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())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Self, Self::Err> {
 | 
			
		||||
        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<char> for TagState {
 | 
			
		||||
    type Error = StringParseError;
 | 
			
		||||
 | 
			
		||||
    fn try_from(value: char) -> Result<Self, Self::Error> {
 | 
			
		||||
        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, Self::Err> {
 | 
			
		||||
        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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										13
									
								
								src/main.rs
								
								
								
								
							
							
						
						
									
										13
									
								
								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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue