use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug, Clone, Default)]
pub struct KeyTranslator {
translations: HashMap<KeyEventKey, KeyEventKey>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct KeyEventKey {
pub code: SerializableKeyCode,
pub modifiers: u8, }
impl KeyEventKey {
pub fn from_key_event(event: &KeyEvent) -> Self {
Self {
code: SerializableKeyCode::from_key_code(&event.code),
modifiers: event.modifiers.bits(),
}
}
pub fn to_key_event(&self) -> KeyEvent {
KeyEvent::new(
self.code.to_key_code(),
KeyModifiers::from_bits_truncate(self.modifiers),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "type", content = "value")]
pub enum SerializableKeyCode {
Backspace,
Enter,
Left,
Right,
Up,
Down,
Home,
End,
PageUp,
PageDown,
Tab,
BackTab,
Delete,
Insert,
F(u8),
Char(char),
Null,
Esc,
CapsLock,
ScrollLock,
NumLock,
PrintScreen,
Pause,
Menu,
Modifier(String),
Unknown,
}
impl SerializableKeyCode {
pub fn from_key_code(code: &KeyCode) -> Self {
match code {
KeyCode::Backspace => SerializableKeyCode::Backspace,
KeyCode::Enter => SerializableKeyCode::Enter,
KeyCode::Left => SerializableKeyCode::Left,
KeyCode::Right => SerializableKeyCode::Right,
KeyCode::Up => SerializableKeyCode::Up,
KeyCode::Down => SerializableKeyCode::Down,
KeyCode::Home => SerializableKeyCode::Home,
KeyCode::End => SerializableKeyCode::End,
KeyCode::PageUp => SerializableKeyCode::PageUp,
KeyCode::PageDown => SerializableKeyCode::PageDown,
KeyCode::Tab => SerializableKeyCode::Tab,
KeyCode::BackTab => SerializableKeyCode::BackTab,
KeyCode::Delete => SerializableKeyCode::Delete,
KeyCode::Insert => SerializableKeyCode::Insert,
KeyCode::F(n) => SerializableKeyCode::F(*n),
KeyCode::Char(c) => SerializableKeyCode::Char(*c),
KeyCode::Null => SerializableKeyCode::Null,
KeyCode::Esc => SerializableKeyCode::Esc,
KeyCode::CapsLock => SerializableKeyCode::CapsLock,
KeyCode::ScrollLock => SerializableKeyCode::ScrollLock,
KeyCode::NumLock => SerializableKeyCode::NumLock,
KeyCode::PrintScreen => SerializableKeyCode::PrintScreen,
KeyCode::Pause => SerializableKeyCode::Pause,
KeyCode::Menu => SerializableKeyCode::Menu,
KeyCode::Modifier(m) => SerializableKeyCode::Modifier(format!("{:?}", m)),
_ => SerializableKeyCode::Unknown,
}
}
pub fn to_key_code(&self) -> KeyCode {
match self {
SerializableKeyCode::Backspace => KeyCode::Backspace,
SerializableKeyCode::Enter => KeyCode::Enter,
SerializableKeyCode::Left => KeyCode::Left,
SerializableKeyCode::Right => KeyCode::Right,
SerializableKeyCode::Up => KeyCode::Up,
SerializableKeyCode::Down => KeyCode::Down,
SerializableKeyCode::Home => KeyCode::Home,
SerializableKeyCode::End => KeyCode::End,
SerializableKeyCode::PageUp => KeyCode::PageUp,
SerializableKeyCode::PageDown => KeyCode::PageDown,
SerializableKeyCode::Tab => KeyCode::Tab,
SerializableKeyCode::BackTab => KeyCode::BackTab,
SerializableKeyCode::Delete => KeyCode::Delete,
SerializableKeyCode::Insert => KeyCode::Insert,
SerializableKeyCode::F(n) => KeyCode::F(*n),
SerializableKeyCode::Char(c) => KeyCode::Char(*c),
SerializableKeyCode::Null => KeyCode::Null,
SerializableKeyCode::Esc => KeyCode::Esc,
SerializableKeyCode::CapsLock => KeyCode::CapsLock,
SerializableKeyCode::ScrollLock => KeyCode::ScrollLock,
SerializableKeyCode::NumLock => KeyCode::NumLock,
SerializableKeyCode::PrintScreen => KeyCode::PrintScreen,
SerializableKeyCode::Pause => KeyCode::Pause,
SerializableKeyCode::Menu => KeyCode::Menu,
SerializableKeyCode::Modifier(_) | SerializableKeyCode::Unknown => KeyCode::Null,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
struct CalibrationFile {
#[serde(rename = "_comment")]
comment: String,
#[serde(rename = "_format")]
format: String,
translations: Vec<TranslationEntry>,
}
#[derive(Debug, Serialize, Deserialize)]
struct TranslationEntry {
raw: KeyEventKey,
expected: KeyEventKey,
}
impl KeyTranslator {
pub fn new() -> Self {
Self {
translations: HashMap::new(),
}
}
pub fn translate(&self, raw: KeyEvent) -> KeyEvent {
let key = KeyEventKey::from_key_event(&raw);
if let Some(normalized) = self.translations.get(&key) {
normalized.to_key_event()
} else {
raw
}
}
pub fn has_translation(&self, raw: &KeyEvent) -> bool {
let key = KeyEventKey::from_key_event(raw);
self.translations.contains_key(&key)
}
pub fn add_translation(&mut self, raw: KeyEvent, expected: KeyEvent) {
let raw_key = KeyEventKey::from_key_event(&raw);
let expected_key = KeyEventKey::from_key_event(&expected);
self.translations.insert(raw_key, expected_key);
}
pub fn remove_translation(&mut self, raw: &KeyEvent) {
let key = KeyEventKey::from_key_event(raw);
self.translations.remove(&key);
}
pub fn len(&self) -> usize {
self.translations.len()
}
pub fn is_empty(&self) -> bool {
self.translations.is_empty()
}
pub fn clear(&mut self) {
self.translations.clear();
}
pub fn load_from_file(path: &Path) -> Result<Self, std::io::Error> {
if !path.exists() {
return Ok(Self::new());
}
let content = std::fs::read_to_string(path)?;
let file: CalibrationFile = serde_json::from_str(&content).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid calibration file: {}", e),
)
})?;
let mut translator = Self::new();
for entry in file.translations {
translator.translations.insert(entry.raw, entry.expected);
}
tracing::info!(
"Loaded {} key translations from {}",
translator.len(),
path.display()
);
Ok(translator)
}
pub fn save_to_file(&self, path: &Path) -> Result<(), std::io::Error> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let entries: Vec<TranslationEntry> = self
.translations
.iter()
.map(|(raw, expected)| TranslationEntry {
raw: raw.clone(),
expected: expected.clone(),
})
.collect();
let file = CalibrationFile {
comment: "Generated by 'Calibrate Input Keys' wizard".to_string(),
format: "raw_key → expected_key".to_string(),
translations: entries,
};
let content = serde_json::to_string_pretty(&file)?;
std::fs::write(path, content)?;
tracing::info!(
"Saved {} key translations to {}",
self.len(),
path.display()
);
Ok(())
}
pub fn default_path() -> Option<std::path::PathBuf> {
dirs::config_dir().map(|p| p.join("fresh").join("key_calibration.json"))
}
pub fn load_default() -> Result<Self, std::io::Error> {
if let Some(path) = Self::default_path() {
Self::load_from_file(&path)
} else {
Ok(Self::new())
}
}
pub fn save_default(&self) -> Result<(), std::io::Error> {
if let Some(path) = Self::default_path() {
self.save_to_file(&path)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Could not determine config directory",
))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_translator_empty() {
let translator = KeyTranslator::new();
assert!(translator.is_empty());
let raw = KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE);
let result = translator.translate(raw);
assert_eq!(result.code, KeyCode::Backspace);
}
#[test]
fn test_translator_with_mapping() {
let mut translator = KeyTranslator::new();
let raw = KeyEvent::new(KeyCode::Char('\x7f'), KeyModifiers::NONE);
let expected = KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE);
translator.add_translation(raw.clone(), expected.clone());
assert_eq!(translator.len(), 1);
assert!(translator.has_translation(&raw));
let result = translator.translate(raw);
assert_eq!(result.code, KeyCode::Backspace);
}
#[test]
fn test_translator_preserves_unmapped() {
let mut translator = KeyTranslator::new();
let mapped_raw = KeyEvent::new(KeyCode::Char('\x7f'), KeyModifiers::NONE);
let mapped_expected = KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE);
translator.add_translation(mapped_raw, mapped_expected);
let unmapped = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
let result = translator.translate(unmapped);
assert_eq!(result.code, KeyCode::Char('a'));
}
#[test]
fn test_translator_with_modifiers() {
let mut translator = KeyTranslator::new();
let raw = KeyEvent::new(KeyCode::Char('b'), KeyModifiers::ALT);
let expected = KeyEvent::new(KeyCode::Left, KeyModifiers::ALT);
translator.add_translation(raw.clone(), expected.clone());
let result = translator.translate(raw);
assert_eq!(result.code, KeyCode::Left);
assert!(result.modifiers.contains(KeyModifiers::ALT));
}
#[test]
fn test_key_event_key_serialization() {
let key = KeyEventKey {
code: SerializableKeyCode::Backspace,
modifiers: KeyModifiers::NONE.bits(),
};
let json = serde_json::to_string(&key).unwrap();
let deserialized: KeyEventKey = serde_json::from_str(&json).unwrap();
assert_eq!(key, deserialized);
}
#[test]
fn test_roundtrip_key_event() {
let original = KeyEvent::new(KeyCode::Home, KeyModifiers::SHIFT);
let key = KeyEventKey::from_key_event(&original);
let restored = key.to_key_event();
assert_eq!(original.code, restored.code);
assert_eq!(original.modifiers, restored.modifiers);
}
}