This commit is contained in:
parent
c7d6d69b72
commit
2745257ce9
|
@ -32,7 +32,7 @@ use log::{error, trace, warn};
|
|||
use anyhow::Result;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::event::{HotKeyEvent, InputEvent, Key, KeyboardEvent, Variant};
|
||||
use crate::event::{HotKeyEvent, InputEvent, Key, KeyboardEvent, Status, Variant};
|
||||
use crate::event::{Key::*, MouseButton, MouseEvent};
|
||||
use crate::{event::Status::*, Source, SourceCallback};
|
||||
use crate::{event::Variant::*, hotkey::HotKey};
|
||||
|
@ -50,6 +50,7 @@ const INPUT_MOUSE_MIDDLE_BUTTON: i32 = 3;
|
|||
|
||||
// Take a look at the native.h header file for an explanation of the fields
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct RawInputEvent {
|
||||
pub event_type: i32,
|
||||
|
||||
|
@ -58,6 +59,12 @@ pub struct RawInputEvent {
|
|||
|
||||
pub key_code: i32,
|
||||
pub status: i32,
|
||||
|
||||
pub is_caps_lock_pressed: i32,
|
||||
pub is_shift_pressed: i32,
|
||||
pub is_control_pressed: i32,
|
||||
pub is_option_pressed: i32,
|
||||
pub is_command_pressed: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
@ -82,15 +89,97 @@ extern "C" {
|
|||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ModifierState {
|
||||
is_ctrl_down: bool,
|
||||
is_shift_down: bool,
|
||||
is_command_down: bool,
|
||||
is_option_down: bool,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref CURRENT_SENDER: Arc<Mutex<Option<Sender<InputEvent>>>> = Arc::new(Mutex::new(None));
|
||||
static ref MODIFIER_STATE: Arc<Mutex<ModifierState>> =
|
||||
Arc::new(Mutex::new(ModifierState::default()));
|
||||
}
|
||||
|
||||
extern "C" fn native_callback(raw_event: RawInputEvent) {
|
||||
let lock = CURRENT_SENDER
|
||||
.lock()
|
||||
.expect("unable to acquire CocoaSource sender lock");
|
||||
|
||||
// Most of the times, when pressing a modifier key (such as Alt, Ctrl, Shift, Cmd),
|
||||
// we get both a Pressed and Released event. This is important to keep Espanso's
|
||||
// internal representation of modifiers in sync.
|
||||
// Unfortunately, there are times when the corresponding "release" event is not sent,
|
||||
// and this causes Espanso to mistakenly think that the modifier is still pressed.
|
||||
// This can happen for various reasons, such as when using external bluetooth keyboards
|
||||
// or certain keyboard shortcuts.
|
||||
// Luckily, most key events include the "modifiers flag" information, that tells us which
|
||||
// modifier keys were currently pressed at that time.
|
||||
// We use this modifier flag information to detect "inconsistent" states to send the corresponding
|
||||
// modifier release events, keeping espanso's state in sync.
|
||||
// For more info, see:
|
||||
// https://github.com/federico-terzi/espanso/issues/825
|
||||
// https://github.com/federico-terzi/espanso/issues/858
|
||||
let mut compensating_events = Vec::new();
|
||||
if raw_event.event_type == INPUT_EVENT_TYPE_KEYBOARD {
|
||||
let (key_code, _) = key_code_to_key(raw_event.key_code);
|
||||
let mut current_mod_state = MODIFIER_STATE
|
||||
.lock()
|
||||
.expect("unable to acquire modifier state in cocoa detector");
|
||||
|
||||
if let Key::Alt = &key_code {
|
||||
current_mod_state.is_option_down = raw_event.status == INPUT_STATUS_PRESSED;
|
||||
} else if let Key::Meta = &key_code {
|
||||
current_mod_state.is_command_down = raw_event.status == INPUT_STATUS_PRESSED;
|
||||
} else if let Key::Shift = &key_code {
|
||||
current_mod_state.is_shift_down = raw_event.status == INPUT_STATUS_PRESSED;
|
||||
} else if let Key::Control = &key_code {
|
||||
current_mod_state.is_ctrl_down = raw_event.status == INPUT_STATUS_PRESSED;
|
||||
} else {
|
||||
if current_mod_state.is_command_down && raw_event.is_command_pressed == 0 {
|
||||
compensating_events.push((Key::Meta, 0x37));
|
||||
current_mod_state.is_command_down = false;
|
||||
}
|
||||
if current_mod_state.is_ctrl_down && raw_event.is_control_pressed == 0 {
|
||||
compensating_events.push((Key::Control, 0x3B));
|
||||
current_mod_state.is_ctrl_down = false;
|
||||
}
|
||||
if current_mod_state.is_shift_down && raw_event.is_shift_pressed == 0 {
|
||||
compensating_events.push((Key::Shift, 0x38));
|
||||
current_mod_state.is_shift_down = false;
|
||||
}
|
||||
if current_mod_state.is_option_down && raw_event.is_option_pressed == 0 {
|
||||
compensating_events.push((Key::Alt, 0x3A));
|
||||
current_mod_state.is_option_down = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !compensating_events.is_empty() {
|
||||
warn!(
|
||||
"detected inconsistent modifier state for keys {:?}, sending compensating events...",
|
||||
compensating_events
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(sender) = lock.as_ref() {
|
||||
for (key, code) in compensating_events {
|
||||
if let Err(error) = sender.send(InputEvent::Keyboard(KeyboardEvent {
|
||||
key,
|
||||
value: None,
|
||||
status: Status::Released,
|
||||
variant: None,
|
||||
code,
|
||||
})) {
|
||||
error!(
|
||||
"Unable to send compensating event to Cocoa Sender: {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let event: Option<InputEvent> = raw_event.into();
|
||||
if let Some(event) = event {
|
||||
if let Err(error) = sender.send(event) {
|
||||
|
@ -386,6 +475,11 @@ mod tests {
|
|||
buffer_len: 0,
|
||||
key_code: 0,
|
||||
status: INPUT_STATUS_PRESSED,
|
||||
is_caps_lock_pressed: 0,
|
||||
is_shift_pressed: 0,
|
||||
is_control_pressed: 0,
|
||||
is_option_pressed: 0,
|
||||
is_command_pressed: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,16 @@ typedef struct {
|
|||
|
||||
// Pressed or Released status
|
||||
int32_t status;
|
||||
|
||||
// Modifier keys status, this is needed to "correct" missing modifier release events.
|
||||
// For more info, see the following issues:
|
||||
// https://github.com/federico-terzi/espanso/issues/825
|
||||
// https://github.com/federico-terzi/espanso/issues/858
|
||||
int32_t is_caps_lock_pressed;
|
||||
int32_t is_shift_pressed;
|
||||
int32_t is_control_pressed;
|
||||
int32_t is_option_pressed;
|
||||
int32_t is_command_pressed;
|
||||
} InputEvent;
|
||||
|
||||
typedef void (*EventCallback)(InputEvent data);
|
||||
|
|
|
@ -76,6 +76,19 @@ void * detect_initialize(EventCallback callback, InitializeOptions options) {
|
|||
strncpy(inputEvent.buffer, chars, 23);
|
||||
inputEvent.buffer_len = event.characters.length;
|
||||
|
||||
// We also send the modifier key status to "correct" missing modifier release events
|
||||
if (([event modifierFlags] & NSEventModifierFlagShift) != 0) {
|
||||
inputEvent.is_shift_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagControl) != 0) {
|
||||
inputEvent.is_control_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagCapsLock) != 0) {
|
||||
inputEvent.is_caps_lock_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagOption) != 0) {
|
||||
inputEvent.is_option_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagCommand) != 0) {
|
||||
inputEvent.is_command_pressed = 1;
|
||||
}
|
||||
|
||||
callback(inputEvent);
|
||||
}else if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeOtherMouseDown ||
|
||||
event.type == NSEventTypeLeftMouseUp || event.type == NSEventTypeRightMouseUp || event.type == NSEventTypeOtherMouseUp) {
|
||||
|
@ -106,6 +119,20 @@ void * detect_initialize(EventCallback callback, InitializeOptions options) {
|
|||
} else if (event.keyCode == kVK_Option || event.keyCode == kVK_RightOption) {
|
||||
inputEvent.status = (([event modifierFlags] & NSEventModifierFlagOption) == 0) ? INPUT_STATUS_RELEASED : INPUT_STATUS_PRESSED;
|
||||
}
|
||||
|
||||
// We also send the modifier key status to "correct" missing modifier release events
|
||||
if (([event modifierFlags] & NSEventModifierFlagShift) != 0) {
|
||||
inputEvent.is_shift_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagControl) != 0) {
|
||||
inputEvent.is_control_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagCapsLock) != 0) {
|
||||
inputEvent.is_caps_lock_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagOption) != 0) {
|
||||
inputEvent.is_option_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagCommand) != 0) {
|
||||
inputEvent.is_command_pressed = 1;
|
||||
}
|
||||
|
||||
callback(inputEvent);
|
||||
}
|
||||
}];
|
||||
|
|
Loading…
Reference in New Issue
Block a user