use std::{fmt::Display, str::FromStr}; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use thiserror::Error; use crate::hlwm::split; use super::{ command::{CommandParseError, HlwmCommand}, StringParseError, 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 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 = KeyParseError; 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(KeyParseError::ExpectedCharKey(s.to_string())) } } } } } #[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 = StringParseError; 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(StringParseError::UnknownValue), } } } 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 FromStr for MousebindAction { type Err = StringParseError; fn from_str(s: &str) -> Result { let mut parts = split::tab_or_space(s).into_iter(); let first = parts.next().ok_or(StringParseError::UnknownValue)?; let act = Self::iter() .find(|i| i.to_string() == first) .ok_or(StringParseError::UnknownValue)?; match act { MousebindAction::Move | MousebindAction::Resize | MousebindAction::Zoom => Ok(act), MousebindAction::Call(_) => { let command = parts.next().ok_or(StringParseError::UnknownValue)?; let args = parts.collect(); Ok(MousebindAction::Call(Box::new( HlwmCommand::from_raw_parts(&command, args) .map_err(|err| StringParseError::CommandParseError(err.to_string()))?, ))) } } } } 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, Error)] pub enum KeyParseError { #[error("value too short (expected >= 2 parts, got {0} parts)")] TooShort(usize), #[error("no keys in keybind")] NoKeys, #[error("command parse error: {0}")] CommandParseError(String), #[error("expected char key, got: [{0}]")] ExpectedCharKey(String), #[error("string parse error: [{0}]")] StringParseError(String), } impl From for KeyParseError { fn from(value: StringParseError) -> Self { KeyParseError::StringParseError(value.to_string()) } } impl From for KeyParseError { fn from(value: CommandParseError) -> Self { KeyParseError::CommandParseError(value.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 FromStr for Keybind { type Err = KeyParseError; fn from_str(s: &str) -> Result { let parts = s.split('\t').collect::>(); if parts.len() < 2 { return Err(KeyParseError::TooShort(s.len())); } let mut parts = parts.into_iter(); let keys = parts .next() .unwrap() .split("+") .map(|key| Key::from_str(key)) .collect::, _>>()?; let command = parts.next().unwrap(); let args: Vec = parts.map(String::from).collect(); Ok(Self { keys, command: Box::new(HlwmCommand::from_raw_parts(command, args)?), }) } } 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 FromStr for Mousebind { type Err = StringParseError; fn from_str(s: &str) -> Result { let mut parts = s.split("\t"); let keys = parts .next() .ok_or(StringParseError::UnknownValue)? .split("-") .map(|key| key.parse()) .collect::, _>>()?; let action = parts.next().ok_or(StringParseError::UnknownValue)?; let mut action = MousebindAction::iter() .into_iter() .find(|act| act.to_string() == action) .ok_or(StringParseError::UnknownValue)?; if let MousebindAction::Call(_) = action { let command = parts.next().ok_or(StringParseError::UnknownValue)?; action = MousebindAction::Call(Box::new( HlwmCommand::from_raw_parts(command, parts.map(String::from).collect()) .map_err(|_| StringParseError::UnknownValue)?, )); } 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 = StringParseError; fn from_str(s: &str) -> Result { match s { "--all" => Ok(Self::All), _ => Ok(KeyUnbind::Keybind( s.split("-") .map(|key| key.parse()) .collect::>()?, )), } } } 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); } } }