From 9bbdb8d95f78be7def6e906797d4ddbb9292cce1 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Tue, 22 Oct 2019 23:03:58 +0200 Subject: [PATCH 1/4] First draft of Cursor Position hint on Windows --- native/libwinbridge/bridge.cpp | 40 ++++++++-------- native/libwinbridge/bridge.h | 5 ++ src/bridge/windows.rs | 1 + src/engine.rs | 7 +++ src/keyboard/mod.rs | 1 + src/keyboard/windows.rs | 7 +++ src/matcher/mod.rs | 83 +++++++++++++++++++++++++++++++++- 7 files changed, 125 insertions(+), 19 deletions(-) diff --git a/native/libwinbridge/bridge.cpp b/native/libwinbridge/bridge.cpp index f7fa9e7..309395c 100644 --- a/native/libwinbridge/bridge.cpp +++ b/native/libwinbridge/bridge.cpp @@ -453,24 +453,7 @@ void send_string(const wchar_t * string) { * Send the backspace keypress, *count* times. */ void delete_string(int32_t count) { - std::vector vec; - - for (int i = 0; i < count; i++) { - INPUT input = { 0 }; - - input.type = INPUT_KEYBOARD; - input.ki.wScan = 0; - input.ki.time = 0; - input.ki.dwExtraInfo = 0; - input.ki.wVk = VK_BACK; - input.ki.dwFlags = 0; // 0 for key press - vec.push_back(input); - - input.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release - vec.push_back(input); - } - - SendInput(vec.size(), vec.data(), sizeof(INPUT)); + send_multi_vkey(VK_BACK, count); } void send_vkey(int32_t vk) { @@ -492,6 +475,27 @@ void send_vkey(int32_t vk) { SendInput(vec.size(), vec.data(), sizeof(INPUT)); } +void send_multi_vkey(int32_t vk, int32_t count) { + std::vector vec; + + for (int i = 0; i < count; i++) { + INPUT input = { 0 }; + + input.type = INPUT_KEYBOARD; + input.ki.wScan = 0; + input.ki.time = 0; + input.ki.dwExtraInfo = 0; + input.ki.wVk = vk; + input.ki.dwFlags = 0; // 0 for key press + vec.push_back(input); + + input.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release + vec.push_back(input); + } + + SendInput(vec.size(), vec.data(), sizeof(INPUT)); +} + void trigger_paste() { std::vector vec; diff --git a/native/libwinbridge/bridge.h b/native/libwinbridge/bridge.h index fc0a80c..85d86ad 100644 --- a/native/libwinbridge/bridge.h +++ b/native/libwinbridge/bridge.h @@ -64,6 +64,11 @@ extern "C" void send_string(const wchar_t * string); */ extern "C" void send_vkey(int32_t vk); +/* + * Send the given Virtual Key press multiple times + */ +extern "C" void send_multi_vkey(int32_t vk, int32_t count); + /* * Send the backspace keypress, *count* times. */ diff --git a/src/bridge/windows.rs b/src/bridge/windows.rs index 349230c..db9833e 100644 --- a/src/bridge/windows.rs +++ b/src/bridge/windows.rs @@ -55,6 +55,7 @@ extern { pub fn eventloop(); pub fn send_string(string: *const u16); pub fn send_vkey(vk: i32); + pub fn send_multi_vkey(vk: i32, count: i32); pub fn delete_string(count: i32); pub fn trigger_paste(); } \ No newline at end of file diff --git a/src/engine.rs b/src/engine.rs index 4376c52..458fc7e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -185,6 +185,13 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa self.keyboard_manager.trigger_paste(); }, } + + // Cursor Hint + if let Some(cursor_rewind) = m._cursor_rewind { + // Simulate left arrow key presses to bring the cursor into the desired position + + self.keyboard_manager.move_cursor_left(cursor_rewind); + } } fn on_enable_update(&self, status: bool) { diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index c48134c..5a9cc5b 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -31,6 +31,7 @@ pub trait KeyboardManager { fn send_enter(&self); fn trigger_paste(&self); fn delete_string(&self, count: i32); + fn move_cursor_left(&self, count: i32); } // WINDOWS IMPLEMENTATION diff --git a/src/keyboard/windows.rs b/src/keyboard/windows.rs index df3564d..8193006 100644 --- a/src/keyboard/windows.rs +++ b/src/keyboard/windows.rs @@ -55,4 +55,11 @@ impl super::KeyboardManager for WindowsKeyboardManager { delete_string(count) } } + + fn move_cursor_left(&self, count: i32) { + unsafe { + // Send the left arrow key multiple times + send_multi_vkey(0x25, count) + } + } } \ No newline at end of file diff --git a/src/matcher/mod.rs b/src/matcher/mod.rs index 772f732..5eb1240 100644 --- a/src/matcher/mod.rs +++ b/src/matcher/mod.rs @@ -38,6 +38,11 @@ pub struct Match { // Automatically calculated from the trigger, used by the matcher to check for correspondences. #[serde(skip_serializing)] pub _trigger_sequence: Vec, + + // If a cursor position hint is present, this value contains the amount of "left" moves necessary + // to arrive to the target point + #[serde(skip_serializing)] + pub _cursor_rewind: Option, } impl <'de> serde::Deserialize<'de> for Match { @@ -55,6 +60,33 @@ impl<'a> From<&'a AutoMatch> for Match{ static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(\\w+)\\s*\\}\\}").unwrap(); } + // TODO: may need to replace windows newline (\r\n) with newline only (\n) + + let mut new_replace = other.replace.clone(); + + // Check if the replace result contains a Cursor Hint + let cursor_rewind = if other.replace.contains("{{|}}") { + let index = other.replace.find("{{|}}"); + if let Some(index) = index { + // Convert the byte index to a char index + let char_str = &other.replace[0..index]; + let char_index = char_str.chars().count(); + let total_size = other.replace.chars().count(); + + // Remove the {{|}} placeholder + new_replace = other.replace.replace("{{|}}", ""); + + // Calculate the amount of rewind moves needed (LEFT ARROW). + // Subtract also 5, equal to the number of chars of the placeholder "{{|}}" + let moves = (total_size - char_index - 5) as i32; + Some(moves) + }else{ + None + } + }else{ + None + }; + // Check if the match contains variables let has_vars = VAR_REGEX.is_match(&other.replace); @@ -70,11 +102,12 @@ impl<'a> From<&'a AutoMatch> for Match{ Self { trigger: other.trigger.clone(), - replace: other.replace.clone(), + replace: new_replace, vars: other.vars.clone(), word: other.word.clone(), _has_vars: has_vars, _trigger_sequence: trigger_sequence, + _cursor_rewind: cursor_rewind, } } } @@ -208,4 +241,52 @@ mod tests { assert_eq!(_match._trigger_sequence[3], TriggerEntry::Char('t')); assert_eq!(_match._trigger_sequence[4], TriggerEntry::WordSeparator); } + + #[test] + fn test_match_cursor_hint_not_present() { + let match_str = r###" + trigger: "test" + replace: "This is a test" + "###; + + let _match : Match = serde_yaml::from_str(match_str).unwrap(); + + assert!(_match._cursor_rewind.is_none()); + } + + #[test] + fn test_match_cursor_hint_should_be_removed() { + let match_str = r###" + trigger: "test" + replace: "Testing the {{|}} cursor position" + "###; + + let _match : Match = serde_yaml::from_str(match_str).unwrap(); + + assert_eq!(_match.replace, "Testing the cursor position"); + } + + #[test] + fn test_match_cursor_rewind_single_line() { + let match_str = r###" + trigger: "test" + replace: "Testing the {{|}} cursor position" + "###; + + let _match : Match = serde_yaml::from_str(match_str).unwrap(); + + assert_eq!(_match._cursor_rewind.unwrap(), 16); + } + + #[test] + fn test_match_cursor_rewind_multiple_line() { + let match_str = r###" + trigger: "test" + replace: "Testing the \n{{|}}\n cursor position" + "###; + + let _match : Match = serde_yaml::from_str(match_str).unwrap(); + + assert_eq!(_match._cursor_rewind.unwrap(), 17); + } } \ No newline at end of file From 4f9be699acd19f8613b2c6594cefb7559ea34af1 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Wed, 23 Oct 2019 19:13:21 +0200 Subject: [PATCH 2/4] Add cursor position implementation for MacOS --- native/libmacbridge/bridge.h | 5 +++++ native/libmacbridge/bridge.mm | 42 +++++++++++++++++++---------------- src/bridge/macos.rs | 1 + src/keyboard/macos.rs | 7 ++++++ 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/native/libmacbridge/bridge.h b/native/libmacbridge/bridge.h index 8f414d9..b3bc3d1 100644 --- a/native/libmacbridge/bridge.h +++ b/native/libmacbridge/bridge.h @@ -60,6 +60,11 @@ void send_string(const char * string); */ void send_vkey(int32_t vk); +/* + * Send the Virtual Key press multiple times + */ +void send_multi_vkey(int32_t vk, int32_t count); + /* * Send the backspace keypress, *count* times. */ diff --git a/native/libmacbridge/bridge.mm b/native/libmacbridge/bridge.mm index 361bfa3..d55ede4 100644 --- a/native/libmacbridge/bridge.mm +++ b/native/libmacbridge/bridge.mm @@ -101,23 +101,7 @@ void send_string(const char * string) { } void delete_string(int32_t count) { - dispatch_async(dispatch_get_main_queue(), ^(void) { - for (int i = 0; i < count; i++) { - CGEventRef keydown; - keydown = CGEventCreateKeyboardEvent(NULL, 0x33, true); - CGEventPost(kCGHIDEventTap, keydown); - CFRelease(keydown); - - usleep(2000); - - CGEventRef keyup; - keyup = CGEventCreateKeyboardEvent(NULL, 0x33, false); - CGEventPost(kCGHIDEventTap, keyup); - CFRelease(keyup); - - usleep(2000); - } - }); + send_multi_vkey(0x33, count); } void send_vkey(int32_t vk) { @@ -127,14 +111,34 @@ void send_vkey(int32_t vk) { CGEventPost(kCGHIDEventTap, keydown); CFRelease(keydown); - usleep(2000); + usleep(500); CGEventRef keyup; keyup = CGEventCreateKeyboardEvent(NULL, vk, false); CGEventPost(kCGHIDEventTap, keyup); CFRelease(keyup); - usleep(2000); + usleep(500); + }); +} + +void send_multi_vkey(int32_t vk, int32_t count) { + dispatch_async(dispatch_get_main_queue(), ^(void) { + for (int i = 0; i < count; i++) { + CGEventRef keydown; + keydown = CGEventCreateKeyboardEvent(NULL, vk, true); + CGEventPost(kCGHIDEventTap, keydown); + CFRelease(keydown); + + usleep(500); + + CGEventRef keyup; + keyup = CGEventCreateKeyboardEvent(NULL, vk, false); + CGEventPost(kCGHIDEventTap, keyup); + CFRelease(keyup); + + usleep(500); + } }); } diff --git a/src/bridge/macos.rs b/src/bridge/macos.rs index 830e994..b229928 100644 --- a/src/bridge/macos.rs +++ b/src/bridge/macos.rs @@ -54,6 +54,7 @@ extern { pub fn send_string(string: *const c_char); pub fn send_vkey(vk: i32); + pub fn send_multi_vkey(vk: i32, count: i32); pub fn delete_string(count: i32); pub fn trigger_paste(); } \ No newline at end of file diff --git a/src/keyboard/macos.rs b/src/keyboard/macos.rs index 7381dc1..724845a 100644 --- a/src/keyboard/macos.rs +++ b/src/keyboard/macos.rs @@ -48,4 +48,11 @@ impl super::KeyboardManager for MacKeyboardManager { fn delete_string(&self, count: i32) { unsafe {delete_string(count)} } + + fn move_cursor_left(&self, count: i32) { + unsafe { + // Simulate the Left arrow count times + send_multi_vkey(0x7B, count); + } + } } \ No newline at end of file From 8afacf4efb4e75d6709664c83b1f45532fa11842 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Thu, 24 Oct 2019 18:48:55 +0200 Subject: [PATCH 3/4] Refactor cursor hint to support dynamic matches --- src/engine.rs | 25 ++++++++++++--- src/matcher/mod.rs | 79 +--------------------------------------------- 2 files changed, 22 insertions(+), 82 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 458fc7e..bc604c1 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -160,6 +160,25 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa // Convert Windows style newlines into unix styles target_string = target_string.replace("\r\n", "\n"); + // Calculate cursor rewind moves if a Cursor Hint is present + let index = target_string.find("$|$"); + let cursor_rewind = if let Some(index) = index { + // Convert the byte index to a char index + let char_str = &target_string[0..index]; + let char_index = char_str.chars().count(); + let total_size = target_string.chars().count(); + + // Remove the $|$ placeholder + target_string = target_string.replace("$|$", ""); + + // Calculate the amount of rewind moves needed (LEFT ARROW). + // Subtract also 3, equal to the number of chars of the placeholder "$|$" + let moves = (total_size - char_index - 3) as i32; + Some(moves) + }else{ + None + }; + match config.backend { BackendType::Inject => { // Send the expected string. On linux, newlines are managed automatically @@ -186,11 +205,9 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa }, } - // Cursor Hint - if let Some(cursor_rewind) = m._cursor_rewind { + if let Some(moves) = cursor_rewind { // Simulate left arrow key presses to bring the cursor into the desired position - - self.keyboard_manager.move_cursor_left(cursor_rewind); + self.keyboard_manager.move_cursor_left(moves); } } diff --git a/src/matcher/mod.rs b/src/matcher/mod.rs index 5eb1240..4667318 100644 --- a/src/matcher/mod.rs +++ b/src/matcher/mod.rs @@ -38,11 +38,6 @@ pub struct Match { // Automatically calculated from the trigger, used by the matcher to check for correspondences. #[serde(skip_serializing)] pub _trigger_sequence: Vec, - - // If a cursor position hint is present, this value contains the amount of "left" moves necessary - // to arrive to the target point - #[serde(skip_serializing)] - pub _cursor_rewind: Option, } impl <'de> serde::Deserialize<'de> for Match { @@ -62,30 +57,7 @@ impl<'a> From<&'a AutoMatch> for Match{ // TODO: may need to replace windows newline (\r\n) with newline only (\n) - let mut new_replace = other.replace.clone(); - - // Check if the replace result contains a Cursor Hint - let cursor_rewind = if other.replace.contains("{{|}}") { - let index = other.replace.find("{{|}}"); - if let Some(index) = index { - // Convert the byte index to a char index - let char_str = &other.replace[0..index]; - let char_index = char_str.chars().count(); - let total_size = other.replace.chars().count(); - - // Remove the {{|}} placeholder - new_replace = other.replace.replace("{{|}}", ""); - - // Calculate the amount of rewind moves needed (LEFT ARROW). - // Subtract also 5, equal to the number of chars of the placeholder "{{|}}" - let moves = (total_size - char_index - 5) as i32; - Some(moves) - }else{ - None - } - }else{ - None - }; + let new_replace = other.replace.clone(); // Check if the match contains variables let has_vars = VAR_REGEX.is_match(&other.replace); @@ -107,7 +79,6 @@ impl<'a> From<&'a AutoMatch> for Match{ word: other.word.clone(), _has_vars: has_vars, _trigger_sequence: trigger_sequence, - _cursor_rewind: cursor_rewind, } } } @@ -241,52 +212,4 @@ mod tests { assert_eq!(_match._trigger_sequence[3], TriggerEntry::Char('t')); assert_eq!(_match._trigger_sequence[4], TriggerEntry::WordSeparator); } - - #[test] - fn test_match_cursor_hint_not_present() { - let match_str = r###" - trigger: "test" - replace: "This is a test" - "###; - - let _match : Match = serde_yaml::from_str(match_str).unwrap(); - - assert!(_match._cursor_rewind.is_none()); - } - - #[test] - fn test_match_cursor_hint_should_be_removed() { - let match_str = r###" - trigger: "test" - replace: "Testing the {{|}} cursor position" - "###; - - let _match : Match = serde_yaml::from_str(match_str).unwrap(); - - assert_eq!(_match.replace, "Testing the cursor position"); - } - - #[test] - fn test_match_cursor_rewind_single_line() { - let match_str = r###" - trigger: "test" - replace: "Testing the {{|}} cursor position" - "###; - - let _match : Match = serde_yaml::from_str(match_str).unwrap(); - - assert_eq!(_match._cursor_rewind.unwrap(), 16); - } - - #[test] - fn test_match_cursor_rewind_multiple_line() { - let match_str = r###" - trigger: "test" - replace: "Testing the \n{{|}}\n cursor position" - "###; - - let _match : Match = serde_yaml::from_str(match_str).unwrap(); - - assert_eq!(_match._cursor_rewind.unwrap(), 17); - } } \ No newline at end of file From b63e2b2592daebd746df924fab48407fb3dcabbb Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Fri, 25 Oct 2019 22:34:31 +0200 Subject: [PATCH 4/4] Add cursor position implementation on Linux --- native/liblinuxbridge/bridge.cpp | 8 +++++++- native/liblinuxbridge/bridge.h | 5 +++++ src/bridge/linux.rs | 1 + src/keyboard/linux.rs | 6 ++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/native/liblinuxbridge/bridge.cpp b/native/liblinuxbridge/bridge.cpp index 55d1c99..750c28f 100644 --- a/native/liblinuxbridge/bridge.cpp +++ b/native/liblinuxbridge/bridge.cpp @@ -278,6 +278,12 @@ void delete_string(int32_t count) { } } +void left_arrow(int32_t count) { + for (int i = 0; i