use std::{borrow::BorrowMut, fmt::Display, str::FromStr}; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use crate::{logerr::UnwrapLog, split}; use super::{ command::HlwmCommand, parser::{ArgParser, FromCommandArgs, FromStrings, ParseError}, ToCommandString, }; #[derive(Debug, Clone, Copy, PartialEq, strum::EnumIter)] pub enum Key { Mod1Alt, Mod4Super, Return, Shift, Tab, Left, Right, Up, Down, Space, Control, Backtick, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, Home, Delete, Char(char), Mouse(MouseButton), } fn title_case(s: &str) -> String { let mut chars = s.chars(); let mut out_chars = Vec::with_capacity(s.len()); if let Some(first) = chars.next() { out_chars.push(first.to_ascii_uppercase()); } while let Some(c) = chars.next() { out_chars.push(c.to_ascii_lowercase()); } out_chars.into_iter().collect() } 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, } } pub fn parse_keybind_keys(s: &str) -> Result, ParseError> { s.split(['-', '+']) .map(Key::from_str) .collect::, _>>() } pub fn from_str_case_insensitive(s: &str) -> Result { Key::from_str(&title_case(s)) } /// Returns the name for this key as it should appear in the config pub fn config_name(&self) -> String { self.aliases() .first() .expect_log("all keys should have at least one alias") .to_string() } /// Returns a list of aliases for the key pub fn aliases(&self) -> Vec { match self { Key::Mod1Alt => vec!["Alt".into(), "Mod1".into()], Key::Mod4Super => vec!["Super".into(), "Mod4".into()], Key::Return => vec!["Return".into(), "Enter".into()], Key::Shift => vec!["Shift".into()], Key::Tab => vec!["Tab".into()], Key::Left => vec!["Left".into()], Key::Right => vec!["Right".into()], Key::Up => vec!["Up".into()], Key::Down => vec!["Down".into()], Key::Space => vec!["Space".into()], Key::Control => vec!["Ctrl".into(), "Ctl".into(), "Control".into()], Key::Backtick => vec!["Backtick".into(), "Grave".into()], Key::F1 => vec!["F1".into()], Key::F2 => vec!["F2".into()], Key::F3 => vec!["F3".into()], Key::F4 => vec!["F4".into()], Key::F5 => vec!["F5".into()], Key::F6 => vec!["F6".into()], Key::F7 => vec!["F7".into()], Key::F8 => vec!["F8".into()], Key::F9 => vec!["F9".into()], Key::F10 => vec!["F10".into()], Key::F11 => vec!["F11".into()], Key::F12 => vec!["F12".into()], Key::Home => vec!["Home".into()], Key::Delete => vec!["Delete".into(), "Del".into()], Key::Char(c) => vec![c.to_ascii_lowercase().to_string()], Key::Mouse(m) => vec![m.to_string()], } } } impl Display for Key { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.aliases().first().unwrap()) } } 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 S: serde::Serializer, { serializer.serialize_str(&self.config_name()) } } impl<'de> Deserialize<'de> for Key { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct ExpectedKey; impl serde::de::Expected for ExpectedKey { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("Expected a supported key") } } let str_val: String = Deserialize::deserialize(deserializer)?; Ok(Self::from_str_case_insensitive(&str_val).map_err(|_| { serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &ExpectedKey) })?) } } impl FromStr for Key { type Err = ParseError; fn from_str(s: &str) -> Result { match s { "Button1" | "Button2" | "Button3" | "Button4" | "Button5" => { Ok(Self::Mouse(MouseButton::from_str(s)?)) } _ => { if let Some(key) = Self::iter().into_iter().find(|key| match key { Key::Char(_) | Key::Mouse(_) => false, _ => key.aliases().contains(&s.to_string()), }) { return Ok(key); } if s.len() == 1 { Ok(Self::Char( s.chars().next().unwrap_log().to_ascii_lowercase(), )) } else { Err(ParseError::InvalidValue { value: s.to_string(), expected: "a valid key", }) } } } } } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] pub enum MouseButton { Button1, Button2, Button3, Button4, Button5, } impl Default for MouseButton { fn default() -> Self { Self::Button1 } } impl FromStr for MouseButton { type Err = ParseError; fn from_str(s: &str) -> Result { match s { "Button1" => Ok(Self::Button1), "Button2" => Ok(Self::Button2), "Button3" => Ok(Self::Button3), "Button4" => Ok(Self::Button4), "Button5" => Ok(Self::Button5), _ => Err(ParseError::InvalidCommand(s.to_string())), } } } impl Display for MouseButton { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { MouseButton::Button1 => write!(f, "Button1"), MouseButton::Button2 => write!(f, "Button2"), MouseButton::Button3 => write!(f, "Button3"), MouseButton::Button4 => write!(f, "Button4"), MouseButton::Button5 => write!(f, "Button5"), } } } #[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq)] #[strum(serialize_all = "snake_case")] pub enum MousebindAction { Move, Resize, Zoom, Call(Box), } impl Serialize for MousebindAction { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&self.to_command_string()) } } impl<'de> Deserialize<'de> for MousebindAction { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct ExpectedKey; impl serde::de::Expected for ExpectedKey { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("Expected a supported key") } } let str_val: String = Deserialize::deserialize(deserializer)?; Ok(Self::from_str(&str_val).map_err(|_| { serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &ExpectedKey) })?) } } impl FromCommandArgs for MousebindAction { fn from_command_args, I: Iterator>( command: &str, args: I, ) -> Result { let mut action = Self::iter() .find(|i| i.to_string() == command) .ok_or(ParseError::InvalidCommand(command.to_string()))?; match action.borrow_mut() { MousebindAction::Move | MousebindAction::Resize | MousebindAction::Zoom => (), MousebindAction::Call(arg) => { *arg = Box::new( ArgParser::from_strings(args.map(|a| a.into())) .collect_command("mousebind_args(command)")?, ) } } Ok(action) } } impl FromStr for MousebindAction { type Err = ParseError; fn from_str(s: &str) -> Result { let mut parser = ArgParser::from_strings(split::tab_or_space(s).into_iter()); let action_str = parser.must_string("mousebind_action(action)")?; let mut action = Self::iter() .find(|i| i.to_string() == action_str) .ok_or(ParseError::InvalidCommand(s.to_string()))?; match action.borrow_mut() { MousebindAction::Move | MousebindAction::Resize | MousebindAction::Zoom => (), MousebindAction::Call(command) => { *command = Box::new(parser.collect_command("mousebind_action(command)")?); } } Ok(action) } } impl Default for MousebindAction { fn default() -> Self { Self::Move } } impl ToCommandString for MousebindAction { fn to_command_string(&self) -> String { match self { MousebindAction::Move | MousebindAction::Resize | MousebindAction::Zoom => { self.to_string() } MousebindAction::Call(cmd) => format!("{self}\t{}", cmd.to_string()), } } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Keybind { pub keys: Vec, pub command: Box, } impl Default for Keybind { fn default() -> Self { Self { keys: Default::default(), command: Default::default(), } } } impl FromStrings for Keybind { fn from_strings>(s: I) -> Result { let mut parser = ArgParser::from_strings(s); Ok(Self { keys: Key::parse_keybind_keys(&parser.must_string("keybind(keys)")?)?, command: Box::new(parser.collect_command("keybind(command)")?), }) } } impl ToCommandString for Keybind { fn to_command_string(&self) -> String { format!("{self}\t{}", self.command.to_command_string()) } } impl Keybind { pub fn new>(keys: I, command: HlwmCommand) -> Self { Self { keys: keys.into_iter().collect(), command: Box::new(command), } } } impl Display for Keybind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str( &(&self.keys) .into_iter() .map(|key| key.to_string()) .collect::>() .join("+"), ) } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Mousebind { pub keys: Vec, pub action: MousebindAction, } impl FromStrings for Mousebind { fn from_strings>(s: I) -> Result { let mut parser = ArgParser::from_strings(s); let keys = Key::parse_keybind_keys(&parser.must_string("mousebind(keys)")?)?; let action: MousebindAction = parser.collect_command("mousebind(action)")?; Ok(Self { keys, action }) } } impl Mousebind { pub fn new>(mod_key: Key, keys: I, action: MousebindAction) -> Self { let keys = [mod_key].into_iter().chain(keys).collect(); Self { keys, action } } } impl Default for Mousebind { fn default() -> Self { Self { keys: Default::default(), action: Default::default(), } } } impl Display for Mousebind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{keys}\t{action}", keys = (&self.keys) .into_iter() .map(|key| key.to_string()) .collect::>() .join("-"), action = self.action.to_command_string(), ) } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum KeyUnbind { Keybind(Vec), All, } impl FromStr for KeyUnbind { type Err = ParseError; fn from_str(s: &str) -> Result { match s { "--all" | "-F" => Ok(Self::All), _ => Ok(Self::Keybind(Key::parse_keybind_keys(s)?)), } } } impl Default for KeyUnbind { fn default() -> Self { Self::All } } impl Display for KeyUnbind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { KeyUnbind::Keybind(keys) => f.write_str( &keys .into_iter() .map(|k| k.to_string()) .collect::>() .join("-"), ), KeyUnbind::All => f.write_str("--all"), } } } #[cfg(test)] mod test { use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use crate::{hlwm::command::HlwmCommand, logerr::UnwrapLog}; use super::{Key, MousebindAction}; use pretty_assertions::assert_eq; #[derive(Debug, Serialize, Deserialize)] pub struct KeyWrapper { key: Key, } #[test] fn key_serialize_deserialize() { for (original_key, wrapper) in Key::iter().map(|key| (key, KeyWrapper { key })) { let wrapper_str = toml::to_string_pretty(&wrapper).expect_log("serialize"); let parsed: KeyWrapper = toml::from_str(&wrapper_str).expect_log("deserialize"); assert_eq!(original_key, parsed.key); } } #[derive(Debug, Serialize, Deserialize)] pub struct MousebindActionWrapper { action: MousebindAction, } #[test] fn mousebindaction_serialize_deserialize() { for (original_key, wrapper) in [ MousebindAction::Call(Box::new(HlwmCommand::Cycle)), MousebindAction::Move, MousebindAction::Resize, MousebindAction::Zoom, ] .map(|action| (action.clone(), MousebindActionWrapper { action })) { let wrapper_str = toml::to_string_pretty(&wrapper).unwrap(); let parsed: MousebindActionWrapper = toml::from_str(&wrapper_str).unwrap(); assert_eq!(original_key, parsed.action); } } fn case_random>(s: S) -> Vec { let s = s.into(); let counts: usize = s.chars().map(|c| c.is_ascii_alphabetic() as usize).sum(); if counts < 2 { return vec![s.to_string()]; } let mut variants: Vec = Vec::with_capacity(counts + 2); let mut working = Vec::new(); for idx in 0..counts { let mut c_idx = 0; for c in s.chars() { if !c.is_ascii_alphabetic() { working.push(c); continue; } if c_idx == idx { working.push(c.to_ascii_uppercase()); } else { working.push(c.to_ascii_lowercase()); } c_idx += 1; } variants.push(working.into_iter().collect()); working = Vec::new(); } variants.push(s.to_ascii_lowercase()); variants.push(s.to_ascii_uppercase()); variants } #[test] fn key_from_case_insensitive() { let test_cases = Key::iter().map(|key| { let strings = key .aliases() .into_iter() .map(|a| case_random(a)) .flatten() .collect::>(); (strings, key) }); for (strings, key) in test_cases { for case in strings { println!("comparing str: [{case}] with key: [{key}]"); let parsed = Key::from_str_case_insensitive(&case).expect(&format!( "key [{key}] parse from insensitive string [{case}] failed" )); assert_eq!(key, parsed); } } } }