Implement specific key modifiers on Windows. See #117
This commit is contained in:
		
							parent
							
								
									b7a6d09d23
								
							
						
					
					
						commit
						f11d130c45
					
				|  | @ -33,6 +33,7 @@ | ||||||
| 
 | 
 | ||||||
| #pragma comment( lib, "gdiplus.lib" ) | #pragma comment( lib, "gdiplus.lib" ) | ||||||
| #include <gdiplus.h> | #include <gdiplus.h> | ||||||
|  | #include <Windows.h> | ||||||
| 
 | 
 | ||||||
| // How many milliseconds must pass between keystrokes to refresh the keyboard layout
 | // How many milliseconds must pass between keystrokes to refresh the keyboard layout
 | ||||||
| const long refreshKeyboardLayoutInterval = 2000; | 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
 |                     // 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.
 |                     // 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) { |                     if (result >= 1 && raw->data.keyboard.VKey != VK_BACK) { | ||||||
|                         keypress_callback(manager_instance, reinterpret_cast<uint16_t*>(buffer.data()), buffer.size(), 0, raw->data.keyboard.VKey, is_key_down); |                         keypress_callback(manager_instance, reinterpret_cast<uint16_t*>(buffer.data()), buffer.size(), 0, raw->data.keyboard.VKey, 0, is_key_down); | ||||||
|                     }else{ |                     }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
 |             }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) { |                 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;
 |                     //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); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -35,11 +35,14 @@ extern void * manager_instance; | ||||||
|  */ |  */ | ||||||
| extern "C" int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_path); | 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, |  * Called when a new keypress is made, the first argument is an int array, | ||||||
|  * while the second is the size of the 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; | extern KeypressCallback keypress_callback; | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  |  | ||||||
|  | @ -51,7 +51,7 @@ extern { | ||||||
| 
 | 
 | ||||||
|     // KEYBOARD
 |     // KEYBOARD
 | ||||||
|     pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u16, |     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 eventloop(); | ||||||
|     pub fn send_string(string: *const u16); |     pub fn send_string(string: *const u16); | ||||||
|  |  | ||||||
|  | @ -108,7 +108,7 @@ impl super::Context for WindowsContext { | ||||||
| // Native bridge code
 | // Native bridge code
 | ||||||
| 
 | 
 | ||||||
| extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32, | 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 { |     unsafe { | ||||||
|         let _self = _self as *mut WindowsContext; |         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
 |         }else{  // KEY UP event
 | ||||||
|             if event_type == 1 {  // Modifier event
 |             if event_type == 1 {  // Modifier event
 | ||||||
|                 let modifier: Option<KeyModifier> = match key_code { |                 let modifier: Option<KeyModifier> = match (key_code, variant) { | ||||||
|                     0x5B | 0x5C => Some(META), |                     (0x5B, _) => Some(LEFT_META), | ||||||
|                     0x10 => Some(SHIFT), |                     (0x5C, _) => Some(RIGHT_META), | ||||||
|                     0x12 => Some(ALT), |                     (0x10, 1) => Some(LEFT_SHIFT), | ||||||
|                     0x11 => Some(CTRL), |                     (0x10, 2) => Some(RIGHT_SHIFT), | ||||||
|                     0x08  => Some(BACKSPACE), |                     (0x12, 1) => Some(LEFT_ALT), | ||||||
|  |                     (0x12, 2) => Some(RIGHT_ALT), | ||||||
|  |                     (0x11, 1) => Some(LEFT_CTRL), | ||||||
|  |                     (0x11, 2) => Some(RIGHT_CTRL), | ||||||
|  |                     (0x08, _)  => Some(BACKSPACE), | ||||||
|                     _ => None, |                     _ => None, | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										126
									
								
								src/event/mod.rs
									
									
									
									
									
								
							
							
						
						
									
										126
									
								
								src/event/mod.rs
									
									
									
									
									
								
							|  | @ -57,6 +57,7 @@ pub enum KeyEvent { | ||||||
|     Other |     Other | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[warn(non_camel_case_types)] | ||||||
| #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | ||||||
| pub enum KeyModifier { | pub enum KeyModifier { | ||||||
|     CTRL, |     CTRL, | ||||||
|  | @ -65,6 +66,70 @@ pub enum KeyModifier { | ||||||
|     META, |     META, | ||||||
|     BACKSPACE, |     BACKSPACE, | ||||||
|     OFF, |     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
 | // Receivers
 | ||||||
|  | @ -75,4 +140,65 @@ pub trait KeyEventReceiver { | ||||||
| 
 | 
 | ||||||
| pub trait ActionEventReceiver { | pub trait ActionEventReceiver { | ||||||
|     fn on_action_event(&self, e: ActionType); |     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)); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | @ -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
 |         // TODO: at the moment, activating the passive key triggers the toggle key
 | ||||||
|         // study a mechanism to avoid this problem
 |         // 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, |             check_interval(&self.toggle_press_time, | ||||||
|                            u128::from(config.toggle_interval), || { |                            u128::from(config.toggle_interval), || { | ||||||
|                 self.toggle(); |                 self.toggle(); | ||||||
|  | @ -223,7 +223,7 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa | ||||||
|                     self.current_set_queue.borrow_mut().clear(); |                     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, |             check_interval(&self.passive_press_time, | ||||||
|                            u128::from(config.toggle_interval), || { |                            u128::from(config.toggle_interval), || { | ||||||
|                 self.receiver.on_passive(); |                 self.receiver.on_passive(); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user