use serde::{Deserialize, Serialize};
use crate::binding::Action;
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
)]
#[expect(
clippy::struct_excessive_bools,
reason = "four independent modifier flags mirrored from the OS hook"
)]
pub struct KeyModifiers {
pub shift: bool,
pub control: bool,
pub option: bool,
pub command: bool,
}
impl KeyModifiers {
#[must_use]
pub fn is_empty(&self) -> bool {
!self.shift && !self.control && !self.option && !self.command
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct KeyTrigger {
pub keycode: u16,
pub modifiers: KeyModifiers,
}
impl std::fmt::Display for KeyTrigger {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut parts: Vec<&str> = Vec::new();
let m = &self.modifiers;
if m.shift {
parts.push("shift");
}
if m.control {
parts.push("control");
}
if m.option {
parts.push("option");
}
if m.command {
parts.push("command");
}
parts.push(keycode_to_name(self.keycode).ok_or(std::fmt::Error)?);
write!(f, "{}", parts.join("+"))
}
}
fn keycode_to_name(code: u16) -> Option<&'static str> {
Some(match code {
0x35 => "esc",
0x7A => "f1",
0x78 => "f2",
0x63 => "f3",
0x76 => "f4",
0x60 => "f5",
0x61 => "f6",
0x62 => "f7",
0x64 => "f8",
0x65 => "f9",
0x6D => "f10",
0x67 => "f11",
0x6F => "f12",
0x69 => "f13",
0x6B => "f14",
0x71 => "f15",
0x6A => "f16",
0x40 => "f17",
0x4F => "f18",
0x50 => "f19",
_ => return None,
})
}
impl Serialize for KeyTrigger {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.collect_str(self)
}
}
impl<'de> Deserialize<'de> for KeyTrigger {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Debug)]
pub struct ParseTriggerError(pub String);
impl std::fmt::Display for ParseTriggerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid key trigger: {}", self.0)
}
}
impl std::error::Error for ParseTriggerError {}
impl std::str::FromStr for KeyTrigger {
type Err = ParseTriggerError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut mods = KeyModifiers::default();
let parts: Vec<&str> = s.split('+').map(str::trim).collect();
if parts.is_empty() || parts.iter().any(|p| p.is_empty()) {
return Err(ParseTriggerError("empty segment".into()));
}
let (mod_parts, key_part) = parts.split_at(parts.len() - 1);
for part in mod_parts {
match part.to_ascii_lowercase().as_str() {
"shift" => mods.shift = true,
"control" | "ctrl" => mods.control = true,
"option" | "alt" => mods.option = true,
"command" | "cmd" => mods.command = true,
other => return Err(ParseTriggerError(format!("unknown modifier '{other}'"))),
}
}
let keycode = match key_part[0].to_ascii_lowercase().as_str() {
"esc" => 0x35,
"f1" => 0x7A,
"f2" => 0x78,
"f3" => 0x63,
"f4" => 0x76,
"f5" => 0x60,
"f6" => 0x61,
"f7" => 0x62,
"f8" => 0x64,
"f9" => 0x65,
"f10" => 0x6D,
"f11" => 0x67,
"f12" => 0x6F,
"f13" => 0x69,
"f14" => 0x6B,
"f15" => 0x71,
"f16" => 0x6A,
"f17" => 0x40,
"f18" => 0x4F,
"f19" => 0x50,
other => return Err(ParseTriggerError(format!("unknown key '{other}'"))),
};
Ok(KeyTrigger {
keycode,
modifiers: mods,
})
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct KeyboardConfig {
#[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
pub bindings: std::collections::HashMap<KeyTrigger, Action>,
}