openlogi_core/config/
key_trigger.rs1use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8use super::FunctionKey;
9use crate::binding::Action;
10
11#[derive(
16 Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
17)]
18#[expect(
19 clippy::struct_excessive_bools,
20 reason = "four independent modifier flags, as the OS reports them"
21)]
22pub struct KeyModifiers {
23 pub shift: bool,
25 pub control: bool,
27 pub option: bool,
29 pub command: bool,
31}
32
33impl KeyModifiers {
34 #[must_use]
36 pub fn is_empty(&self) -> bool {
37 !self.shift && !self.control && !self.option && !self.command
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
49pub struct KeyTrigger {
50 pub keycode: u16,
52 pub modifiers: KeyModifiers,
54}
55
56impl std::fmt::Display for KeyTrigger {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 let modifiers = &self.modifiers;
59 let mut separator = "";
60 for (enabled, name) in [
61 (modifiers.shift, "shift"),
62 (modifiers.control, "control"),
63 (modifiers.option, "option"),
64 (modifiers.command, "command"),
65 ] {
66 if enabled {
67 write!(f, "{separator}{name}")?;
68 separator = "+";
69 }
70 }
71 let key = FunctionKey::from_keycode(self.keycode).ok_or(std::fmt::Error)?;
72 write!(f, "{separator}{}", key.name())
73 }
74}
75
76impl Serialize for KeyTrigger {
78 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
79 s.collect_str(self)
80 }
81}
82impl<'de> Deserialize<'de> for KeyTrigger {
83 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
84 let s = String::deserialize(d)?;
85 s.parse().map_err(serde::de::Error::custom)
86 }
87}
88
89#[derive(Debug, Error)]
91#[error("invalid key trigger: {0}")]
92pub struct ParseTriggerError(pub String);
93
94impl std::str::FromStr for KeyTrigger {
95 type Err = ParseTriggerError;
96 fn from_str(s: &str) -> Result<Self, Self::Err> {
97 let mut mods = KeyModifiers::default();
98 let parts: Vec<&str> = s.split('+').map(str::trim).collect();
99 if parts.is_empty() || parts.iter().any(|p| p.is_empty()) {
100 return Err(ParseTriggerError("empty segment".into()));
101 }
102 let (mod_parts, key_part) = parts.split_at(parts.len() - 1);
104 for part in mod_parts {
105 match part.to_ascii_lowercase().as_str() {
106 "shift" => mods.shift = true,
107 "control" | "ctrl" => mods.control = true,
108 "option" | "alt" => mods.option = true,
109 "command" | "cmd" => mods.command = true,
110 other => return Err(ParseTriggerError(format!("unknown modifier '{other}'"))),
111 }
112 }
113 let key = key_part[0].to_ascii_lowercase();
114 let keycode = FunctionKey::from_name(&key)
115 .ok_or_else(|| ParseTriggerError(format!("unknown key '{key}'")))?
116 .keycode();
117 Ok(KeyTrigger {
118 keycode,
119 modifiers: mods,
120 })
121 }
122}
123
124#[derive(Debug, Clone, Default, Serialize, Deserialize)]
126#[serde(deny_unknown_fields)]
127pub struct KeyboardConfig {
128 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
130 pub bindings: BTreeMap<KeyTrigger, Action>,
131}