hlctl/src/hlwm/key.rs

498 lines
13 KiB
Rust

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<Vec<Key>, ParseError> {
s.split(['-', '+'])
.map(Key::from_str)
.collect::<Result<Vec<_>, _>>()
}
}
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 = ParseError;
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(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<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(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<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 FromCommandArgs for MousebindAction {
fn from_command_args<S: Into<String>, I: Iterator<Item = S>>(
command: &str,
args: I,
) -> Result<Self, ParseError> {
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<Self, Self::Err> {
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<Key>,
pub command: Box<HlwmCommand>,
}
impl Default for Keybind {
fn default() -> Self {
Self {
keys: Default::default(),
command: Default::default(),
}
}
}
impl FromStrings for Keybind {
fn from_strings<I: Iterator<Item = String>>(s: I) -> Result<Self, ParseError> {
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<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 FromStrings for Mousebind {
fn from_strings<I: Iterator<Item = String>>(s: I) -> Result<Self, ParseError> {
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<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 = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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::<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);
}
}
}