add key handling by code

This commit is contained in:
Alexander Mohr 2025-05-23 22:17:04 +02:00
parent f0f9b83270
commit 22458ddd4a
4 changed files with 163 additions and 21 deletions

View file

@ -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<Self, Self::Err> {
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<String>,
cfg_path: Option<String>,
/// 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<Orientation>,
row_box_orientation: Option<Orientation>,
#[clap(long = "line-wrap")]
line_wrap: Option<WrapMode>,
@ -337,6 +360,9 @@ pub struct Config {
/// Display only icon in emoji mode
#[clap(long = "emoji-hide-string")]
emoji_hide_label: Option<bool>,
#[clap(long = "keyboard-detection-type")]
key_detection_type: Option<KeyDetectionType>,
}
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<Config, Error> {
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}")))?;

View file

@ -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<gdk::Key> for Key {
}
}
impl From<u32> 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<T: Clone + 'static + Send>(
let meta_clone = Rc::<MetaData<T>>::clone(meta);
let ui_clone = Rc::<UiElements<T>>::clone(ui);
glib::idle_add_local(move || {
let mut done = false;
{
@ -801,10 +904,9 @@ fn build_ui_from_menu_items<T: Clone + 'static + Send>(
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<T: Clone + 'static + Send>(
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<T: Clone + 'static + Send>(
ui: &Rc<UiElements<T>>,
meta: &Rc<MetaData<T>>,
keyboard_key: gdk4::Key,
key_code: u32,
modifier_type: gdk4::ModifierType,
custom_keys: Option<&CustomKeys>,
) -> Propagation {
@ -873,10 +977,13 @@ fn handle_key_press<T: Clone + 'static + Send>(
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,

View file

@ -11,19 +11,22 @@ pub(crate) struct EmojiProvider<T: Clone> {
}
impl<T: Clone> EmojiProvider<T> {
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<T: Clone> ItemProvider<T> for EmojiProvider<T> {
///
/// 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),

View file

@ -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<T: Clone> {
@ -25,7 +25,6 @@ impl<T: Clone> ItemProvider<T> for MathProvider<T> {
#[allow(clippy::cast_possible_truncation)]
fn get_elements(&mut self, search: Option<&str>) -> (bool, Vec<MenuItem<T>>) {
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: &regex::Captures| {
let hex_str = &caps[0][2..]; // Skip "0x"