diff --git a/native/libwinbridge/bridge.cpp b/native/libwinbridge/bridge.cpp index 7d6d3df..3a771b9 100644 --- a/native/libwinbridge/bridge.cpp +++ b/native/libwinbridge/bridge.cpp @@ -33,6 +33,7 @@ #pragma comment( lib, "gdiplus.lib" ) #include +#include // How many milliseconds must pass between keystrokes to refresh the keyboard layout const long refreshKeyboardLayoutInterval = 2000; @@ -258,16 +259,38 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR // We need to call the callback in two different ways based on the type of key // The only modifier we use that has a result > 0 is the BACKSPACE, so we have to consider it. if (result >= 1 && raw->data.keyboard.VKey != VK_BACK) { - keypress_callback(manager_instance, reinterpret_cast(buffer.data()), buffer.size(), 0, raw->data.keyboard.VKey, is_key_down); + keypress_callback(manager_instance, reinterpret_cast(buffer.data()), buffer.size(), 0, raw->data.keyboard.VKey, 0, is_key_down); }else{ - keypress_callback(manager_instance, nullptr, 0, 1, raw->data.keyboard.VKey, is_key_down); + //std::cout << raw->data.keyboard.MakeCode << " " << raw->data.keyboard.Flags << std::endl; + int variant = 0; + if (raw->data.keyboard.VKey == VK_SHIFT) { + // To discriminate between the left and right shift, we need to employ a workaround. + // See: https://stackoverflow.com/questions/5920301/distinguish-between-left-and-right-shift-keys-using-rawinput + if (raw->data.keyboard.MakeCode == 42) { // Left shift + variant = LEFT_VARIANT; + }if (raw->data.keyboard.MakeCode == 54) { // Right shift + variant = RIGHT_VARIANT; + } + }else{ + // Also the ALT and CTRL key are special cases + // Check out the previous Stackoverflow question for more information + if (raw->data.keyboard.VKey == VK_CONTROL || raw->data.keyboard.VKey == VK_MENU) { + if ((raw->data.keyboard.Flags & RI_KEY_E0) != 0) { + variant = RIGHT_VARIANT; + }else{ + variant = LEFT_VARIANT; + } + } + } + + keypress_callback(manager_instance, nullptr, 0, 1, raw->data.keyboard.VKey, variant, is_key_down); } } }else if (raw->header.dwType == RIM_TYPEMOUSE) // Mouse input, registered as "other" events. Needed to improve the reliability of word matches { if ((raw->data.mouse.usButtonFlags & (RI_MOUSE_LEFT_BUTTON_DOWN | RI_MOUSE_RIGHT_BUTTON_DOWN | RI_MOUSE_MIDDLE_BUTTON_DOWN)) != 0) { //std::cout << "mouse down" << std::endl; - keypress_callback(manager_instance, nullptr, 0, 2, raw->data.mouse.usButtonFlags, 0); + keypress_callback(manager_instance, nullptr, 0, 2, raw->data.mouse.usButtonFlags, 0, 0); } } diff --git a/native/libwinbridge/bridge.h b/native/libwinbridge/bridge.h index 40ed1e6..f8084ad 100644 --- a/native/libwinbridge/bridge.h +++ b/native/libwinbridge/bridge.h @@ -35,11 +35,14 @@ extern void * manager_instance; */ extern "C" int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_path); +#define LEFT_VARIANT 1 +#define RIGHT_VARIANT 2 + /* * Called when a new keypress is made, the first argument is an int array, * while the second is the size of the array. */ -typedef void (*KeypressCallback)(void * self, uint16_t *buffer, int32_t len, int32_t event_type, int32_t key_code, int32_t is_key_down); +typedef void (*KeypressCallback)(void * self, uint16_t *buffer, int32_t len, int32_t event_type, int32_t key_code, int32_t is_key_down, int32_t variant); extern KeypressCallback keypress_callback; /* diff --git a/src/bridge/windows.rs b/src/bridge/windows.rs index f7a4459..f337638 100644 --- a/src/bridge/windows.rs +++ b/src/bridge/windows.rs @@ -51,7 +51,7 @@ extern { // KEYBOARD pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u16, - i32, i32, i32, i32)); + i32, i32, i32, i32, i32)); pub fn eventloop(); pub fn send_string(string: *const u16); diff --git a/src/context/windows.rs b/src/context/windows.rs index ab15201..c797d1f 100644 --- a/src/context/windows.rs +++ b/src/context/windows.rs @@ -108,7 +108,7 @@ impl super::Context for WindowsContext { // Native bridge code extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32, - event_type: i32, key_code: i32, is_key_down: i32) { + event_type: i32, key_code: i32, variant: i32, is_key_down: i32) { unsafe { let _self = _self as *mut WindowsContext; @@ -145,12 +145,16 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32 } }else{ // KEY UP event if event_type == 1 { // Modifier event - let modifier: Option = match key_code { - 0x5B | 0x5C => Some(META), - 0x10 => Some(SHIFT), - 0x12 => Some(ALT), - 0x11 => Some(CTRL), - 0x08 => Some(BACKSPACE), + let modifier: Option = match (key_code, variant) { + (0x5B, _) => Some(LEFT_META), + (0x5C, _) => Some(RIGHT_META), + (0x10, 1) => Some(LEFT_SHIFT), + (0x10, 2) => Some(RIGHT_SHIFT), + (0x12, 1) => Some(LEFT_ALT), + (0x12, 2) => Some(RIGHT_ALT), + (0x11, 1) => Some(LEFT_CTRL), + (0x11, 2) => Some(RIGHT_CTRL), + (0x08, _) => Some(BACKSPACE), _ => None, }; diff --git a/src/event/mod.rs b/src/event/mod.rs index 8e81324..7d63a7f 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -57,6 +57,7 @@ pub enum KeyEvent { Other } +#[warn(non_camel_case_types)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum KeyModifier { CTRL, @@ -65,6 +66,70 @@ pub enum KeyModifier { META, BACKSPACE, OFF, + + // These are specific variants of the ones above. See issue: #117 + // https://github.com/federico-terzi/espanso/issues/117 + LEFT_CTRL, + RIGHT_CTRL, + LEFT_ALT, + RIGHT_ALT, + LEFT_META, + RIGHT_META, + LEFT_SHIFT, + RIGHT_SHIFT, +} + +impl KeyModifier { + /// This function is used to compare KeyModifiers, considering the relations between + /// the generic modifier and the specific left/right variant + /// For example, CTRL will match with CTRL, LEFT_CTRL and RIGHT_CTRL; + /// but LEFT_CTRL will only match will LEFT_CTRL + pub fn shallow_equals(current: &KeyModifier, config: &KeyModifier) -> bool { + use KeyModifier::*; + + match config { + KeyModifier::CTRL => { + current == &LEFT_CTRL || current == &RIGHT_CTRL || current == &CTRL + }, + KeyModifier::SHIFT => { + current == &LEFT_SHIFT || current == &RIGHT_SHIFT || current == &SHIFT + }, + KeyModifier::ALT => { + current == &LEFT_ALT || current == &RIGHT_ALT || current == &ALT + }, + KeyModifier::META => { + current == &LEFT_META || current == &RIGHT_META || current == &META + }, + KeyModifier::BACKSPACE => { + current == &BACKSPACE + }, + KeyModifier::LEFT_CTRL => { + current == &LEFT_CTRL + }, + KeyModifier::RIGHT_CTRL => { + current == &RIGHT_CTRL + }, + KeyModifier::LEFT_ALT => { + current == &LEFT_ALT + }, + KeyModifier::RIGHT_ALT => { + current == &RIGHT_ALT + }, + KeyModifier::LEFT_META => { + current == &LEFT_META + }, + KeyModifier::RIGHT_META => { + current == &RIGHT_META + }, + KeyModifier::LEFT_SHIFT => { + current == &LEFT_SHIFT + }, + KeyModifier::RIGHT_SHIFT => { + current == &RIGHT_SHIFT + }, + _ => {false}, + } + } } // Receivers @@ -75,4 +140,65 @@ pub trait KeyEventReceiver { pub trait ActionEventReceiver { fn on_action_event(&self, e: ActionType); +} + +// TESTS + +#[cfg(test)] +mod tests { + use super::*; + use super::KeyModifier::*; + + #[test] + fn test_shallow_equals_ctrl() { + assert!(KeyModifier::shallow_equals(&CTRL, &CTRL)); + assert!(KeyModifier::shallow_equals(&LEFT_CTRL, &CTRL)); + assert!(KeyModifier::shallow_equals(&RIGHT_CTRL, &CTRL)); + + assert!(!KeyModifier::shallow_equals(&CTRL, &LEFT_CTRL)); + assert!(!KeyModifier::shallow_equals(&CTRL, &RIGHT_CTRL)); + } + + #[test] + fn test_shallow_equals_shift() { + assert!(KeyModifier::shallow_equals(&SHIFT, &SHIFT)); + assert!(KeyModifier::shallow_equals(&LEFT_SHIFT, &SHIFT)); + assert!(KeyModifier::shallow_equals(&RIGHT_SHIFT, &SHIFT)); + + assert!(!KeyModifier::shallow_equals(&SHIFT, &LEFT_SHIFT)); + assert!(!KeyModifier::shallow_equals(&SHIFT, &RIGHT_SHIFT)); + } + + #[test] + fn test_shallow_equals_alt() { + assert!(KeyModifier::shallow_equals(&ALT, &ALT)); + assert!(KeyModifier::shallow_equals(&LEFT_ALT, &ALT)); + assert!(KeyModifier::shallow_equals(&RIGHT_ALT, &ALT)); + + assert!(!KeyModifier::shallow_equals(&ALT, &LEFT_ALT)); + assert!(!KeyModifier::shallow_equals(&ALT, &RIGHT_ALT)); + } + + #[test] + fn test_shallow_equals_meta() { + assert!(KeyModifier::shallow_equals(&META, &META)); + assert!(KeyModifier::shallow_equals(&LEFT_META, &META)); + assert!(KeyModifier::shallow_equals(&RIGHT_META, &META)); + + assert!(!KeyModifier::shallow_equals(&META, &LEFT_META)); + assert!(!KeyModifier::shallow_equals(&META, &RIGHT_META)); + } + + #[test] + fn test_shallow_equals_backspace() { + assert!(KeyModifier::shallow_equals(&BACKSPACE, &BACKSPACE)); + } + + #[test] + fn test_shallow_equals_off() { + assert!(!KeyModifier::shallow_equals(&OFF, &CTRL)); + assert!(!KeyModifier::shallow_equals(&OFF, &ALT)); + assert!(!KeyModifier::shallow_equals(&OFF, &META)); + assert!(!KeyModifier::shallow_equals(&OFF, &SHIFT)); + } } \ No newline at end of file diff --git a/src/matcher/scrolling.rs b/src/matcher/scrolling.rs index 98df210..b1a24a8 100644 --- a/src/matcher/scrolling.rs +++ b/src/matcher/scrolling.rs @@ -212,7 +212,7 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa // 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 KeyModifier::shallow_equals(&m, &config.toggle_key) { check_interval(&self.toggle_press_time, u128::from(config.toggle_interval), || { self.toggle(); @@ -223,7 +223,7 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa self.current_set_queue.borrow_mut().clear(); } }); - }else if m == config.passive_key { + }else if KeyModifier::shallow_equals(&m, &config.passive_key) { check_interval(&self.passive_press_time, u128::from(config.toggle_interval), || { self.receiver.on_passive();