add more custom key support
This commit is contained in:
parent
53cceb499e
commit
8770a31971
4 changed files with 475 additions and 348 deletions
|
@ -176,6 +176,8 @@ The possibilities are endless! Here are some powerful examples of what you can b
|
||||||
- Removed x,y offset and global coords as GTK4 does not support this anymore, similar results can be achieved with `--location`
|
- Removed x,y offset and global coords as GTK4 does not support this anymore, similar results can be achieved with `--location`
|
||||||
- Removed copy_exec as we are not executing a binary to copy data into the clipboard
|
- Removed copy_exec as we are not executing a binary to copy data into the clipboard
|
||||||
- `exec-search` not supported
|
- `exec-search` not supported
|
||||||
|
- All custom keys that change the default bindings for navigation like up, down, page, etc.
|
||||||
|
- key_custom_(n) is not supported, such specialized behaviour can be achieved via the API though.
|
||||||
|
|
||||||
#### Removed Command Line Arguments
|
#### Removed Command Line Arguments
|
||||||
- `mode` → Use `show` instead
|
- `mode` → Use `show` instead
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::{collections::HashMap, env, process::Command, thread::sleep, time::Duration};
|
use std::{collections::HashMap, env, process::Command, thread::sleep, time::Duration};
|
||||||
|
|
||||||
use worf::{
|
use worf::{
|
||||||
config::{self, Config, CustomKeyHintLocation},
|
config::{self, Config, CustomKeyHintLocation, Key},
|
||||||
desktop::{copy_to_clipboard, spawn_fork},
|
desktop::{copy_to_clipboard, spawn_fork},
|
||||||
gui::{self, CustomKeyHint, CustomKeys, ItemProvider, Key, KeyBinding, MenuItem, Modifier},
|
gui::{self, CustomKeyHint, CustomKeys, ItemProvider, KeyBinding, MenuItem, Modifier},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -192,6 +192,233 @@ impl FromStr for KeyDetectionType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub enum Key {
|
||||||
|
None,
|
||||||
|
|
||||||
|
// Letters
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
D,
|
||||||
|
E,
|
||||||
|
F,
|
||||||
|
G,
|
||||||
|
H,
|
||||||
|
I,
|
||||||
|
J,
|
||||||
|
K,
|
||||||
|
L,
|
||||||
|
M,
|
||||||
|
N,
|
||||||
|
O,
|
||||||
|
P,
|
||||||
|
Q,
|
||||||
|
R,
|
||||||
|
S,
|
||||||
|
T,
|
||||||
|
U,
|
||||||
|
V,
|
||||||
|
W,
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
Z,
|
||||||
|
|
||||||
|
// Numbers
|
||||||
|
Num0,
|
||||||
|
Num1,
|
||||||
|
Num2,
|
||||||
|
Num3,
|
||||||
|
Num4,
|
||||||
|
Num5,
|
||||||
|
Num6,
|
||||||
|
Num7,
|
||||||
|
Num8,
|
||||||
|
Num9,
|
||||||
|
|
||||||
|
// Function Keys
|
||||||
|
F1,
|
||||||
|
F2,
|
||||||
|
F3,
|
||||||
|
F4,
|
||||||
|
F5,
|
||||||
|
F6,
|
||||||
|
F7,
|
||||||
|
F8,
|
||||||
|
F9,
|
||||||
|
F10,
|
||||||
|
F11,
|
||||||
|
F12,
|
||||||
|
|
||||||
|
// Navigation / Editing
|
||||||
|
Escape,
|
||||||
|
Enter,
|
||||||
|
Space,
|
||||||
|
Tab,
|
||||||
|
Backspace,
|
||||||
|
Insert,
|
||||||
|
Delete,
|
||||||
|
Home,
|
||||||
|
End,
|
||||||
|
PageUp,
|
||||||
|
PageDown,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
|
||||||
|
// Special characters
|
||||||
|
Exclamation, // !
|
||||||
|
At, // @
|
||||||
|
Hash, // #
|
||||||
|
Dollar, // $
|
||||||
|
Percent, // %
|
||||||
|
Caret, // ^
|
||||||
|
Ampersand, // &
|
||||||
|
Asterisk, // *
|
||||||
|
LeftParen, // (
|
||||||
|
RightParen, // )
|
||||||
|
Minus, // -
|
||||||
|
Underscore, // _
|
||||||
|
Equal, // =
|
||||||
|
Plus, // +
|
||||||
|
LeftBracket, // [
|
||||||
|
RightBracket, // ]
|
||||||
|
LeftBrace, // {
|
||||||
|
RightBrace, // }
|
||||||
|
Backslash, // \
|
||||||
|
Pipe, // |
|
||||||
|
Semicolon, // ;
|
||||||
|
Colon, // :
|
||||||
|
Apostrophe, // '
|
||||||
|
Quote, // "
|
||||||
|
Comma, // ,
|
||||||
|
Period, // .
|
||||||
|
Slash, // /
|
||||||
|
Question, // ?
|
||||||
|
Grave, // `
|
||||||
|
Tilde, // ~
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Key {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)] // won't fix, need all of them
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let key = match s {
|
||||||
|
// Letters
|
||||||
|
"A" | "a" => Key::A,
|
||||||
|
"B" | "b" => Key::B,
|
||||||
|
"C" | "c" => Key::C,
|
||||||
|
"D" | "d" => Key::D,
|
||||||
|
"E" | "e" => Key::E,
|
||||||
|
"F" | "f" => Key::F,
|
||||||
|
"G" | "g" => Key::G,
|
||||||
|
"H" | "h" => Key::H,
|
||||||
|
"I" | "i" => Key::I,
|
||||||
|
"J" | "j" => Key::J,
|
||||||
|
"K" | "k" => Key::K,
|
||||||
|
"L" | "l" => Key::L,
|
||||||
|
"M" | "m" => Key::M,
|
||||||
|
"N" | "n" => Key::N,
|
||||||
|
"O" | "o" => Key::O,
|
||||||
|
"P" | "p" => Key::P,
|
||||||
|
"Q" | "q" => Key::Q,
|
||||||
|
"R" | "r" => Key::R,
|
||||||
|
"S" | "s" => Key::S,
|
||||||
|
"T" | "t" => Key::T,
|
||||||
|
"U" | "u" => Key::U,
|
||||||
|
"V" | "v" => Key::V,
|
||||||
|
"W" | "w" => Key::W,
|
||||||
|
"X" | "x" => Key::X,
|
||||||
|
"Y" | "y" => Key::Y,
|
||||||
|
"Z" | "z" => Key::Z,
|
||||||
|
|
||||||
|
// Numbers
|
||||||
|
"0" => Key::Num0,
|
||||||
|
"1" => Key::Num1,
|
||||||
|
"2" => Key::Num2,
|
||||||
|
"3" => Key::Num3,
|
||||||
|
"4" => Key::Num4,
|
||||||
|
"5" => Key::Num5,
|
||||||
|
"6" => Key::Num6,
|
||||||
|
"7" => Key::Num7,
|
||||||
|
"8" => Key::Num8,
|
||||||
|
"9" => Key::Num9,
|
||||||
|
|
||||||
|
// Function keys
|
||||||
|
"F1" => Key::F1,
|
||||||
|
"F2" => Key::F2,
|
||||||
|
"F3" => Key::F3,
|
||||||
|
"F4" => Key::F4,
|
||||||
|
"F5" => Key::F5,
|
||||||
|
"F6" => Key::F6,
|
||||||
|
"F7" => Key::F7,
|
||||||
|
"F8" => Key::F8,
|
||||||
|
"F9" => Key::F9,
|
||||||
|
"F10" => Key::F10,
|
||||||
|
"F11" => Key::F11,
|
||||||
|
"F12" => Key::F12,
|
||||||
|
|
||||||
|
// Navigation / Editing
|
||||||
|
"Escape" => Key::Escape,
|
||||||
|
"Enter" => Key::Enter,
|
||||||
|
"Space" => Key::Space,
|
||||||
|
"Tab" => Key::Tab,
|
||||||
|
"Backspace" => Key::Backspace,
|
||||||
|
"Insert" => Key::Insert,
|
||||||
|
"Delete" => Key::Delete,
|
||||||
|
"Home" => Key::Home,
|
||||||
|
"End" => Key::End,
|
||||||
|
"PageUp" => Key::PageUp,
|
||||||
|
"PageDown" => Key::PageDown,
|
||||||
|
"Left" => Key::Left,
|
||||||
|
"Right" => Key::Right,
|
||||||
|
"Up" => Key::Up,
|
||||||
|
"Down" => Key::Down,
|
||||||
|
|
||||||
|
// Special characters
|
||||||
|
"!" => Key::Exclamation,
|
||||||
|
"@" => Key::At,
|
||||||
|
"#" => Key::Hash,
|
||||||
|
"$" => Key::Dollar,
|
||||||
|
"%" => Key::Percent,
|
||||||
|
"^" => Key::Caret,
|
||||||
|
"&" => Key::Ampersand,
|
||||||
|
"*" => Key::Asterisk,
|
||||||
|
"(" => Key::LeftParen,
|
||||||
|
")" => Key::RightParen,
|
||||||
|
"-" => Key::Minus,
|
||||||
|
"_" => Key::Underscore,
|
||||||
|
"=" => Key::Equal,
|
||||||
|
"+" => Key::Plus,
|
||||||
|
"[" => Key::LeftBracket,
|
||||||
|
"]" => Key::RightBracket,
|
||||||
|
"{" => Key::LeftBrace,
|
||||||
|
"}" => Key::RightBrace,
|
||||||
|
"\\" => Key::Backslash,
|
||||||
|
"|" => Key::Pipe,
|
||||||
|
";" => Key::Semicolon,
|
||||||
|
":" => Key::Colon,
|
||||||
|
"'" => Key::Apostrophe,
|
||||||
|
"\"" => Key::Quote,
|
||||||
|
"," => Key::Comma,
|
||||||
|
"." => Key::Period,
|
||||||
|
"/" => Key::Slash,
|
||||||
|
"?" => Key::Question,
|
||||||
|
"`" => Key::Grave,
|
||||||
|
"~" => Key::Tilde,
|
||||||
|
_ => Key::None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if key == Key::None {
|
||||||
|
Err(Error::InvalidArgument(format!("{s} is not a valid key")))
|
||||||
|
} else {
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, Parser)]
|
#[derive(Debug, Deserialize, Serialize, Clone, Parser)]
|
||||||
#[clap(about = "Worf is a wofi clone written in rust, it aims to be a drop-in replacement")]
|
#[clap(about = "Worf is a wofi clone written in rust, it aims to be a drop-in replacement")]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -353,28 +580,30 @@ pub struct Config {
|
||||||
#[clap(long = "hide-search")]
|
#[clap(long = "hide-search")]
|
||||||
hide_search: Option<bool>,
|
hide_search: Option<bool>,
|
||||||
|
|
||||||
/// can be set to a button to toggle the search bar.
|
/// can be set to a key to toggle the search bar.
|
||||||
/// default is not set.
|
/// default is not set.
|
||||||
#[clap(long = "key-hide-search")]
|
#[clap(long = "key-hide-search")]
|
||||||
key_hide_search: Option<String>,
|
key_hide_search: Option<Key>,
|
||||||
|
|
||||||
// key_up: Option<String>, // todo support this
|
/// Key to run the associated thing.
|
||||||
// key_down: Option<String>, // todo support this
|
/// Defaults to enter
|
||||||
// key_left: Option<String>, // todo support this
|
#[clap(long = "key-submit")]
|
||||||
// key_right: Option<String>, // todo support this
|
key_submit: Option<Key>,
|
||||||
// key_forward: Option<String>, // todo support this
|
|
||||||
// key_backward: Option<String>, // todo support this
|
|
||||||
// key_submit: Option<String>, // todo support this
|
|
||||||
// key_exit: Option<String>, // todo support this
|
|
||||||
// key_pgup: Option<String>, // todo support this
|
|
||||||
// key_pgdn: Option<String>, // todo support this
|
|
||||||
// key_expand: Option<String>, // todo support this
|
|
||||||
|
|
||||||
// key_copy: Option<String>, // todo support this
|
/// Key to close the window.
|
||||||
|
/// Defaults to escape
|
||||||
|
#[clap(long = "key-exit")]
|
||||||
|
key_exit: Option<Key>,
|
||||||
|
|
||||||
|
/// Can be set to a Key which copies the action to the clipboard.
|
||||||
|
/// Copying to clipboard does not affect any cache file
|
||||||
|
#[clap(long = "key-copy")]
|
||||||
|
key_copy: Option<Key>,
|
||||||
|
|
||||||
|
/// Used to expand or autocomplete entries. Defaults to tab
|
||||||
|
#[clap(long = "key-expand")]
|
||||||
|
key_expand: Option<Key>,
|
||||||
|
|
||||||
// todo re-add this
|
|
||||||
// #[serde(flatten)]
|
|
||||||
// key_custom: Option<HashMap<String, String>>,
|
|
||||||
/// If enabled, worf will resize according to the amount of displayed rows
|
/// If enabled, worf will resize according to the amount of displayed rows
|
||||||
/// defaults to false
|
/// defaults to false
|
||||||
#[clap(long = "dynamic-lines")]
|
#[clap(long = "dynamic-lines")]
|
||||||
|
@ -629,8 +858,28 @@ impl Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn key_hide_search(&self) -> Option<String> {
|
pub fn key_hide_search(&self) -> Option<Key> {
|
||||||
self.key_hide_search.clone()
|
self.key_hide_search
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn key_submit(&self) -> Key {
|
||||||
|
self.key_submit.unwrap_or(Key::Enter)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn key_exit(&self) -> Key {
|
||||||
|
self.key_exit.unwrap_or(Key::Escape)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn key_copy(&self) -> Option<Key> {
|
||||||
|
self.key_copy
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn key_expand(&self) -> Key {
|
||||||
|
self.key_expand.unwrap_or(Key::Tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
|
@ -30,9 +30,10 @@ use regex::Regex;
|
||||||
use crate::{
|
use crate::{
|
||||||
Error,
|
Error,
|
||||||
config::{
|
config::{
|
||||||
self, Anchor, Config, CustomKeyHintLocation, KeyDetectionType, MatchMethod, SortOrder,
|
self, Anchor, Config, CustomKeyHintLocation, Key, KeyDetectionType, MatchMethod, SortOrder,
|
||||||
WrapMode,
|
WrapMode,
|
||||||
},
|
},
|
||||||
|
desktop,
|
||||||
desktop::known_image_extension_regex_pattern,
|
desktop::known_image_extension_regex_pattern,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,225 +125,6 @@ pub struct MenuItem<T: Clone> {
|
||||||
visible: bool,
|
visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum Key {
|
|
||||||
None,
|
|
||||||
|
|
||||||
// Letters
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
C,
|
|
||||||
D,
|
|
||||||
E,
|
|
||||||
F,
|
|
||||||
G,
|
|
||||||
H,
|
|
||||||
I,
|
|
||||||
J,
|
|
||||||
K,
|
|
||||||
L,
|
|
||||||
M,
|
|
||||||
N,
|
|
||||||
O,
|
|
||||||
P,
|
|
||||||
Q,
|
|
||||||
R,
|
|
||||||
S,
|
|
||||||
T,
|
|
||||||
U,
|
|
||||||
V,
|
|
||||||
W,
|
|
||||||
X,
|
|
||||||
Y,
|
|
||||||
Z,
|
|
||||||
|
|
||||||
// Numbers
|
|
||||||
Num0,
|
|
||||||
Num1,
|
|
||||||
Num2,
|
|
||||||
Num3,
|
|
||||||
Num4,
|
|
||||||
Num5,
|
|
||||||
Num6,
|
|
||||||
Num7,
|
|
||||||
Num8,
|
|
||||||
Num9,
|
|
||||||
|
|
||||||
// Function Keys
|
|
||||||
F1,
|
|
||||||
F2,
|
|
||||||
F3,
|
|
||||||
F4,
|
|
||||||
F5,
|
|
||||||
F6,
|
|
||||||
F7,
|
|
||||||
F8,
|
|
||||||
F9,
|
|
||||||
F10,
|
|
||||||
F11,
|
|
||||||
F12,
|
|
||||||
|
|
||||||
// Navigation / Editing
|
|
||||||
Escape,
|
|
||||||
Enter,
|
|
||||||
Space,
|
|
||||||
Tab,
|
|
||||||
Backspace,
|
|
||||||
Insert,
|
|
||||||
Delete,
|
|
||||||
Home,
|
|
||||||
End,
|
|
||||||
PageUp,
|
|
||||||
PageDown,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Up,
|
|
||||||
Down,
|
|
||||||
|
|
||||||
// Special characters
|
|
||||||
Exclamation, // !
|
|
||||||
At, // @
|
|
||||||
Hash, // #
|
|
||||||
Dollar, // $
|
|
||||||
Percent, // %
|
|
||||||
Caret, // ^
|
|
||||||
Ampersand, // &
|
|
||||||
Asterisk, // *
|
|
||||||
LeftParen, // (
|
|
||||||
RightParen, // )
|
|
||||||
Minus, // -
|
|
||||||
Underscore, // _
|
|
||||||
Equal, // =
|
|
||||||
Plus, // +
|
|
||||||
LeftBracket, // [
|
|
||||||
RightBracket, // ]
|
|
||||||
LeftBrace, // {
|
|
||||||
RightBrace, // }
|
|
||||||
Backslash, // \
|
|
||||||
Pipe, // |
|
|
||||||
Semicolon, // ;
|
|
||||||
Colon, // :
|
|
||||||
Apostrophe, // '
|
|
||||||
Quote, // "
|
|
||||||
Comma, // ,
|
|
||||||
Period, // .
|
|
||||||
Slash, // /
|
|
||||||
Question, // ?
|
|
||||||
Grave, // `
|
|
||||||
Tilde, // ~
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Key {
|
|
||||||
fn from(value: String) -> Self {
|
|
||||||
match value.as_str() {
|
|
||||||
// Letters
|
|
||||||
"A" | "a" => Key::A,
|
|
||||||
"B" | "b" => Key::B,
|
|
||||||
"C" | "c" => Key::C,
|
|
||||||
"D" | "d" => Key::D,
|
|
||||||
"E" | "e" => Key::E,
|
|
||||||
"F" | "f" => Key::F,
|
|
||||||
"G" | "g" => Key::G,
|
|
||||||
"H" | "h" => Key::H,
|
|
||||||
"I" | "i" => Key::I,
|
|
||||||
"J" | "j" => Key::J,
|
|
||||||
"K" | "k" => Key::K,
|
|
||||||
"L" | "l" => Key::L,
|
|
||||||
"M" | "m" => Key::M,
|
|
||||||
"N" | "n" => Key::N,
|
|
||||||
"O" | "o" => Key::O,
|
|
||||||
"P" | "p" => Key::P,
|
|
||||||
"Q" | "q" => Key::Q,
|
|
||||||
"R" | "r" => Key::R,
|
|
||||||
"S" | "s" => Key::S,
|
|
||||||
"T" | "t" => Key::T,
|
|
||||||
"U" | "u" => Key::U,
|
|
||||||
"V" | "v" => Key::V,
|
|
||||||
"W" | "w" => Key::W,
|
|
||||||
"X" | "x" => Key::X,
|
|
||||||
"Y" | "y" => Key::Y,
|
|
||||||
"Z" | "z" => Key::Z,
|
|
||||||
|
|
||||||
// Numbers
|
|
||||||
"0" => Key::Num0,
|
|
||||||
"1" => Key::Num1,
|
|
||||||
"2" => Key::Num2,
|
|
||||||
"3" => Key::Num3,
|
|
||||||
"4" => Key::Num4,
|
|
||||||
"5" => Key::Num5,
|
|
||||||
"6" => Key::Num6,
|
|
||||||
"7" => Key::Num7,
|
|
||||||
"8" => Key::Num8,
|
|
||||||
"9" => Key::Num9,
|
|
||||||
|
|
||||||
// Function keys
|
|
||||||
"F1" => Key::F1,
|
|
||||||
"F2" => Key::F2,
|
|
||||||
"F3" => Key::F3,
|
|
||||||
"F4" => Key::F4,
|
|
||||||
"F5" => Key::F5,
|
|
||||||
"F6" => Key::F6,
|
|
||||||
"F7" => Key::F7,
|
|
||||||
"F8" => Key::F8,
|
|
||||||
"F9" => Key::F9,
|
|
||||||
"F10" => Key::F10,
|
|
||||||
"F11" => Key::F11,
|
|
||||||
"F12" => Key::F12,
|
|
||||||
|
|
||||||
// Navigation / Editing
|
|
||||||
"Escape" => Key::Escape,
|
|
||||||
"Enter" => Key::Enter,
|
|
||||||
"Space" => Key::Space,
|
|
||||||
"Tab" => Key::Tab,
|
|
||||||
"Backspace" => Key::Backspace,
|
|
||||||
"Insert" => Key::Insert,
|
|
||||||
"Delete" => Key::Delete,
|
|
||||||
"Home" => Key::Home,
|
|
||||||
"End" => Key::End,
|
|
||||||
"PageUp" => Key::PageUp,
|
|
||||||
"PageDown" => Key::PageDown,
|
|
||||||
"Left" => Key::Left,
|
|
||||||
"Right" => Key::Right,
|
|
||||||
"Up" => Key::Up,
|
|
||||||
"Down" => Key::Down,
|
|
||||||
|
|
||||||
// Special characters
|
|
||||||
"!" => Key::Exclamation,
|
|
||||||
"@" => Key::At,
|
|
||||||
"#" => Key::Hash,
|
|
||||||
"$" => Key::Dollar,
|
|
||||||
"%" => Key::Percent,
|
|
||||||
"^" => Key::Caret,
|
|
||||||
"&" => Key::Ampersand,
|
|
||||||
"*" => Key::Asterisk,
|
|
||||||
"(" => Key::LeftParen,
|
|
||||||
")" => Key::RightParen,
|
|
||||||
"-" => Key::Minus,
|
|
||||||
"_" => Key::Underscore,
|
|
||||||
"=" => Key::Equal,
|
|
||||||
"+" => Key::Plus,
|
|
||||||
"[" => Key::LeftBracket,
|
|
||||||
"]" => Key::RightBracket,
|
|
||||||
"{" => Key::LeftBrace,
|
|
||||||
"}" => Key::RightBrace,
|
|
||||||
"\\" => Key::Backslash,
|
|
||||||
"|" => Key::Pipe,
|
|
||||||
";" => Key::Semicolon,
|
|
||||||
":" => Key::Colon,
|
|
||||||
"'" => Key::Apostrophe,
|
|
||||||
"\"" => Key::Quote,
|
|
||||||
"," => Key::Comma,
|
|
||||||
"." => Key::Period,
|
|
||||||
"/" => Key::Slash,
|
|
||||||
"?" => Key::Question,
|
|
||||||
"`" => Key::Grave,
|
|
||||||
"~" => Key::Tilde,
|
|
||||||
|
|
||||||
_ => Key::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<gtk4::gdk::Key> for Key {
|
impl From<gtk4::gdk::Key> for Key {
|
||||||
fn from(value: gdk4::Key) -> Self {
|
fn from(value: gdk4::Key) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
@ -933,7 +715,7 @@ fn build_search_entry<T: Clone + Send>(
|
||||||
ui_elements.search.set_visible(false);
|
ui_elements.search.set_visible(false);
|
||||||
}
|
}
|
||||||
if let Some(search) = config.search() {
|
if let Some(search) = config.search() {
|
||||||
set_search_text(&search, ui_elements, meta);
|
set_search_text(ui_elements, meta, &search);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1008,17 +790,17 @@ fn build_custom_key_view(custom_keys: &CustomKeys, outer_box: >k4::Box, inner_
|
||||||
outer_box.append(inner_box);
|
outer_box.append(inner_box);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_search_text<T: Clone + Send>(text: &str, ui: &UiElements<T>, meta: &MetaData<T>) {
|
fn set_search_text<T: Clone + Send>(ui: &UiElements<T>, meta: &MetaData<T>, query: &str) {
|
||||||
let mut lock = ui.search_text.lock().unwrap();
|
let mut lock = ui.search_text.lock().unwrap();
|
||||||
text.clone_into(&mut lock);
|
query.clone_into(&mut lock);
|
||||||
if let Some(pw) = meta.config.password() {
|
if let Some(pw) = meta.config.password() {
|
||||||
let mut ui_text = String::new();
|
let mut ui_text = String::new();
|
||||||
for _ in 0..text.len() {
|
for _ in 0..query.len() {
|
||||||
ui_text += &pw;
|
ui_text += &pw;
|
||||||
}
|
}
|
||||||
ui.search.set_text(&ui_text);
|
ui.search.set_text(&ui_text);
|
||||||
} else {
|
} else {
|
||||||
ui.search.set_text(text);
|
ui.search.set_text(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1112,14 +894,13 @@ fn setup_key_event_handler<T: Clone + 'static + Send>(
|
||||||
ui.window.add_controller(key_controller);
|
ui.window.add_controller(key_controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_key_match_from_str_opt(
|
fn is_key_match(
|
||||||
str_key_opt: Option<String>,
|
key_opt: Option<Key>,
|
||||||
key_detection_type: &KeyDetectionType,
|
key_detection_type: &KeyDetectionType,
|
||||||
key_code: u32,
|
key_code: u32,
|
||||||
gdk_key: gdk4::Key,
|
gdk_key: gdk4::Key,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let Some(str_key) = str_key_opt {
|
if let Some(key) = key_opt {
|
||||||
let key: Key = str_key.into();
|
|
||||||
if key_detection_type == &KeyDetectionType::Code {
|
if key_detection_type == &KeyDetectionType::Code {
|
||||||
key == key_code.into()
|
key == key_code.into()
|
||||||
} else {
|
} else {
|
||||||
|
@ -1130,7 +911,6 @@ fn is_key_match_from_str_opt(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)] // todo fix this.
|
|
||||||
fn handle_key_press<T: Clone + 'static + Send>(
|
fn handle_key_press<T: Clone + 'static + Send>(
|
||||||
ui: &Rc<UiElements<T>>,
|
ui: &Rc<UiElements<T>>,
|
||||||
meta: &Rc<MetaData<T>>,
|
meta: &Rc<MetaData<T>>,
|
||||||
|
@ -1139,33 +919,6 @@ fn handle_key_press<T: Clone + 'static + Send>(
|
||||||
modifier_type: gdk4::ModifierType,
|
modifier_type: gdk4::ModifierType,
|
||||||
custom_keys: Option<&CustomKeys>,
|
custom_keys: Option<&CustomKeys>,
|
||||||
) -> Propagation {
|
) -> Propagation {
|
||||||
let update_view = |query: &String| {
|
|
||||||
let mut lock = ui.menu_rows.write().unwrap();
|
|
||||||
set_menu_visibility_for_search(
|
|
||||||
query,
|
|
||||||
&mut lock,
|
|
||||||
&meta.config,
|
|
||||||
meta.search_ignored_words.as_ref(),
|
|
||||||
);
|
|
||||||
|
|
||||||
select_first_visible_child(&*lock, &ui.main_box);
|
|
||||||
drop(lock);
|
|
||||||
if meta.config.dynamic_lines() {
|
|
||||||
if let Some(geometry) = get_monitor_geometry(ui) {
|
|
||||||
let height = calculate_dynamic_lines_window_height(&meta.config, ui, geometry);
|
|
||||||
ui.window.set_height_request(height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let update_view_from_provider = |query: &String| {
|
|
||||||
let (changed, filtered_list) = meta.item_provider.lock().unwrap().get_elements(Some(query));
|
|
||||||
if changed {
|
|
||||||
build_ui_from_menu_items(ui, meta, filtered_list);
|
|
||||||
}
|
|
||||||
update_view(query);
|
|
||||||
};
|
|
||||||
|
|
||||||
log::debug!("received key. code: {key_code}, key: {keyboard_key:?}");
|
log::debug!("received key. code: {key_code}, key: {keyboard_key:?}");
|
||||||
|
|
||||||
let detection_type = meta.config.key_detection_type();
|
let detection_type = meta.config.key_detection_type();
|
||||||
|
@ -1196,91 +949,204 @@ fn handle_key_press<T: Clone + 'static + Send>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_key_match_from_str_opt(
|
// hide search
|
||||||
|
let propagate = if is_key_match(
|
||||||
meta.config.key_hide_search(),
|
meta.config.key_hide_search(),
|
||||||
&detection_type,
|
&detection_type,
|
||||||
key_code,
|
key_code,
|
||||||
keyboard_key,
|
keyboard_key,
|
||||||
) {
|
) {
|
||||||
ui.search.set_visible(!ui.search.is_visible());
|
handle_key_hide_search(ui)
|
||||||
|
// submit
|
||||||
|
} else if is_key_match(
|
||||||
|
Some(meta.config.key_submit()),
|
||||||
|
&detection_type,
|
||||||
|
key_code,
|
||||||
|
keyboard_key,
|
||||||
|
) {
|
||||||
|
handle_key_submit(ui, meta)
|
||||||
|
}
|
||||||
|
// exit
|
||||||
|
else if is_key_match(
|
||||||
|
Some(meta.config.key_exit()),
|
||||||
|
&detection_type,
|
||||||
|
key_code,
|
||||||
|
keyboard_key,
|
||||||
|
) {
|
||||||
|
handle_key_exit(ui, meta)
|
||||||
|
// copy
|
||||||
|
} else if is_key_match(
|
||||||
|
meta.config.key_copy(),
|
||||||
|
&detection_type,
|
||||||
|
key_code,
|
||||||
|
keyboard_key,
|
||||||
|
) {
|
||||||
|
handle_key_copy(ui, meta)
|
||||||
|
// expand
|
||||||
|
} else if is_key_match(
|
||||||
|
Some(meta.config.key_expand()),
|
||||||
|
&detection_type,
|
||||||
|
key_code,
|
||||||
|
keyboard_key,
|
||||||
|
) {
|
||||||
|
handle_key_expand(ui, meta)
|
||||||
|
} else {
|
||||||
|
Propagation::Proceed
|
||||||
|
};
|
||||||
|
|
||||||
|
if propagate == Propagation::Stop {
|
||||||
|
return propagate;
|
||||||
}
|
}
|
||||||
|
|
||||||
match keyboard_key {
|
match keyboard_key {
|
||||||
gdk4::Key::Escape => {
|
|
||||||
if let Err(e) = meta.selected_sender.send(Err(Error::NoSelection)) {
|
|
||||||
log::error!("failed to send message {e}");
|
|
||||||
}
|
|
||||||
close_gui(&ui.app);
|
|
||||||
}
|
|
||||||
gdk4::Key::Return => {
|
|
||||||
let search_lock = ui.search_text.lock().unwrap();
|
|
||||||
if let Err(e) = handle_selected_item(
|
|
||||||
ui,
|
|
||||||
Rc::<MetaData<T>>::clone(meta),
|
|
||||||
Some(&search_lock),
|
|
||||||
None,
|
|
||||||
meta.new_on_empty,
|
|
||||||
None,
|
|
||||||
) {
|
|
||||||
log::error!("{e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gdk4::Key::BackSpace => {
|
gdk4::Key::BackSpace => {
|
||||||
let mut query = ui.search_text.lock().unwrap().to_string();
|
let mut query = ui.search_text.lock().unwrap().to_string();
|
||||||
if !query.is_empty() {
|
if !query.is_empty() {
|
||||||
query.pop();
|
query.pop();
|
||||||
|
|
||||||
set_search_text(&query, ui, meta);
|
set_search_text(ui, meta, &query);
|
||||||
update_view_from_provider(&query);
|
update_view_from_provider(ui, meta, &query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gdk4::Key::Tab => {
|
|
||||||
if let Some(fb) = ui.main_box.selected_children().first() {
|
|
||||||
if let Some(child) = fb.child() {
|
|
||||||
let expander = child.downcast::<Expander>().ok();
|
|
||||||
if let Some(expander) = expander {
|
|
||||||
expander.set_expanded(true);
|
|
||||||
} else {
|
|
||||||
let opt_changed = {
|
|
||||||
let lock = ui.menu_rows.read().unwrap();
|
|
||||||
let menu_item = lock.get(fb);
|
|
||||||
menu_item.map(|menu_item| {
|
|
||||||
(
|
|
||||||
meta.item_provider
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.get_sub_elements(menu_item),
|
|
||||||
menu_item.label.clone(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(changed) = opt_changed {
|
|
||||||
let items = changed.0.1.unwrap_or_default();
|
|
||||||
if changed.0.0 {
|
|
||||||
build_ui_from_menu_items(ui, meta, items);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = changed.1;
|
|
||||||
set_search_text(&query, ui, meta);
|
|
||||||
update_view(&query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Propagation::Stop;
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(c) = keyboard_key.to_unicode() {
|
if let Some(c) = keyboard_key.to_unicode() {
|
||||||
let query = format!("{}{c}", ui.search_text.lock().unwrap());
|
let query = format!("{}{c}", ui.search_text.lock().unwrap());
|
||||||
set_search_text(&query, ui, meta);
|
set_search_text(ui, meta, &query);
|
||||||
update_view_from_provider(&query);
|
update_view_from_provider(ui, meta, &query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Propagation::Proceed
|
Propagation::Proceed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_view_from_provider<T>(ui: &Rc<UiElements<T>>, meta: &Rc<MetaData<T>>, query: &str)
|
||||||
|
where
|
||||||
|
T: Clone + Send + 'static,
|
||||||
|
{
|
||||||
|
let (changed, filtered_list) = meta.item_provider.lock().unwrap().get_elements(Some(query));
|
||||||
|
if changed {
|
||||||
|
build_ui_from_menu_items(ui, meta, filtered_list);
|
||||||
|
}
|
||||||
|
update_view(ui, meta, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_view<T>(ui: &Rc<UiElements<T>>, meta: &Rc<MetaData<T>>, query: &str)
|
||||||
|
where
|
||||||
|
T: Clone + Send + 'static,
|
||||||
|
{
|
||||||
|
let mut lock = ui.menu_rows.write().unwrap();
|
||||||
|
set_menu_visibility_for_search(
|
||||||
|
query,
|
||||||
|
&mut lock,
|
||||||
|
&meta.config,
|
||||||
|
meta.search_ignored_words.as_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
select_first_visible_child(&*lock, &ui.main_box);
|
||||||
|
drop(lock);
|
||||||
|
if meta.config.dynamic_lines() {
|
||||||
|
if let Some(geometry) = get_monitor_geometry(ui) {
|
||||||
|
let height = calculate_dynamic_lines_window_height(&meta.config, ui, geometry);
|
||||||
|
ui.window.set_height_request(height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key_exit<T>(ui: &Rc<UiElements<T>>, meta: &Rc<MetaData<T>>) -> Propagation
|
||||||
|
where
|
||||||
|
T: Clone + Send + 'static,
|
||||||
|
{
|
||||||
|
if let Err(e) = meta.selected_sender.send(Err(Error::NoSelection)) {
|
||||||
|
log::error!("failed to send message {e}");
|
||||||
|
}
|
||||||
|
close_gui(&ui.app);
|
||||||
|
Propagation::Stop
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key_expand<T>(ui: &Rc<UiElements<T>>, meta: &Rc<MetaData<T>>) -> Propagation
|
||||||
|
where
|
||||||
|
T: Clone + Send + 'static,
|
||||||
|
{
|
||||||
|
if let Some(fb) = ui.main_box.selected_children().first() {
|
||||||
|
if let Some(child) = fb.child() {
|
||||||
|
let expander = child.downcast::<Expander>().ok();
|
||||||
|
if let Some(expander) = expander {
|
||||||
|
expander.set_expanded(true);
|
||||||
|
} else {
|
||||||
|
let opt_changed = {
|
||||||
|
let lock = ui.menu_rows.read().unwrap();
|
||||||
|
let menu_item = lock.get(fb);
|
||||||
|
menu_item.map(|menu_item| {
|
||||||
|
(
|
||||||
|
meta.item_provider
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get_sub_elements(menu_item),
|
||||||
|
menu_item.label.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(changed) = opt_changed {
|
||||||
|
let items = changed.0.1.unwrap_or_default();
|
||||||
|
if changed.0.0 {
|
||||||
|
build_ui_from_menu_items(ui, meta, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = changed.1;
|
||||||
|
set_search_text(ui, meta, &query);
|
||||||
|
update_view(ui, meta, &query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Propagation::Stop
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key_copy<T>(ui: &Rc<UiElements<T>>, meta: &Rc<MetaData<T>>) -> Propagation
|
||||||
|
where
|
||||||
|
T: Clone + Send + 'static,
|
||||||
|
{
|
||||||
|
if let Some(item) = get_selected_item(ui) {
|
||||||
|
if let Some(action) = item.action {
|
||||||
|
if let Err(e) = desktop::copy_to_clipboard(action, None) {
|
||||||
|
log::error!("failed to copy to clipboard: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(e) = meta.selected_sender.send(Err(Error::NoSelection)) {
|
||||||
|
log::error!("failed to send message {e}");
|
||||||
|
}
|
||||||
|
close_gui(&ui.app);
|
||||||
|
Propagation::Stop
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key_submit<T>(ui: &Rc<UiElements<T>>, meta: &Rc<MetaData<T>>) -> Propagation
|
||||||
|
where
|
||||||
|
T: Clone + Send + 'static,
|
||||||
|
{
|
||||||
|
let search_lock = ui.search_text.lock().unwrap();
|
||||||
|
if let Err(e) = handle_selected_item(
|
||||||
|
ui,
|
||||||
|
Rc::<MetaData<T>>::clone(meta),
|
||||||
|
Some(&search_lock),
|
||||||
|
None,
|
||||||
|
meta.new_on_empty,
|
||||||
|
None,
|
||||||
|
) {
|
||||||
|
log::error!("{e}");
|
||||||
|
}
|
||||||
|
Propagation::Stop
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key_hide_search<T>(ui: &Rc<UiElements<T>>) -> Propagation
|
||||||
|
where
|
||||||
|
T: Clone + Send + 'static,
|
||||||
|
{
|
||||||
|
ui.search.set_visible(!ui.search.is_visible());
|
||||||
|
Propagation::Stop
|
||||||
|
}
|
||||||
|
|
||||||
fn sort_flow_box_childs<T: Clone>(
|
fn sort_flow_box_childs<T: Clone>(
|
||||||
child1: &FlowBoxChild,
|
child1: &FlowBoxChild,
|
||||||
child2: &FlowBoxChild,
|
child2: &FlowBoxChild,
|
||||||
|
@ -1446,6 +1312,22 @@ fn visible_row_count<T: Clone + 'static>(ui: &UiElements<T>) -> i32 {
|
||||||
.unwrap_or(i32::MAX)
|
.unwrap_or(i32::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_selected_item<T>(ui: &UiElements<T>) -> Option<MenuItem<T>>
|
||||||
|
where
|
||||||
|
T: Clone + Send + 'static,
|
||||||
|
{
|
||||||
|
if let Some(s) = ui.main_box.selected_children().into_iter().next() {
|
||||||
|
let list_items = ui.menu_rows.read().unwrap();
|
||||||
|
let item = list_items.get(&s);
|
||||||
|
if let Some(selected_item) = item {
|
||||||
|
if selected_item.visible {
|
||||||
|
return Some(selected_item.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
fn handle_selected_item<T>(
|
fn handle_selected_item<T>(
|
||||||
ui: &Rc<UiElements<T>>,
|
ui: &Rc<UiElements<T>>,
|
||||||
meta: Rc<MetaData<T>>,
|
meta: Rc<MetaData<T>>,
|
||||||
|
@ -1460,15 +1342,9 @@ where
|
||||||
if let Some(selected_item) = item {
|
if let Some(selected_item) = item {
|
||||||
send_selected_item(ui, meta, custom_key.cloned(), selected_item);
|
send_selected_item(ui, meta, custom_key.cloned(), selected_item);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else if let Some(s) = ui.main_box.selected_children().into_iter().next() {
|
} else if let Some(item) = get_selected_item(ui) {
|
||||||
let list_items = ui.menu_rows.read().unwrap();
|
send_selected_item(ui, meta, custom_key.cloned(), item);
|
||||||
let item = list_items.get(&s);
|
return Ok(());
|
||||||
if let Some(selected_item) = item {
|
|
||||||
if selected_item.visible {
|
|
||||||
send_selected_item(ui, meta, custom_key.cloned(), selected_item.clone());
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_on_empty {
|
if new_on_empty {
|
||||||
|
|
Loading…
Add table
Reference in a new issue