From 340b0fbfeab3fa10839341f60e277dccc1d1c29a Mon Sep 17 00:00:00 2001 From: Alexander Mohr Date: Tue, 20 May 2025 22:00:31 +0200 Subject: [PATCH] add support for a hint label in custom keys this is used in worf-warden to show that shift can be used as additional modifier to send enter --- README.md | 3 + examples/worf-warden/src/main.rs | 85 +++++++++++++----- styles/compact/style.css | 9 +- worf/src/lib/config.rs | 6 ++ worf/src/lib/gui.rs | 143 +++++++++++++++++++++---------- 5 files changed, 179 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 0b1161d..a8c7b2f 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,9 @@ Styling names and classes are inspired by wofi, so most of the documentation and | `row` | Row containing the entry, used to control hover effects. | | `custom-key-label-text` | The label for custom keys | | `custom-key-label-box` | Box containing the label, can be used for borders etc. | +| `custom-key-hint-text` | The label for custom keys hint | +| `custom-key-hint-box` | Box containing the hint, can be used for borders etc. | + Checkout more showcases in the [styles directory of this repo](styles). diff --git a/examples/worf-warden/src/main.rs b/examples/worf-warden/src/main.rs index fbc64b0..c2b07ae 100644 --- a/examples/worf-warden/src/main.rs +++ b/examples/worf-warden/src/main.rs @@ -3,9 +3,9 @@ use std::env; use std::process::Command; use std::thread::sleep; use std::time::Duration; -use worf_lib::config::Config; +use worf_lib::config::{Config, CustomKeyHintLocation}; use worf_lib::desktop::{copy_to_clipboard, spawn_fork}; -use worf_lib::gui::{ItemProvider, Key, KeyBinding, MenuItem, Modifier}; +use worf_lib::gui::{CustomKeyHint, CustomKeys, ItemProvider, Key, KeyBinding, MenuItem, Modifier}; use worf_lib::{config, gui}; #[derive(Clone)] @@ -189,6 +189,16 @@ fn key_type_all() -> KeyBinding { key: Key::Num1, modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+1 Type All".to_string(), + visible: true, + } +} + +fn key_type_all_and_enter() -> KeyBinding { + KeyBinding { + key: Key::Exclamation, + modifiers: vec![Modifier::Alt, Modifier::Shift].into_iter().collect(), + label: String::new(), + visible: false, } } @@ -197,6 +207,16 @@ fn key_type_user() -> KeyBinding { key: Key::Num2, modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+2 Type User".to_string(), + visible: true, + } +} + +fn key_type_user_and_enter() -> KeyBinding { + KeyBinding { + key: Key::At, + modifiers: vec![Modifier::Alt, Modifier::Shift].into_iter().collect(), + label: String::new(), + visible: false, } } @@ -205,6 +225,7 @@ fn key_type_password() -> KeyBinding { key: Key::Num3, modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+3 Type Password".to_string(), + visible: true, } } @@ -212,7 +233,8 @@ fn key_type_password_and_enter() -> KeyBinding { KeyBinding { key: Key::Hash, modifiers: vec![Modifier::Alt, Modifier::Shift].into_iter().collect(), - label: "Alt+Shift+3 Type Password + Enter".to_string(), + label: String::new(), + visible: false, } } @@ -221,6 +243,16 @@ fn key_type_totp() -> KeyBinding { key: Key::Num4, modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+4 Type Totp".to_string(), + visible: true, + } +} + +fn key_type_totp_and_enter() -> KeyBinding { + KeyBinding { + key: Key::Dollar, + modifiers: vec![Modifier::Alt, Modifier::Shift].into_iter().collect(), + label: String::new(), + visible: false, } } @@ -229,6 +261,7 @@ fn key_sync() -> KeyBinding { key: Key::R, modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+r Sync".to_string(), + visible: true, } } @@ -238,6 +271,7 @@ fn key_totp() -> KeyBinding { key: Key::T, modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+t Totp".to_string(), + visible: true, } } @@ -246,6 +280,7 @@ fn key_lock() -> KeyBinding { key: Key::L, modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+l Lock".to_string(), + visible: true, } } @@ -255,16 +290,25 @@ fn show(config: Config, provider: PasswordProvider) -> Result<(), String> { provider, false, None, - Some(vec![ - key_type_all(), - key_type_user(), - key_type_password(), - key_type_password_and_enter(), - key_type_totp(), - key_sync(), - key_totp(), - key_lock(), - ]), + Some(CustomKeys { + bindings: vec![ + key_type_all(), + key_type_all_and_enter(), + key_type_user(), + key_type_user_and_enter(), + key_type_password(), + key_type_password_and_enter(), + key_type_totp(), + key_type_totp_and_enter(), + key_sync(), + key_totp(), + key_lock(), + ], + hint: Some(CustomKeyHint { + label: "Use Shift as additional modifier to send enter".to_string(), + location: CustomKeyHintLocation::Top, + }), + }), ) { Ok(selection) => { if let Some(meta) = selection.menu.data { @@ -276,18 +320,15 @@ fn show(config: Config, provider: PasswordProvider) -> Result<(), String> { sleep(Duration::from_millis(500)); if let Some(key) = selection.custom_key { - if key == key_type_all() { + if key == key_type_all() || key == key_type_all_and_enter() { keyboard_type(&rbw_get_user(id, false)?); keyboard_tab(); keyboard_type(&rbw_get_password(id, false)?); - } else if key == key_type_user() { + } else if key == key_type_user() || key == key_type_user_and_enter() { keyboard_type(&rbw_get_user(id, false)?); - } else if key == key_type_password() { + } else if key == key_type_password() || key == key_type_password_and_enter() { keyboard_type(&rbw_get_password(id, false)?); - } else if key == key_type_password_and_enter() { - keyboard_type(&rbw_get_password(id, false)?); - keyboard_return(); - } else if key == key_type_totp() { + } else if key == key_type_totp() || key == key_type_totp_and_enter() { keyboard_type(&rbw_get_totp(id, false)?); } else if key == key_lock() { rbw("lock", None)?; @@ -296,6 +337,10 @@ fn show(config: Config, provider: PasswordProvider) -> Result<(), String> { } else if key == key_totp() { rbw_get_totp(id, true)?; } + + if key.modifiers.contains(&Modifier::Shift) { + keyboard_return(); + } } else { let pw = rbw_get_password(id, true)?; if let Err(e) = copy_to_clipboard(pw, None) { diff --git a/styles/compact/style.css b/styles/compact/style.css index 81837dd..83d3a16 100644 --- a/styles/compact/style.css +++ b/styles/compact/style.css @@ -74,8 +74,15 @@ font-family: DejaVu; margin-bottom: 0.25em; border-right: 1px solid rgba(214, 174, 0, 1); border-left: 1px solid rgba(214, 174, 0, 1); + border-bottom: 1px solid rgba(214, 174, 0, 1); + padding-left: 1em; +} + +#custom-key-hint-box { + margin-top: 0.25em; + margin-bottom: 0.25em; padding-left: 1em; } #custom-key-label-text { - } +} diff --git a/worf/src/lib/config.rs b/worf/src/lib/config.rs index edc4f7b..4a05a6d 100644 --- a/worf/src/lib/config.rs +++ b/worf/src/lib/config.rs @@ -49,6 +49,12 @@ pub enum SortOrder { Alphabetical, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum CustomKeyHintLocation { + Top, + Bottom, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Mode { /// searches `$PATH` for executables and allows them to be run by selecting them. diff --git a/worf/src/lib/gui.rs b/worf/src/lib/gui.rs index cbbe9e1..4442f18 100644 --- a/worf/src/lib/gui.rs +++ b/worf/src/lib/gui.rs @@ -25,7 +25,7 @@ use gtk4_layer_shell::{Edge, KeyboardMode, LayerShell}; use log; use regex::Regex; -use crate::config::{Anchor, Config, MatchMethod, SortOrder, WrapMode}; +use crate::config::{Anchor, Config, CustomKeyHintLocation, MatchMethod, SortOrder, WrapMode}; use crate::desktop::known_image_extension_regex_pattern; use crate::{Error, config, desktop}; @@ -83,17 +83,11 @@ impl From for Align { } } -fn into_core_order(gtk_order: &Ordering) -> core::cmp::Ordering { +fn into_core_order(gtk_order: Ordering) -> core::cmp::Ordering { match gtk_order { - Ordering::Smaller => { - core::cmp::Ordering::Less - } - Ordering::Larger => { - core::cmp::Ordering::Greater - } - _ => { - core::cmp::Ordering::Equal - } + Ordering::Smaller => core::cmp::Ordering::Less, + Ordering::Larger => core::cmp::Ordering::Greater, + _ => core::cmp::Ordering::Equal, } } @@ -386,6 +380,19 @@ pub struct KeyBinding { pub key: Key, pub modifiers: HashSet, pub label: String, + pub visible: bool, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct CustomKeyHint { + pub label: String, + pub location: CustomKeyHintLocation, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct CustomKeys { + pub bindings: Vec, + pub hint: Option, } impl MenuItem { @@ -445,7 +452,7 @@ pub fn show( item_provider: P, new_on_empty: bool, search_ignored_words: Option>, - custom_keys: Option>, + custom_keys: Option, ) -> Result, Error> where T: Clone + 'static + Send, @@ -503,7 +510,7 @@ fn build_ui( app: Application, new_on_empty: bool, search_ignored_words: Option>, - custom_keys: Option<&Vec>, + custom_keys: Option<&CustomKeys>, ) where T: Clone + 'static + Send, P: ItemProvider + 'static + Send, @@ -569,7 +576,9 @@ fn build_ui( let outer_box = gtk4::Box::new(config.orientation().into(), 0); outer_box.set_widget_name("outer-box"); outer_box.append(&ui_elements.search); - build_custom_key_view(custom_keys, &outer_box); + if let Some(custom_keys) = custom_keys { + build_custom_key_view(custom_keys, &outer_box); + } ui_elements.window.set_child(Some(&outer_box)); @@ -595,15 +604,11 @@ fn build_ui( log::debug!("got items after {:?}", wait_for_items.elapsed()); let active_cfg = config.clone(); - //let map_cfg = config.clone(); let animate_window = ui_elements.window.clone(); animate_window.connect_is_active_notify(move |w| { window_show_resize(&active_cfg.clone(), w); }); - // animate_window.connect_map(move |w| { - // window_show_resize(&map_cfg.clone(), w); - // }); build_ui_from_menu_items(&ui_elements, &meta, provider_elements); @@ -626,7 +631,6 @@ fn build_main_box(config: &Config, ui_elements: &Rc( } } -fn build_custom_key_view(custom_keys: Option<&Vec>, outer_box: >k4::Box) { - let inner_box = FlowBox::new(); +fn build_custom_key_view(custom_keys: &CustomKeys, outer_box: >k4::Box) { + fn create_label(inner_box: &FlowBox, text: &str, label_css: &str, box_css: &str) { + let label_box = FlowBoxChild::new(); + label_box.set_halign(Align::Fill); + inner_box.set_valign(Align::Start); + label_box.set_widget_name(box_css); + inner_box.append(&label_box); + inner_box.set_vexpand(false); + inner_box.set_hexpand(false); + let label = Label::new(Some(text)); + label.set_halign(Align::Fill); + label.set_valign(Align::Start); + label.set_use_markup(true); + label.set_hexpand(true); + label.set_vexpand(false); + label.set_widget_name(label_css); + label.set_wrap(false); + label.set_xalign(0.0); + label_box.set_child(Some(&label)); + } + + let inner_box = gtk4::Box::new(Orientation::Vertical, 0); inner_box.set_halign(Align::Fill); - inner_box.set_widget_name("custom-key-box"); - if let Some(custom_keys) = custom_keys { - for key in custom_keys { - let label_box = FlowBoxChild::new(); - label_box.set_halign(Align::Fill); - inner_box.set_valign(Align::Start); - label_box.set_widget_name("custom-key-label-box"); - inner_box.append(&label_box); - inner_box.set_vexpand(false); - inner_box.set_hexpand(false); - let label = Label::new(Some(&key.label)); - label.set_halign(Align::Fill); - label.set_valign(Align::Start); - label.set_use_markup(true); - label.set_hexpand(true); - label.set_vexpand(false); - label.set_widget_name("custom-key-label-text"); - label.set_wrap(false); - label.set_xalign(0.0); - label_box.set_child(Some(&label)); + + let hint_box = FlowBox::new(); + hint_box.set_halign(Align::Fill); + hint_box.set_widget_name("custom-key-box"); + + let custom_key_box = FlowBox::new(); + custom_key_box.set_halign(Align::Fill); + custom_key_box.set_widget_name("custom-key-box"); + inner_box.append(&custom_key_box); + + let make_key_labels = || { + for key in custom_keys.bindings.iter().filter(|key| key.visible) { + create_label( + &custom_key_box, + key.label.as_ref(), + "custom-key-label-text", + "custom-key-label-box", + ); + } + }; + + if let Some(hint) = custom_keys.hint.as_ref() { + match hint.location { + CustomKeyHintLocation::Top => { + inner_box.append(&hint_box); + create_label( + &hint_box, + &hint.label, + "custom-key-hint-text", + "custom-key-hint-box", + ); + make_key_labels(); + } // todo this surely can be done better + CustomKeyHintLocation::Bottom => { + make_key_labels(); + create_label( + &hint_box, + &hint.label, + "custom-key-hint-text", + "custom-key-hint-box", + ); + inner_box.append(&hint_box); + } } } + outer_box.append(&inner_box); } @@ -771,7 +819,7 @@ fn build_ui_from_menu_items( fn setup_key_event_handler( ui: &Rc>, meta: &Rc>, - custom_keys: Option<&Vec>, + custom_keys: Option<&CustomKeys>, ) { let key_controller = EventControllerKey::new(); @@ -797,7 +845,7 @@ fn handle_key_press( meta: &Rc>, keyboard_key: gdk4::Key, modifier_type: gdk4::ModifierType, - custom_keys: Option<&Vec>, + custom_keys: Option<&CustomKeys>, ) -> Propagation { let update_view = |query: &String| { let mut lock = ui.menu_rows.write().unwrap(); @@ -820,7 +868,7 @@ fn handle_key_press( if let Some(custom_keys) = custom_keys { let mods = modifiers_from_mask(modifier_type); - for custom_key in custom_keys { + for custom_key in &custom_keys.bindings { log::debug!( "comparing custom key {custom_key:?} to mask {mods:?} and key {keyboard_key}" ); @@ -935,7 +983,10 @@ fn sort_flow_box_childs( sort_menu_items_by_score(m1, m2) } -fn sort_menu_items_by_score(m1: Option<&MenuItem>, m2: Option<&MenuItem>) -> Ordering { +fn sort_menu_items_by_score( + m1: Option<&MenuItem>, + m2: Option<&MenuItem>, +) -> Ordering { match (m1, m2) { (Some(menu1), Some(menu2)) => { fn compare(a: f64, b: f64) -> Ordering { @@ -1376,7 +1427,7 @@ pub fn apply_sort(items: &mut [MenuItem], order: &SortOrder) { } } - items.sort_by(|l, r| into_core_order(&sort_menu_items_by_score(Some(l), Some(r)))); + items.sort_by(|l, r| into_core_order(sort_menu_items_by_score(Some(l), Some(r)))); } } }