From 22458ddd4a718c1712d31b5b2d114efd453c486e Mon Sep 17 00:00:00 2001 From: Alexander Mohr Date: Fri, 23 May 2025 22:17:04 +0200 Subject: [PATCH] add key handling by code --- worf/src/lib/config.rs | 41 ++++++++++-- worf/src/lib/gui.rs | 127 +++++++++++++++++++++++++++++++++--- worf/src/lib/modes/emoji.rs | 13 ++-- worf/src/lib/modes/math.rs | 3 +- 4 files changed, 163 insertions(+), 21 deletions(-) diff --git a/worf/src/lib/config.rs b/worf/src/lib/config.rs index 34501a4..a174f0c 100644 --- a/worf/src/lib/config.rs +++ b/worf/src/lib/config.rs @@ -55,6 +55,15 @@ pub enum CustomKeyHintLocation { Bottom, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] + +pub enum KeyDetectionType { + /// Raw keyboard value, might not be correct all layouts + Code, + /// The value of the key, but note that shift+3 != 3 (as shift+3 = #) + Value, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Mode { /// searches `$PATH` for executables and allows them to be run by selecting them. @@ -151,6 +160,20 @@ impl FromStr for SortOrder { } } +impl FromStr for KeyDetectionType { + type Err = ArgsError; + + fn from_str(s: &str) -> Result { + match s { + "value" => Ok(KeyDetectionType::Value), + "code" => Ok(KeyDetectionType::Code), + _ => Err(ArgsError::InvalidParameter( + format!("{s} is not a valid argument, see help for details").to_owned(), + )), + } + } +} + #[derive(Debug, Deserialize, Serialize, Clone, Parser)] #[clap(about = "Worf is a wofi clone written in rust, it aims to be a drop-in replacement")] #[derive(Default)] @@ -161,7 +184,7 @@ pub struct Config { /// Selects a config file to use #[clap(short = 'c', long = "conf")] - config: Option, + cfg_path: Option, /// Prints the version and then exits #[clap(short = 'v', long = "version")] @@ -329,7 +352,7 @@ pub struct Config { /// Orientation of items in the row box where items are displayed #[clap(long = "row-box-orientation")] - row_bow_orientation: Option, + row_box_orientation: Option, #[clap(long = "line-wrap")] line_wrap: Option, @@ -337,6 +360,9 @@ pub struct Config { /// Display only icon in emoji mode #[clap(long = "emoji-hide-string")] emoji_hide_label: Option, + + #[clap(long = "keyboard-detection-type")] + key_detection_type: Option, } impl Config { @@ -438,7 +464,7 @@ impl Config { #[must_use] pub fn row_bow_orientation(&self) -> Orientation { - self.row_bow_orientation.unwrap_or(Orientation::Horizontal) + self.row_box_orientation.unwrap_or(Orientation::Horizontal) } #[must_use] @@ -519,6 +545,13 @@ impl Config { pub fn emoji_hide_label(&self) -> bool { self.emoji_hide_label.unwrap_or(false) } + + #[must_use] + pub fn key_detection_type(&self) -> KeyDetectionType { + self.key_detection_type + .clone() + .unwrap_or(KeyDetectionType::Value) + } } fn default_false() -> bool { @@ -672,7 +705,7 @@ pub fn resolve_path( /// * no config file exists /// * config file and args cannot be merged pub fn load_config(args_opt: Option<&Config>) -> Result { - let config_path = conf_path(args_opt.as_ref().and_then(|c| c.config.as_ref())); + let config_path = conf_path(args_opt.as_ref().and_then(|c| c.cfg_path.as_ref())); match config_path { Ok(path) => { let toml_content = fs::read_to_string(path).map_err(|e| Error::Io(format!("{e}")))?; diff --git a/worf/src/lib/gui.rs b/worf/src/lib/gui.rs index 6cf57c4..5f6227f 100644 --- a/worf/src/lib/gui.rs +++ b/worf/src/lib/gui.rs @@ -25,7 +25,9 @@ use gtk4_layer_shell::{Edge, KeyboardMode, LayerShell}; use log; use regex::Regex; -use crate::config::{Anchor, Config, CustomKeyHintLocation, MatchMethod, SortOrder, WrapMode}; +use crate::config::{ + Anchor, Config, CustomKeyHintLocation, KeyDetectionType, MatchMethod, SortOrder, WrapMode, +}; use crate::desktop::known_image_extension_regex_pattern; use crate::{Error, config, desktop}; @@ -335,6 +337,109 @@ impl From for Key { } } +impl From for Key { + fn from(value: u32) -> Self { + match value { + // Letters + 38 => Key::A, + 56 => Key::B, + 54 => Key::C, + 40 => Key::D, + 26 => Key::E, + 41 => Key::F, + 42 => Key::G, + 43 => Key::H, + 31 => Key::I, + 44 => Key::J, + 45 => Key::K, + 46 => Key::L, + 58 => Key::M, + 57 => Key::N, + 32 => Key::O, + 33 => Key::P, + 24 => Key::Q, + 27 => Key::R, + 39 => Key::S, + 28 => Key::T, + 30 => Key::U, + 55 => Key::V, + 25 => Key::W, + 53 => Key::X, + 29 => Key::Y, + 52 => Key::Z, + + // Numbers + 10 => Key::Num0, + 11 => Key::Num1, + 12 => Key::Num2, + 13 => Key::Num3, + 14 => Key::Num4, + 15 => Key::Num5, + 16 => Key::Num6, + 17 => Key::Num7, + 18 => Key::Num8, + 19 => Key::Num9, + + // Function Keys + 67 => Key::F1, + 68 => Key::F2, + 69 => Key::F3, + 70 => Key::F4, + 71 => Key::F5, + 72 => Key::F6, + 73 => Key::F7, + 74 => Key::F8, + 75 => Key::F9, + 76 => Key::F10, + 77 => Key::F11, + 78 => Key::F12, + + // Navigation / Editing + 9 => Key::Escape, + 36 => Key::Enter, + 65 => Key::Space, + 23 => Key::Tab, + 22 => Key::Backspace, + 118 => Key::Insert, + 119 => Key::Delete, + 110 => Key::Home, + 115 => Key::End, + 112 => Key::PageUp, + 117 => Key::PageDown, + 113 => Key::Left, + 114 => Key::Right, + 111 => Key::Up, + 116 => Key::Down, + + // Special characters + 20 => Key::Exclamation, + 63 => Key::At, + 3 => Key::Hash, + 4 => Key::Dollar, + 5 => Key::Percent, + 6 => Key::Caret, + 7 => Key::Ampersand, + 8 => Key::Asterisk, + 34 => Key::LeftParen, + 35 => Key::RightParen, + 48 => Key::Minus, + 47 => Key::Underscore, + 21 => Key::Equal, + 49 => Key::Plus, + 51 => Key::Backslash, + 94 => Key::Pipe, + 50 => Key::Quote, + 59 => Key::Comma, + 60 => Key::Period, + 61 => Key::Slash, + 62 => Key::Question, + 96 => Key::Grave, + 97 => Key::Tilde, + _ => Key::None, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Modifier { Shift, @@ -771,8 +876,6 @@ fn build_ui_from_menu_items( let meta_clone = Rc::>::clone(meta); let ui_clone = Rc::>::clone(ui); - - glib::idle_add_local(move || { let mut done = false; { @@ -801,10 +904,9 @@ fn build_ui_from_menu_items( sort_flow_box_childs(child1, child2, &items_sort) }); - if done { let lock = ui_clone.menu_rows.read().unwrap(); - + select_first_visible_child(&lock, &ui_clone.main_box); log::debug!( @@ -830,11 +932,12 @@ fn setup_key_event_handler( let ui_clone = Rc::clone(ui); let meta_clone = Rc::clone(meta); let keys_clone = custom_keys.cloned(); - key_controller.connect_key_pressed(move |_, key_value, _, modifier| { + key_controller.connect_key_pressed(move |_, key_value, key_code, modifier| { handle_key_press( &ui_clone, &meta_clone, key_value, + key_code, modifier, keys_clone.as_ref(), ) @@ -848,6 +951,7 @@ fn handle_key_press( ui: &Rc>, meta: &Rc>, keyboard_key: gdk4::Key, + key_code: u32, modifier_type: gdk4::ModifierType, custom_keys: Option<&CustomKeys>, ) -> Propagation { @@ -873,10 +977,13 @@ fn handle_key_press( if let Some(custom_keys) = custom_keys { let mods = modifiers_from_mask(modifier_type); for custom_key in &custom_keys.bindings { - log::debug!( - "comparing custom key {custom_key:?} to mask {mods:?} and key {keyboard_key}" - ); - if custom_key.key == keyboard_key.to_upper().into() && mods.is_subset(&custom_key.modifiers) { + let custom_key_match = if meta.config.key_detection_type() == KeyDetectionType::Code { + custom_key.key == key_code.into() + } else { + custom_key.key == keyboard_key.to_upper().into() + } && mods.is_subset(&custom_key.modifiers); + + if custom_key_match { let search_lock = ui.search_text.lock().unwrap(); if let Err(e) = handle_selected_item( ui, diff --git a/worf/src/lib/modes/emoji.rs b/worf/src/lib/modes/emoji.rs index 6485ab1..3f648ae 100644 --- a/worf/src/lib/modes/emoji.rs +++ b/worf/src/lib/modes/emoji.rs @@ -11,19 +11,22 @@ pub(crate) struct EmojiProvider { } impl EmojiProvider { - pub(crate) fn new(data: T, sort_order: &SortOrder, hide_label: &bool) -> Self { + pub(crate) fn new(data: T, sort_order: &SortOrder, hide_label: bool) -> Self { let emoji = emoji::search::search_annotation_all(""); let mut menus = emoji .into_iter() .map(|e| { MenuItem::new( - if *hide_label { - e.glyph.to_string() + if hide_label { + e.glyph.to_string() } else { format!("{} — Category: {} — Name: {}", e.glyph, e.group, e.name) }, None, - Some(format!("emoji {} — Category: {} — Name: {}", e.glyph, e.group, e.name)), + Some(format!( + "emoji {} — Category: {} — Name: {}", + e.glyph, e.group, e.name + )), vec![], None, 0.0, @@ -55,7 +58,7 @@ impl ItemProvider for EmojiProvider { /// /// Forwards errors from the gui. See `gui::show` for details. pub fn show(config: &Config) -> Result<(), Error> { - let provider = EmojiProvider::new(0, &config.sort_order(), &config.emoji_hide_label()); + let provider = EmojiProvider::new(0, &config.sort_order(), config.emoji_hide_label()); let selection_result = gui::show(config.clone(), provider, true, None, None)?; match selection_result.menu.action { None => Err(Error::MissingAction), diff --git a/worf/src/lib/modes/math.rs b/worf/src/lib/modes/math.rs index 03d05ed..6aadeab 100644 --- a/worf/src/lib/modes/math.rs +++ b/worf/src/lib/modes/math.rs @@ -1,7 +1,7 @@ -use regex::Regex; use crate::config::Config; use crate::gui; use crate::gui::{ItemProvider, MenuItem}; +use regex::Regex; #[derive(Clone)] pub(crate) struct MathProvider { @@ -25,7 +25,6 @@ impl ItemProvider for MathProvider { #[allow(clippy::cast_possible_truncation)] fn get_elements(&mut self, search: Option<&str>) -> (bool, Vec>) { if let Some(search_text) = search { - let re = Regex::new(r"0x[0-9a-fA-F]+").unwrap(); let result = re.replace_all(search_text, |caps: ®ex::Captures| { let hex_str = &caps[0][2..]; // Skip "0x"