diff --git a/src/macos/common.rs b/src/macos/common.rs index d4448669..a288c874 100644 --- a/src/macos/common.rs +++ b/src/macos/common.rs @@ -13,10 +13,58 @@ use crate::macos::keycodes::key_from_code_or_name; use super::keycodes::key_from_special_key; lazy_static! { - pub static ref LAST_FLAGS: Mutex = Mutex::new(CGEventFlags(0)); pub static ref KEYBOARD_STATE: Mutex = Mutex::new(Keyboard::new().unwrap()); } +const CONTROL_LEFT: u16 = 59; +const CONTROL_RIGHT: u16 = 62; +const SHIFT_LEFT: u16 = 56; +const SHIFT_RIGHT: u16 = 60; +const ALT_LEFT: u16 = 58; +const ALT_RIGHT: u16 = 61; +const META_LEFT: u16 = 55; +const META_RIGHT: u16 = 54; +const CAPS_LOCK: u16 = 57; +const FUNCTION: u16 = 63; + +// Device-dependent modifier masks from IOKit/hidsystem/ev_keymap.h. Unlike +// CGEventFlags' aggregate masks, these identify the physical left/right key. +const NX_DEVICELCTLKEYMASK: u64 = 0x0000_0001; +const NX_DEVICELSHIFTKEYMASK: u64 = 0x0000_0002; +const NX_DEVICERSHIFTKEYMASK: u64 = 0x0000_0004; +const NX_DEVICELCMDKEYMASK: u64 = 0x0000_0008; +const NX_DEVICERCMDKEYMASK: u64 = 0x0000_0010; +const NX_DEVICELALTKEYMASK: u64 = 0x0000_0020; +const NX_DEVICERALTKEYMASK: u64 = 0x0000_0040; +const NX_DEVICERCTLKEYMASK: u64 = 0x0000_2000; + +pub(super) fn modifier_mask_for_keycode(code: u16) -> Option { + let mask = match code { + CONTROL_LEFT => NX_DEVICELCTLKEYMASK, + CONTROL_RIGHT => NX_DEVICERCTLKEYMASK, + SHIFT_LEFT => NX_DEVICELSHIFTKEYMASK, + SHIFT_RIGHT => NX_DEVICERSHIFTKEYMASK, + ALT_LEFT => NX_DEVICELALTKEYMASK, + ALT_RIGHT => NX_DEVICERALTKEYMASK, + META_LEFT => NX_DEVICELCMDKEYMASK, + META_RIGHT => NX_DEVICERCMDKEYMASK, + CAPS_LOCK => CGEventFlags::MaskAlphaShift.bits(), + FUNCTION => CGEventFlags::MaskSecondaryFn.bits(), + _ => return None, + }; + Some(CGEventFlags::from_bits_retain(mask)) +} + +fn modifier_event_type(code: u16, flags: CGEventFlags) -> Option { + let mask = modifier_mask_for_keycode(code)?; + let key = key_from_code_or_name(code, None); + if flags.contains(mask) { + Some(EventType::KeyPress(key)) + } else { + Some(EventType::KeyRelease(key)) + } +} + pub fn set_is_main_thread(b: bool) { KEYBOARD_STATE.lock().unwrap().set_is_main_thread(b); } @@ -82,53 +130,7 @@ pub unsafe fn convert( ); let code = code.try_into().ok()?; let flags = CGEvent::flags(Some(cg_event.as_ref())); - let key = key_from_code_or_name(code, None); - - // Determine if this is a press or release based on flag changes - let mut global_flags = LAST_FLAGS.lock().unwrap(); - if flags.contains(CGEventFlags::MaskShift) - && !global_flags.contains(CGEventFlags::MaskShift) - { - *global_flags = flags; - Some(EventType::KeyPress(key)) - } else if !flags.contains(CGEventFlags::MaskShift) - && global_flags.contains(CGEventFlags::MaskShift) - { - *global_flags = flags; - Some(EventType::KeyRelease(key)) - } else if flags.contains(CGEventFlags::MaskControl) - && !global_flags.contains(CGEventFlags::MaskControl) - { - *global_flags = flags; - Some(EventType::KeyPress(key)) - } else if !flags.contains(CGEventFlags::MaskControl) - && global_flags.contains(CGEventFlags::MaskControl) - { - *global_flags = flags; - Some(EventType::KeyRelease(key)) - } else if flags.contains(CGEventFlags::MaskAlternate) - && !global_flags.contains(CGEventFlags::MaskAlternate) - { - *global_flags = flags; - Some(EventType::KeyPress(key)) - } else if !flags.contains(CGEventFlags::MaskAlternate) - && global_flags.contains(CGEventFlags::MaskAlternate) - { - *global_flags = flags; - Some(EventType::KeyRelease(key)) - } else if flags.contains(CGEventFlags::MaskCommand) - && !global_flags.contains(CGEventFlags::MaskCommand) - { - *global_flags = flags; - Some(EventType::KeyPress(key)) - } else if !flags.contains(CGEventFlags::MaskCommand) - && global_flags.contains(CGEventFlags::MaskCommand) - { - *global_flags = flags; - Some(EventType::KeyRelease(key)) - } else { - None - } + modifier_event_type(code, flags) } CGEventType::ScrollWheel => { let delta_y = CGEvent::integer_value_field( @@ -197,3 +199,41 @@ pub unsafe fn convert( } None } + +#[cfg(test)] +mod tests { + use super::{ + NX_DEVICELSHIFTKEYMASK, NX_DEVICERSHIFTKEYMASK, SHIFT_LEFT, SHIFT_RIGHT, + modifier_event_type, + }; + use crate::{EventType, Key}; + use objc2_core_graphics::CGEventFlags; + + fn flags(bits: u64) -> CGEventFlags { + CGEventFlags::from_bits_retain(bits) + } + + #[test] + fn classifies_modifier_from_keycode_without_cached_state() { + let left_down = flags(CGEventFlags::MaskShift.bits() | NX_DEVICELSHIFTKEYMASK); + assert!(matches!( + modifier_event_type(SHIFT_LEFT, left_down), + Some(EventType::KeyPress(Key::ShiftLeft)) + )); + + let right_down = flags(CGEventFlags::MaskShift.bits() | NX_DEVICERSHIFTKEYMASK); + assert!(matches!( + modifier_event_type(SHIFT_LEFT, right_down), + Some(EventType::KeyRelease(Key::ShiftLeft)) + )); + assert!(matches!( + modifier_event_type(SHIFT_RIGHT, right_down), + Some(EventType::KeyPress(Key::ShiftRight)) + )); + } + + #[test] + fn ignores_flags_changed_for_unknown_keycode() { + assert!(modifier_event_type(0, CGEventFlags::empty()).is_none()); + } +} diff --git a/src/macos/keycodes.rs b/src/macos/keycodes.rs index d773c510..fefba80c 100644 --- a/src/macos/keycodes.rs +++ b/src/macos/keycodes.rs @@ -192,6 +192,7 @@ pub fn key_from_code(code: CGKeyCode) -> Key { BACKSPACE => Key::Backspace, CAPS_LOCK => Key::CapsLock, CONTROL_LEFT => Key::ControlLeft, + CONTROL_RIGHT => Key::ControlRight, DOWN_ARROW => Key::DownArrow, ESCAPE => Key::Escape, F1 => Key::F1, diff --git a/src/macos/simulate.rs b/src/macos/simulate.rs index 2b1beb77..816aad96 100644 --- a/src/macos/simulate.rs +++ b/src/macos/simulate.rs @@ -7,10 +7,13 @@ use objc2_core_graphics::{ use crate::Key; use crate::rdev::{Button, EventType, SimulateError}; use std::convert::TryInto; +use std::sync::Mutex; -use crate::macos::common::LAST_FLAGS; +use crate::macos::common::modifier_mask_for_keycode; use crate::macos::keycodes::code_from_key; +static SIMULATED_FLAGS: Mutex = Mutex::new(CGEventFlags::empty()); + unsafe fn convert_native_with_source( event_type: &EventType, source: CFRetained, @@ -30,7 +33,7 @@ unsafe fn convert_native_with_source( ); // Get current flags and update them - let mut new_flags = LAST_FLAGS.lock().unwrap(); + let mut new_flags = SIMULATED_FLAGS.lock().unwrap(); match key { Key::ShiftLeft | Key::ShiftRight => { new_flags.insert(CGEventFlags::MaskShift); @@ -46,13 +49,16 @@ unsafe fn convert_native_with_source( } _ => {} } + if let Some(mask) = modifier_mask_for_keycode(code) { + new_flags.insert(mask); + } CGEvent::set_flags(Some(&event), *new_flags); // event.set_flags(*new_flags); Some(event) } else { // For non-modifier keys, use regular key events let event = CGEvent::new_keyboard_event(Some(&source), code, true)?; - CGEvent::set_flags(Some(&event), *LAST_FLAGS.lock().unwrap()); + CGEvent::set_flags(Some(&event), *SIMULATED_FLAGS.lock().unwrap()); Some(event) } } @@ -69,7 +75,7 @@ unsafe fn convert_native_with_source( ); // Get current flags and update them - let mut new_flags = LAST_FLAGS.lock().unwrap(); + let mut new_flags = SIMULATED_FLAGS.lock().unwrap(); match key { Key::ShiftLeft | Key::ShiftRight => { new_flags.remove(CGEventFlags::MaskShift); @@ -85,12 +91,15 @@ unsafe fn convert_native_with_source( } _ => {} } + if let Some(mask) = modifier_mask_for_keycode(code) { + new_flags.remove(mask); + } CGEvent::set_flags(Some(&event), *new_flags); Some(event) } else { // For non-modifier keys, use regular key events let event = CGEvent::new_keyboard_event(Some(&source), code, false)?; - CGEvent::set_flags(Some(&event), *LAST_FLAGS.lock().unwrap()); + CGEvent::set_flags(Some(&event), *SIMULATED_FLAGS.lock().unwrap()); Some(event) } }