Implement specific key modifiers on Windows. See #117

This commit is contained in:
Federico Terzi 2020-03-10 17:22:50 +01:00
parent b7a6d09d23
commit f11d130c45
6 changed files with 170 additions and 14 deletions

View File

@ -33,6 +33,7 @@
#pragma comment( lib, "gdiplus.lib" )
#include <gdiplus.h>
#include <Windows.h>
// 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<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{
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);
}
}

View File

@ -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;
/*

View File

@ -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);

View File

@ -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<KeyModifier> = match key_code {
0x5B | 0x5C => Some(META),
0x10 => Some(SHIFT),
0x12 => Some(ALT),
0x11 => Some(CTRL),
0x08 => Some(BACKSPACE),
let modifier: Option<KeyModifier> = 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,
};

View File

@ -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
@ -76,3 +141,64 @@ 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));
}
}

View File

@ -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();