diff --git a/Cargo.lock b/Cargo.lock index 8740c7b..172d2ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -302,6 +302,9 @@ name = "bitflags" version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -542,6 +545,7 @@ dependencies = [ "mio", "parking_lot", "rustix 1.0.8", + "serde", "signal-hook", "signal-hook-mio", "winapi", diff --git a/Cargo.toml b/Cargo.toml index 9829df3..34a66ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ repository = "https://github.com/pythops/impala" async-channel = "2" crossterm = { version = "0.29", default-features = false, features = [ "event-stream", + "serde" ] } ratatui = "0.29" tui-input = "0.12" diff --git a/Readme.md b/Readme.md index 5774206..56cb25a 100644 --- a/Readme.md +++ b/Readme.md @@ -99,9 +99,12 @@ This will produce an executable file at `target/release/impala` that you can cop Keybindings can be customized in the config file `$HOME/.config/impala/config.toml` +Keybindings can be a single char or have modifier keys. ```toml +# switch = ["CTRL", "r"] switch = "r" + mode = "station" [device] diff --git a/src/adapter.rs b/src/adapter.rs index 8f062e6..efff243 100644 --- a/src/adapter.rs +++ b/src/adapter.rs @@ -14,7 +14,7 @@ use tokio::sync::mpsc::UnboundedSender; use crate::{ app::{AppResult, ColorMode, FocusedBlock}, - config::Config, + config::{Config, KeyBind}, device::Device, event::Event, }; @@ -1069,11 +1069,15 @@ impl Adapter { Span::from("j,").bold(), Span::from(" Down"), Span::from(" | "), - Span::from(if self.config.station.toggle_connect == ' ' { - "󱁐 ".to_string() - } else { - self.config.station.toggle_connect.to_string() - }) + Span::from( + if let KeyBind::SingleChar(ch) = self.config.station.toggle_connect + && ch == ' ' + { + "󱁐 ".to_string() + } else { + self.config.station.toggle_connect.to_string() + }, + ) .bold(), Span::from(" Connect/Disconnect"), Span::from(" | "), diff --git a/src/config.rs b/src/config.rs index f6e96a8..4e17601 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,13 @@ +use crossterm::event::{KeyEvent, KeyModifiers}; use toml; use dirs; -use serde::Deserialize; +use serde::{Deserialize, de::IntoDeserializer}; #[derive(Deserialize, Debug)] pub struct Config { #[serde(default = "default_switch_mode")] - pub switch: char, + pub switch: KeyBind, #[serde(default = "default_device_mode")] pub mode: String, @@ -21,43 +22,82 @@ pub struct Config { pub ap: AccessPoint, } -fn default_switch_mode() -> char { - 'r' +fn default_switch_mode() -> KeyBind { + (KeyModifiers::CONTROL, 'r').into() } fn default_device_mode() -> String { "station".to_string() } +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum KeyBind { + SingleChar(char), + WithModKey((KeyModifiers, char)), +} + +impl core::fmt::Display for KeyBind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::SingleChar(ch) => write!(f, "{ch}"), + Self::WithModKey((modifier, ch)) => write!(f, "{modifier} + {ch}"), + } + } +} + +impl From for KeyBind { + fn from(value: char) -> Self { + Self::SingleChar(value) + } +} + +impl From<(KeyModifiers, char)> for KeyBind { + fn from(value: (KeyModifiers, char)) -> Self { + KeyBind::WithModKey((value.0, value.1)) + } +} +impl PartialEq for KeyBind { + // Assumption is that we're using this in event loop, where we're already matching KeyCode::Char + fn eq(&self, other: &KeyEvent) -> bool { + match self { + Self::SingleChar(ch) => *ch == other.code.as_char().unwrap(), + Self::WithModKey((modkey, ch)) => { + *ch == other.code.as_char().unwrap() && *modkey == other.modifiers + } + } + } +} + // Device #[derive(Deserialize, Debug)] pub struct Device { #[serde(default = "default_show_device_infos")] - pub infos: char, - pub toggle_power: char, + pub infos: KeyBind, + pub toggle_power: KeyBind, } impl Default for Device { fn default() -> Self { Self { - infos: 'i', - toggle_power: 'o', + infos: 'i'.into(), + toggle_power: 'o'.into(), } } } -fn default_show_device_infos() -> char { - 'i' +fn default_show_device_infos() -> KeyBind { + 'i'.into() } // Station #[derive(Deserialize, Debug)] pub struct Station { #[serde(default = "default_station_start_scanning")] - pub start_scanning: char, + pub start_scanning: KeyBind, #[serde(default = "default_station_toggle_connect")] - pub toggle_connect: char, + pub toggle_connect: KeyBind, #[serde(default)] pub known_network: KnownNetwork, @@ -66,75 +106,71 @@ pub struct Station { impl Default for Station { fn default() -> Self { Self { - start_scanning: 's', - toggle_connect: ' ', + start_scanning: 's'.into(), + toggle_connect: ' '.into(), known_network: KnownNetwork::default(), } } } -fn default_station_start_scanning() -> char { - 's' +fn default_station_start_scanning() -> KeyBind { + 's'.into() } -fn default_station_toggle_connect() -> char { - ' ' +fn default_station_toggle_connect() -> KeyBind { + ' '.into() } #[derive(Deserialize, Debug)] pub struct KnownNetwork { #[serde(default = "default_station_remove_known_network")] - pub remove: char, - pub toggle_autoconnect: char, + pub remove: KeyBind, + pub toggle_autoconnect: KeyBind, } impl Default for KnownNetwork { fn default() -> Self { Self { - remove: 'd', - toggle_autoconnect: 'a', + remove: 'd'.into(), + toggle_autoconnect: 'a'.into(), } } } -fn default_station_remove_known_network() -> char { - 'd' +fn default_station_remove_known_network() -> KeyBind { + 'd'.into() } // Access Point #[derive(Deserialize, Debug)] pub struct AccessPoint { #[serde(default = "default_ap_start")] - pub start: char, + pub start: KeyBind, #[serde(default = "default_ap_stop")] - pub stop: char, + pub stop: KeyBind, } impl Default for AccessPoint { fn default() -> Self { Self { - start: 'n', - stop: 'x', + start: 'n'.into(), + stop: 'x'.into(), } } } -fn default_ap_start() -> char { - 'n' +fn default_ap_start() -> KeyBind { + 'n'.into() } -fn default_ap_stop() -> char { - 'x' +fn default_ap_stop() -> KeyBind { + 'x'.into() } impl Config { pub fn new() -> Self { - let conf_path = dirs::config_dir() - .unwrap() - .join("impala") - .join("config.toml"); - + let conf_path = "./config.toml"; let config = std::fs::read_to_string(conf_path).unwrap_or_default(); let app_config: Config = toml::from_str(&config).unwrap(); diff --git a/src/handler.rs b/src/handler.rs index fe440b9..e35005d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -120,9 +120,7 @@ pub async fn handle_key_events( } // Switch mode - KeyCode::Char(c) - if c == config.switch && key_event.modifiers == KeyModifiers::CONTROL => - { + KeyCode::Char(c) if config.switch == key_event => { app.reset_mode = true; } @@ -134,7 +132,7 @@ pub async fn handle_key_events( } // Start Scan - KeyCode::Char(c) if c == config.station.start_scanning => { + KeyCode::Char(c) if config.station.start_scanning == key_event => { match app.adapter.device.mode { Mode::Station => { app.adapter @@ -259,11 +257,11 @@ pub async fn handle_key_events( _ => { match app.focused_block { FocusedBlock::Device => match key_event.code { - KeyCode::Char(c) if c == config.device.infos => { + KeyCode::Char(c) if config.device.infos == key_event => { app.focused_block = FocusedBlock::AdapterInfos; } - KeyCode::Char(c) if c == config.device.toggle_power => { + KeyCode::Char(c) if config.device.toggle_power == key_event => { if app.adapter.device.is_powered { match app.adapter.device.power_off().await { Ok(()) => { @@ -312,7 +310,7 @@ pub async fn handle_key_events( match key_event.code { // Remove a known network KeyCode::Char(c) - if c == config.station.known_network.remove + if config.station.known_network.remove == key_event && app.focused_block == FocusedBlock::KnownNetworks => { @@ -340,10 +338,8 @@ pub async fn handle_key_events( // Toggle autoconnect KeyCode::Char(c) - if c == config - .station - .known_network - .toggle_autoconnect + if config.station.known_network.toggle_autoconnect + == key_event && app.focused_block == FocusedBlock::KnownNetworks => { @@ -372,7 +368,9 @@ pub async fn handle_key_events( } // Connect/Disconnect - KeyCode::Char(c) if c == config.station.toggle_connect => { + KeyCode::Char(c) + if config.station.toggle_connect == key_event => + { match app.focused_block { FocusedBlock::NewNetworks => { if let Some(net_index) = app @@ -714,14 +712,14 @@ pub async fn handle_key_events( } } Mode::Ap => match key_event.code { - KeyCode::Char(c) if c == config.ap.start => { + KeyCode::Char(c) if config.ap.start == key_event => { if let Some(ap) = &app.adapter.device.access_point { // Start AP ap.ap_start .store(true, std::sync::atomic::Ordering::Relaxed); } } - KeyCode::Char(c) if c == config.ap.stop => { + KeyCode::Char(c) if config.ap.stop == key_event => { if let Some(ap) = &mut app.adapter.device.access_point { ap.stop(sender).await?; ap.connected_devices = Vec::new();