From 9332899969ce28d2649ff79b892c153a6dd7f30f Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sun, 19 Jan 2020 00:30:30 +0100 Subject: [PATCH] First draft of working passive mode on linux --- native/liblinuxbridge/bridge.cpp | 8 ++++++ native/liblinuxbridge/bridge.h | 4 +++ src/bridge/linux.rs | 1 + src/config/mod.rs | 10 ++++++-- src/engine.rs | 33 ++++++++++++++++++++++++ src/event/mod.rs | 6 ----- src/keyboard/linux.rs | 6 +++++ src/keyboard/mod.rs | 1 + src/matcher/mod.rs | 1 + src/matcher/scrolling.rs | 43 ++++++++++++++++++++++---------- 10 files changed, 92 insertions(+), 21 deletions(-) diff --git a/native/liblinuxbridge/bridge.cpp b/native/liblinuxbridge/bridge.cpp index a34661a..beb9646 100644 --- a/native/liblinuxbridge/bridge.cpp +++ b/native/liblinuxbridge/bridge.cpp @@ -307,6 +307,14 @@ void trigger_alt_shift_ins_paste() { xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Shift+Alt+Insert", 8000); } +void trigger_copy() { + // Release the other keys, for an explanation, read the 'trigger_paste' method + + xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Alt", 8000); + xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Shift", 8000); + xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Control_L+c", 8000); +} + // SYSTEM MODULE // Function taken from the wmlib tool source code diff --git a/native/liblinuxbridge/bridge.h b/native/liblinuxbridge/bridge.h index 6e921bf..2389cf8 100644 --- a/native/liblinuxbridge/bridge.h +++ b/native/liblinuxbridge/bridge.h @@ -92,6 +92,10 @@ extern "C" void trigger_shift_ins_paste(); */ extern "C" void trigger_alt_shift_ins_paste(); +/* + * Trigger copy shortcut ( Pressing CTRL+C ) + */ +extern "C" void trigger_copy(); // SYSTEM MODULE diff --git a/src/bridge/linux.rs b/src/bridge/linux.rs index 90edfc6..967aa9d 100644 --- a/src/bridge/linux.rs +++ b/src/bridge/linux.rs @@ -44,4 +44,5 @@ extern { pub fn trigger_terminal_paste(); pub fn trigger_shift_ins_paste(); pub fn trigger_alt_shift_ins_paste(); + pub fn trigger_copy(); } \ No newline at end of file diff --git a/src/config/mod.rs b/src/config/mod.rs index 32bfb93..166b6bc 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -53,10 +53,12 @@ fn default_use_system_agent() -> bool { true } fn default_config_caching_interval() -> i32 { 800 } fn default_word_separators() -> Vec { vec![' ', ',', '.', '\r', '\n', 22u8 as char] } fn default_toggle_interval() -> u32 { 230 } +fn default_toggle_key() -> KeyModifier { KeyModifier::ALT } fn default_preserve_clipboard() -> bool {false} fn default_passive_match_regex() -> String{ "(?P:\\p{L}+)(/(?P.*)/)?".to_owned() } fn default_passive_arg_delimiter() -> char { '/' } fn default_passive_arg_escape() -> char { '\\' } +fn default_passive_key() -> KeyModifier { KeyModifier::OFF } fn default_backspace_limit() -> i32 { 3 } fn default_exclude_default_matches() -> bool {false} fn default_matches() -> Vec { Vec::new() } @@ -96,7 +98,7 @@ pub struct Configs { #[serde(default = "default_word_separators")] pub word_separators: Vec, // TODO: add parsing test - #[serde(default)] + #[serde(default = "default_toggle_key")] pub toggle_key: KeyModifier, #[serde(default = "default_toggle_interval")] @@ -114,6 +116,9 @@ pub struct Configs { #[serde(default = "default_passive_arg_escape")] pub passive_arg_escape: char, + #[serde(default = "default_passive_key")] + pub passive_key: KeyModifier, + #[serde(default)] pub paste_shortcut: PasteShortcut, @@ -156,7 +161,7 @@ impl Configs { validate_field!(result, self.config_caching_interval, default_config_caching_interval()); validate_field!(result, self.log_level, default_log_level()); - validate_field!(result, self.toggle_key, KeyModifier::default()); + validate_field!(result, self.toggle_key, default_toggle_key()); validate_field!(result, self.toggle_interval, default_toggle_interval()); validate_field!(result, self.backspace_limit, default_backspace_limit()); validate_field!(result, self.ipc_server_port, default_ipc_server_port()); @@ -165,6 +170,7 @@ impl Configs { validate_field!(result, self.passive_match_regex, default_passive_match_regex()); validate_field!(result, self.passive_arg_delimiter, default_passive_arg_delimiter()); validate_field!(result, self.passive_arg_escape, default_passive_arg_escape()); + validate_field!(result, self.passive_key, default_passive_key()); result } diff --git a/src/engine.rs b/src/engine.rs index 7cfd507..7ef9ee8 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -231,6 +231,39 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa self.ui_manager.notify(message); } + + fn on_passive(&self) { + info!("Passive mode activated"); + + // Trigger a copy shortcut to transfer the content of the selection to the clipboard + self.keyboard_manager.trigger_copy(); + + // Sleep for a while, giving time to effectively copy the text + std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding + + // Then get the text from the clipboard and render the match output + let clipboard = self.clipboard_manager.get_clipboard(); + + if let Some(clipboard) = clipboard { + let config = self.config_manager.active_config(); + + let rendered = self.renderer.render_passive(&clipboard, + &config); + + match rendered { + RenderResult::Text(payload) => { + // Paste back the result in the field + self.clipboard_manager.set_clipboard(&payload); + + std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding + self.keyboard_manager.trigger_paste(&config.paste_shortcut); + }, + _ => { + warn!("Cannot expand passive match") + }, + } + } + } } impl <'a, S: KeyboardManager, C: ClipboardManager, diff --git a/src/event/mod.rs b/src/event/mod.rs index bc9828a..dc47691 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -66,12 +66,6 @@ pub enum KeyModifier { OFF, } -impl Default for KeyModifier { - fn default() -> Self { - KeyModifier::ALT - } -} - // Receivers pub trait KeyEventReceiver { diff --git a/src/keyboard/linux.rs b/src/keyboard/linux.rs index 538c375..78e0961 100644 --- a/src/keyboard/linux.rs +++ b/src/keyboard/linux.rs @@ -81,4 +81,10 @@ impl super::KeyboardManager for LinuxKeyboardManager { left_arrow(count); } } + + fn trigger_copy(&self) { + unsafe { + trigger_copy(); + } + } } \ No newline at end of file diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index d8f2611..66ebe77 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -34,6 +34,7 @@ pub trait KeyboardManager { fn trigger_paste(&self, shortcut: &PasteShortcut); fn delete_string(&self, count: i32); fn move_cursor_left(&self, count: i32); + fn trigger_copy(&self); } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/src/matcher/mod.rs b/src/matcher/mod.rs index c1ac263..a8f0ffa 100644 --- a/src/matcher/mod.rs +++ b/src/matcher/mod.rs @@ -181,6 +181,7 @@ pub enum TriggerEntry { pub trait MatchReceiver { fn on_match(&self, m: &Match, trailing_separator: Option); fn on_enable_update(&self, status: bool); + fn on_passive(&self); } pub trait Matcher : KeyEventReceiver { diff --git a/src/matcher/scrolling.rs b/src/matcher/scrolling.rs index 2d59b19..9c70fcd 100644 --- a/src/matcher/scrolling.rs +++ b/src/matcher/scrolling.rs @@ -18,7 +18,7 @@ */ use crate::matcher::{Match, MatchReceiver, TriggerEntry}; -use std::cell::RefCell; +use std::cell::{RefCell, Ref}; use crate::event::{KeyModifier, ActionEventReceiver, ActionType}; use crate::config::ConfigManager; use crate::event::KeyModifier::BACKSPACE; @@ -30,6 +30,7 @@ pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> { receiver: &'a R, current_set_queue: RefCell>>>, toggle_press_time: RefCell, + passive_press_time: RefCell, is_enabled: RefCell, was_previous_char_word_separator: RefCell, } @@ -45,12 +46,14 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> { pub fn new(config_manager: &'a M, receiver: &'a R) -> ScrollingMatcher<'a, R, M> { let current_set_queue = RefCell::new(VecDeque::new()); let toggle_press_time = RefCell::new(SystemTime::now()); + let passive_press_time = RefCell::new(SystemTime::now()); ScrollingMatcher{ config_manager, receiver, current_set_queue, toggle_press_time, + passive_press_time, is_enabled: RefCell::new(true), was_previous_char_word_separator: RefCell::new(true), } @@ -193,22 +196,25 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa fn handle_modifier(&self, m: KeyModifier) { let config = self.config_manager.default_config(); + // TODO: at the moment, activating the passive key triggers the toggle key + // study a mechanism to avoid this problem + if m == config.toggle_key { - if m == KeyModifier::OFF { return } - let mut toggle_press_time = self.toggle_press_time.borrow_mut(); - if let Ok(elapsed) = toggle_press_time.elapsed() { - if elapsed.as_millis() < u128::from(config.toggle_interval) { - self.toggle(); + check_interval(&self.toggle_press_time, + u128::from(config.toggle_interval), || { + self.toggle(); - let is_enabled = self.is_enabled.borrow(); + let is_enabled = self.is_enabled.borrow(); - if !*is_enabled { - self.current_set_queue.borrow_mut().clear(); - } + if !*is_enabled { + self.current_set_queue.borrow_mut().clear(); } - } - - (*toggle_press_time) = SystemTime::now(); + }); + }else if m == config.passive_key { + check_interval(&self.passive_press_time, + u128::from(config.toggle_interval), || { + self.receiver.on_passive(); + }); } // Backspace handling, basically "rewinding history" @@ -234,4 +240,15 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ActionEventReceiver for Scroll _ => {} } } +} + +fn check_interval(state_var: &RefCell, interval: u128, elapsed_callback: F) where F:Fn() { + let mut press_time = state_var.borrow_mut(); + if let Ok(elapsed) = press_time.elapsed() { + if elapsed.as_millis() < interval { + elapsed_callback(); + } + } + + (*press_time) = SystemTime::now(); } \ No newline at end of file