Fullscreen terminal UI for Clash proxy manager with proxy selector, connections viewer, configurable keybindings, and TOML config support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
109 lines
3.7 KiB
Rust
109 lines
3.7 KiB
Rust
use anyhow::{anyhow, Result};
|
|
use iocraft::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct KeyBinding {
|
|
pub code: KeyCode,
|
|
pub modifiers: KeyModifiers,
|
|
}
|
|
|
|
impl KeyBinding {
|
|
pub fn parse(s: &str) -> Result<Self> {
|
|
let s = s.trim();
|
|
let (modifiers, key_str) = if s.contains('+') {
|
|
let parts: Vec<&str> = s.splitn(2, '+').collect();
|
|
let mut mods = KeyModifiers::NONE;
|
|
for m in parts[0].to_lowercase().split('+') {
|
|
match m.trim() {
|
|
"ctrl" | "control" => mods.insert(KeyModifiers::CONTROL),
|
|
"shift" => mods.insert(KeyModifiers::SHIFT),
|
|
"alt" => mods.insert(KeyModifiers::ALT),
|
|
other => return Err(anyhow!("unknown modifier: {}", other)),
|
|
}
|
|
}
|
|
(mods, parts[1].trim())
|
|
} else {
|
|
(KeyModifiers::NONE, s)
|
|
};
|
|
|
|
let code = parse_key_code(key_str)?;
|
|
Ok(Self { code, modifiers })
|
|
}
|
|
|
|
pub fn matches(&self, event: &KeyEvent) -> bool {
|
|
event.kind != KeyEventKind::Release
|
|
&& event.code == self.code
|
|
&& event.modifiers == self.modifiers
|
|
}
|
|
}
|
|
|
|
fn parse_key_code(s: &str) -> Result<KeyCode> {
|
|
let lower = s.to_lowercase();
|
|
match lower.as_str() {
|
|
"enter" | "return" => Ok(KeyCode::Enter),
|
|
"tab" => Ok(KeyCode::Tab),
|
|
"backtab" => Ok(KeyCode::BackTab),
|
|
"esc" | "escape" => Ok(KeyCode::Esc),
|
|
"up" => Ok(KeyCode::Up),
|
|
"down" => Ok(KeyCode::Down),
|
|
"left" => Ok(KeyCode::Left),
|
|
"right" => Ok(KeyCode::Right),
|
|
"home" => Ok(KeyCode::Home),
|
|
"end" => Ok(KeyCode::End),
|
|
"pageup" | "page_up" => Ok(KeyCode::PageUp),
|
|
"pagedown" | "page_down" => Ok(KeyCode::PageDown),
|
|
"delete" | "del" => Ok(KeyCode::Delete),
|
|
"space" => Ok(KeyCode::Char(' ')),
|
|
"backspace" => Ok(KeyCode::Backspace),
|
|
"insert" => Ok(KeyCode::Insert),
|
|
"f1" => Ok(KeyCode::F(1)),
|
|
"f2" => Ok(KeyCode::F(2)),
|
|
"f3" => Ok(KeyCode::F(3)),
|
|
"f4" => Ok(KeyCode::F(4)),
|
|
"f5" => Ok(KeyCode::F(5)),
|
|
"f6" => Ok(KeyCode::F(6)),
|
|
"f7" => Ok(KeyCode::F(7)),
|
|
"f8" => Ok(KeyCode::F(8)),
|
|
"f9" => Ok(KeyCode::F(9)),
|
|
"f10" => Ok(KeyCode::F(10)),
|
|
"f11" => Ok(KeyCode::F(11)),
|
|
"f12" => Ok(KeyCode::F(12)),
|
|
"null" => Ok(KeyCode::Null),
|
|
c if c.len() == 1 => Ok(KeyCode::Char(c.chars().next().unwrap())),
|
|
_ => Err(anyhow!("unknown key: {}", s)),
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Keybindings {
|
|
pub quit: KeyBinding,
|
|
pub tab_next: KeyBinding,
|
|
pub tab_prev: KeyBinding,
|
|
pub up: KeyBinding,
|
|
pub down: KeyBinding,
|
|
pub left: KeyBinding,
|
|
pub right: KeyBinding,
|
|
pub select: KeyBinding,
|
|
pub close_connection: KeyBinding,
|
|
pub close_all_connections: KeyBinding,
|
|
pub refresh: KeyBinding,
|
|
}
|
|
|
|
impl Keybindings {
|
|
pub fn from_config(cfg: &crate::config::KeybindingsConfig) -> Result<Self> {
|
|
Ok(Self {
|
|
quit: KeyBinding::parse(&cfg.quit)?,
|
|
tab_next: KeyBinding::parse(&cfg.tab_next)?,
|
|
tab_prev: KeyBinding::parse(&cfg.tab_prev)?,
|
|
up: KeyBinding::parse(&cfg.up)?,
|
|
down: KeyBinding::parse(&cfg.down)?,
|
|
left: KeyBinding::parse(&cfg.left)?,
|
|
right: KeyBinding::parse(&cfg.right)?,
|
|
select: KeyBinding::parse(&cfg.select)?,
|
|
close_connection: KeyBinding::parse(&cfg.close_connection)?,
|
|
close_all_connections: KeyBinding::parse(&cfg.close_all_connections)?,
|
|
refresh: KeyBinding::parse(&cfg.refresh)?,
|
|
})
|
|
}
|
|
}
|