use std::{borrow::BorrowMut, fmt::Display, str::FromStr}; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use crate::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), } 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::, _>>() } } 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.to_string()) } } 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(&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| key.to_string() == s) { match key { Key::Char(_) | Key::Mouse(_) => (), _ => return Ok(key), } } if s.len() == 1 { Ok(Self::Char(s.chars().next().unwrap())) } 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"), } } } impl Display for Key { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let data = match self { Key::Return => "Return".to_string(), Key::Shift => "Shift".to_string(), Key::Tab => "Tab".to_string(), Key::Left => "Left".to_string(), Key::Right => "Right".to_string(), Key::Up => "Up".to_string(), Key::Down => "Down".to_string(), Key::Space => "space".to_string(), Key::Control => "Control".to_string(), Key::Backtick => "grave".to_string(), Key::Char(c) => c.to_string(), Key::Mouse(m) => m.to_string(), Key::F1 => "F1".to_string(), Key::F2 => "F2".to_string(), Key::F3 => "F3".to_string(), Key::F4 => "F4".to_string(), Key::F5 => "F5".to_string(), Key::F6 => "F6".to_string(), Key::F7 => "F7".to_string(), Key::F8 => "F8".to_string(), Key::F9 => "F9".to_string(), Key::F10 => "F10".to_string(), Key::F11 => "F11".to_string(), Key::F12 => "F12".to_string(), Key::Home => "Home".to_string(), Key::Delete => "Delete".to_string(), Key::Mod1Alt => "Mod1".to_string(), Key::Mod4Super => "Mod4".to_string(), }; f.write_str(&data) } } #[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; use super::{Key, MousebindAction}; #[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).unwrap(); let parsed: KeyWrapper = toml::from_str(&wrapper_str).unwrap(); 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); } } }