This commit is contained in:
parent
c7d6d69b72
commit
2745257ce9
|
@ -32,7 +32,7 @@ use log::{error, trace, warn};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use thiserror::Error;
|
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::{Key::*, MouseButton, MouseEvent};
|
||||||
use crate::{event::Status::*, Source, SourceCallback};
|
use crate::{event::Status::*, Source, SourceCallback};
|
||||||
use crate::{event::Variant::*, hotkey::HotKey};
|
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
|
// Take a look at the native.h header file for an explanation of the fields
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct RawInputEvent {
|
pub struct RawInputEvent {
|
||||||
pub event_type: i32,
|
pub event_type: i32,
|
||||||
|
|
||||||
|
@ -58,6 +59,12 @@ pub struct RawInputEvent {
|
||||||
|
|
||||||
pub key_code: i32,
|
pub key_code: i32,
|
||||||
pub status: 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)]
|
#[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! {
|
lazy_static! {
|
||||||
static ref CURRENT_SENDER: Arc<Mutex<Option<Sender<InputEvent>>>> = Arc::new(Mutex::new(None));
|
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) {
|
extern "C" fn native_callback(raw_event: RawInputEvent) {
|
||||||
let lock = CURRENT_SENDER
|
let lock = CURRENT_SENDER
|
||||||
.lock()
|
.lock()
|
||||||
.expect("unable to acquire CocoaSource 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() {
|
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();
|
let event: Option<InputEvent> = raw_event.into();
|
||||||
if let Some(event) = event {
|
if let Some(event) = event {
|
||||||
if let Err(error) = sender.send(event) {
|
if let Err(error) = sender.send(event) {
|
||||||
|
@ -386,6 +475,11 @@ mod tests {
|
||||||
buffer_len: 0,
|
buffer_len: 0,
|
||||||
key_code: 0,
|
key_code: 0,
|
||||||
status: INPUT_STATUS_PRESSED,
|
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
|
// Pressed or Released status
|
||||||
int32_t 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;
|
} InputEvent;
|
||||||
|
|
||||||
typedef void (*EventCallback)(InputEvent data);
|
typedef void (*EventCallback)(InputEvent data);
|
||||||
|
|
|
@ -76,6 +76,19 @@ void * detect_initialize(EventCallback callback, InitializeOptions options) {
|
||||||
strncpy(inputEvent.buffer, chars, 23);
|
strncpy(inputEvent.buffer, chars, 23);
|
||||||
inputEvent.buffer_len = event.characters.length;
|
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);
|
callback(inputEvent);
|
||||||
}else if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeOtherMouseDown ||
|
}else if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeOtherMouseDown ||
|
||||||
event.type == NSEventTypeLeftMouseUp || event.type == NSEventTypeRightMouseUp || event.type == NSEventTypeOtherMouseUp) {
|
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) {
|
} else if (event.keyCode == kVK_Option || event.keyCode == kVK_RightOption) {
|
||||||
inputEvent.status = (([event modifierFlags] & NSEventModifierFlagOption) == 0) ? INPUT_STATUS_RELEASED : INPUT_STATUS_PRESSED;
|
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);
|
callback(inputEvent);
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
Loading…
Reference in New Issue
Block a user