498 lines
14 KiB
Rust
498 lines
14 KiB
Rust
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<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);
|
|
}
|
|
}
|
|
}
|