hlctl/src/hlwm/key.rs

531 lines
14 KiB
Rust

use std::{fmt::Display, str::FromStr};
use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use thiserror::Error;
use crate::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 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
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for Key {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<Self, Self::Err> {
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<Self, Self::Err> {
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<HlwmCommand>),
}
impl Serialize for MousebindAction {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_command_string())
}
}
impl<'de> Deserialize<'de> for MousebindAction {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<Self, Self::Err> {
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<StringParseError> for KeyParseError {
fn from(value: StringParseError) -> Self {
KeyParseError::StringParseError(value.to_string())
}
}
impl From<CommandParseError> for KeyParseError {
fn from(value: CommandParseError) -> Self {
KeyParseError::CommandParseError(value.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Keybind {
pub keys: Vec<Key>,
pub command: Box<HlwmCommand>,
}
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<Self, Self::Err> {
let parts = s.split('\t').collect::<Vec<&str>>();
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::<Result<Vec<Key>, _>>()?;
let command = parts.next().unwrap();
let args: Vec<String> = 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<I: IntoIterator<Item = Key>>(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::<Vec<String>>()
.join("+"),
)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Mousebind {
pub keys: Vec<Key>,
pub action: MousebindAction,
}
impl FromStr for Mousebind {
type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split("\t");
let keys = parts
.next()
.ok_or(StringParseError::UnknownValue)?
.split("-")
.map(|key| key.parse())
.collect::<Result<Vec<Key>, _>>()?;
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<I: Iterator<Item = Key>>(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::<Vec<String>>()
.join("-"),
action = self.action.to_command_string(),
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum KeyUnbind {
Keybind(Vec<Key>),
All,
}
impl FromStr for KeyUnbind {
type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"--all" => Ok(Self::All),
_ => Ok(KeyUnbind::Keybind(
s.split("-")
.map(|key| key.parse())
.collect::<Result<_, _>>()?,
)),
}
}
}
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::<Vec<String>>()
.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);
}
}
}