hlctl/src/hlwm/key.rs

587 lines
16 KiB
Rust

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<Vec<Key>, ParseError> {
s.split(['-', '+'])
.map(Key::from_str)
.collect::<Result<Vec<_>, _>>()
}
pub fn from_str_case_insensitive(s: &str) -> Result<Self, ParseError> {
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<String> {
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<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.config_name())
}
}
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_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<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| 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<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"),
}
}
}
#[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, 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: Into<String>>(s: S) -> Vec<String> {
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<String> = 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::<Vec<_>>();
(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);
}
}
}
}