diff --git a/Cargo.lock b/Cargo.lock index 1ea2e7c9b..987b6fdc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -470,9 +470,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "combine" -version = "4.6.2" +version = "4.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b2f5d0ee456f3928812dfc8c6d9a1d592b98678f6d56db9b0cd2b7bc6c8db5" +checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" dependencies = [ "bytes", "memchr", @@ -601,8 +601,8 @@ dependencies = [ "lazy_static", "libc", "mach", - "ndk", - "ndk-glue", + "ndk 0.4.0", + "ndk-glue 0.4.0", "nix 0.23.1", "oboe", "parking_lot", @@ -671,8 +671,18 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + +[[package]] +name = "darling" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +dependencies = [ + "darling_core 0.13.1", + "darling_macro 0.13.1", ] [[package]] @@ -689,13 +699,38 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_core" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + [[package]] name = "darling_macro" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ - "darling_core", + "darling_core 0.10.2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +dependencies = [ + "darling_core 0.13.1", "quote", "syn", ] @@ -2116,7 +2151,20 @@ checksum = "d64d6af06fde0e527b1ba5c7b79a6cc89cfc46325b0b2887dffe8f70197e0c3c" dependencies = [ "bitflags", "jni-sys", - "ndk-sys", + "ndk-sys 0.2.2", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys 0.3.0", "num_enum", "thiserror", ] @@ -2130,9 +2178,23 @@ dependencies = [ "lazy_static", "libc", "log", - "ndk", - "ndk-macro", - "ndk-sys", + "ndk 0.4.0", + "ndk-macro 0.2.0", + "ndk-sys 0.2.2", +] + +[[package]] +name = "ndk-glue" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c0d14b0858eb9962a5dac30b809b19f19da7e4547d64af2b0bb051d2e55d79" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.6.0", + "ndk-macro 0.3.0", + "ndk-sys 0.3.0", ] [[package]] @@ -2141,19 +2203,41 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" dependencies = [ - "darling", + "darling 0.10.2", "proc-macro-crate 0.1.5", "proc-macro2", "quote", "syn", ] +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling 0.13.1", + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ndk-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + [[package]] name = "net2" version = "0.2.37" @@ -2371,13 +2455,13 @@ checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" [[package]] name = "oboe" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e15e22bc67e047fe342a32ecba55f555e3be6166b04dd157cd0f803dfa9f48e1" +checksum = "2463c8f2e19b4e0d0710a21f8e4011501ff28db1c95d7a5482a553b2100502d2" dependencies = [ "jni", - "ndk", - "ndk-glue", + "ndk 0.6.0", + "ndk-glue 0.6.0", "num-derive", "num-traits 0.2.14", "oboe-sys", @@ -2385,9 +2469,9 @@ dependencies = [ [[package]] name = "oboe-sys" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338142ae5ab0aaedc8275aa8f67f460e43ae0fca76a695a742d56da0a269eadc" +checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd" dependencies = [ "cc", ] @@ -2400,9 +2484,9 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "openssl-probe" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "pango" @@ -2958,7 +3042,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0" -source = "git+https://github.com/open-trade/rdev#2a3205a13102907da2442a369f8b704601eecc9d" +source = "git+https://github.com/open-trade/rdev#341a4237d2cecfca20f59febddc2ddde29c84449" dependencies = [ "cocoa 0.22.0", "core-foundation 0.7.0", diff --git a/libs/enigo/src/macos/macos_impl.rs b/libs/enigo/src/macos/macos_impl.rs index 0ee038012..328285f45 100644 --- a/libs/enigo/src/macos/macos_impl.rs +++ b/libs/enigo/src/macos/macos_impl.rs @@ -5,18 +5,73 @@ use core_graphics; use self::core_graphics::display::*; use self::core_graphics::event::*; use self::core_graphics::event_source::*; +use std::collections::HashMap as Map; +use std::ffi::c_void; +use std::ffi::CStr; +use std::os::raw::*; +use std::ptr::null_mut; use crate::macos::keycodes::*; use crate::{Key, KeyboardControllable, MouseButton, MouseControllable}; use objc::runtime::Class; struct MyCGEvent; +type TISInputSourceRef = *mut c_void; +type CFDataRef = *const c_void; +type OptionBits = u32; +type OSStatus = i32; +type UniChar = u16; +type UniCharCount = usize; +type Boolean = c_uchar; +type CFStringEncoding = u32; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +struct __CFString([u8; 0]); +type CFStringRef = *const __CFString; + +#[allow(non_upper_case_globals)] +const kCFStringEncodingUTF8: u32 = 134_217_984; +#[allow(non_upper_case_globals)] +const kUCKeyActionDisplay: u16 = 3; +#[allow(non_upper_case_globals)] +const kUCKeyTranslateDeadKeysBit: OptionBits = 1 << 31; +const BUF_LEN: usize = 4; #[allow(improper_ctypes)] #[allow(non_snake_case)] #[link(name = "ApplicationServices", kind = "framework")] extern "C" { + fn CFDataGetBytePtr(theData: CFDataRef) -> *const u8; + fn TISCopyCurrentKeyboardInputSource() -> TISInputSourceRef; + fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef; + fn TISCopyCurrentASCIICapableKeyboardLayoutInputSource() -> TISInputSourceRef; + static kTISPropertyUnicodeKeyLayoutData: *mut c_void; + static kTISPropertyInputSourceID: *mut c_void; + fn UCKeyTranslate( + keyLayoutPtr: *const u8, //*const UCKeyboardLayout, + virtualKeyCode: u16, + keyAction: u16, + modifierKeyState: u32, + keyboardType: u32, + keyTranslateOptions: OptionBits, + deadKeyState: *mut u32, + maxStringLength: UniCharCount, + actualStringLength: *mut UniCharCount, + unicodeString: *mut [UniChar; BUF_LEN], + ) -> OSStatus; + fn LMGetKbdType() -> u8; + fn CFStringGetCString( + theString: CFStringRef, + buffer: *mut c_char, + bufferSize: CFIndex, + encoding: CFStringEncoding, + ) -> Boolean; + fn CGEventPost(tapLocation: CGEventTapLocation, event: *mut MyCGEvent); + // Actually return CFDataRef which is const here, but for coding convienence, return *mut c_void + fn TISGetInputSourceProperty(source: TISInputSourceRef, property: *const c_void) + -> *mut c_void; // not present in servo/core-graphics fn CGEventCreateScrollWheelEvent( source: &CGEventSourceRef, @@ -51,6 +106,7 @@ pub struct Enigo { last_click_time: Option, multiple_click: i64, flags: CGEventFlags, + char_to_vkey_map: Map>, } impl Enigo { @@ -102,6 +158,7 @@ impl Default for Enigo { multiple_click: 1, last_click_time: None, flags: CGEventFlags::CGEventFlagNull, + char_to_vkey_map: Default::default(), } } } @@ -433,7 +490,32 @@ impl Enigo { } #[inline] - fn map_key_board(&self, ch: char) -> CGKeyCode { + fn map_key_board(&mut self, ch: char) -> CGKeyCode { + let mut code = 0; + unsafe { + let (keyboard, layout) = get_layout(); + if !keyboard.is_null() && !layout.is_null() { + let name_ref = TISGetInputSourceProperty(keyboard, kTISPropertyInputSourceID); + if !name_ref.is_null() { + let name = get_string(name_ref as _); + if let Some(name) = name { + if let Some(m) = self.char_to_vkey_map.get(&name) { + code = *m.get(&ch).unwrap_or(&0); + } else { + let m = get_map(&name, layout); + code = *m.get(&ch).unwrap_or(&0); + self.char_to_vkey_map.insert(name.clone(), m); + } + } + } + } + if !keyboard.is_null() { + CFRelease(keyboard); + } + } + if code > 0 { + return code; + } match ch { 'a' => kVK_ANSI_A, 'b' => kVK_ANSI_B, @@ -487,4 +569,99 @@ impl Enigo { } } +#[inline] +unsafe fn get_string(cf_string: CFStringRef) -> Option { + if !cf_string.is_null() { + let mut buf: [i8; 255] = [0; 255]; + let success = CFStringGetCString( + cf_string, + buf.as_mut_ptr(), + buf.len() as _, + kCFStringEncodingUTF8, + ); + if success != 0 { + let name: &CStr = CStr::from_ptr(buf.as_ptr()); + if let Ok(name) = name.to_str() { + return Some(name.to_string()); + } + } + } + None +} + +#[inline] +unsafe fn get_layout() -> (TISInputSourceRef, *const u8) { + let mut keyboard = TISCopyCurrentKeyboardInputSource(); + let mut layout = null_mut(); + if !keyboard.is_null() { + layout = TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData); + } + if layout.is_null() { + if !keyboard.is_null() { + CFRelease(keyboard); + } + // https://github.com/microsoft/vscode/issues/23833 + keyboard = TISCopyCurrentKeyboardLayoutInputSource(); + if !keyboard.is_null() { + layout = TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData); + } + } + if layout.is_null() { + if !keyboard.is_null() { + CFRelease(keyboard); + } + keyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); + if !keyboard.is_null() { + layout = TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData); + } + } + if layout.is_null() { + if !keyboard.is_null() { + CFRelease(keyboard); + } + return (null_mut(), null_mut()); + } + let layout_ptr = CFDataGetBytePtr(layout as _); + if layout_ptr.is_null() { + if !keyboard.is_null() { + CFRelease(keyboard); + } + return (null_mut(), null_mut()); + } + (keyboard, layout_ptr) +} + +#[inline] +fn get_map(name: &str, layout: *const u8) -> Map { + log::info!("Create keyboard map for {}", name); + let mut keys_down: u32 = 0; + let mut map = Map::new(); + for keycode in 0..128 { + let mut buff = [0_u16; BUF_LEN]; + let kb_type = unsafe { LMGetKbdType() }; + let mut length: UniCharCount = 0; + let _retval = unsafe { + UCKeyTranslate( + layout, + keycode, + kUCKeyActionDisplay as _, + 0, + kb_type as _, + kUCKeyTranslateDeadKeysBit as _, + &mut keys_down, + BUF_LEN, + &mut length, + &mut buff, + ) + }; + if length > 0 { + if let Ok(str) = String::from_utf16(&buff[..length]) { + if let Some(chr) = str.chars().next() { + map.insert(chr, keycode as _); + } + } + } + } + map +} unsafe impl Send for Enigo {} diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 657790aa2..eee50d18e 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -40,7 +40,7 @@ struct Input { time: i64, } -const KEY_CHAR_START: i32 = 9999; +const KEY_CHAR_START: u64 = 9999; #[derive(Clone, Default)] pub struct MouseCursorSub { @@ -163,7 +163,7 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()> lazy_static::lazy_static! { static ref ENIGO: Arc> = Arc::new(Mutex::new(Enigo::new())); - static ref KEYS_DOWN: Arc>> = Default::default(); + static ref KEYS_DOWN: Arc>> = Default::default(); static ref LATEST_INPUT: Arc> = Default::default(); } static EXITING: AtomicBool = AtomicBool::new(false); @@ -246,6 +246,11 @@ pub fn fix_key_down_timeout_at_exit() { log::info!("fix_key_down_timeout_at_exit"); } +#[inline] +fn get_layout(key: u32) -> Key { + Key::Layout(std::char::from_u32(key).unwrap_or('\0')) +} + fn fix_key_down_timeout(force: bool) { if KEYS_DOWN.lock().unwrap().is_empty() { return; @@ -256,13 +261,13 @@ fn fix_key_down_timeout(force: bool) { if force || value.elapsed().as_millis() >= 3_000 { KEYS_DOWN.lock().unwrap().remove(&key); let key = if key < KEY_CHAR_START { - if let Some(key) = KEY_MAP.get(&key) { + if let Some(key) = KEY_MAP.get(&(key as _)) { Some(*key) } else { None } } else { - Some(Key::Layout(((key - KEY_CHAR_START) as u8) as _)) + Some(get_layout((key - KEY_CHAR_START) as _)) }; if let Some(key) = key { let func = move || { @@ -604,10 +609,13 @@ fn handle_key_(evt: &KeyEvent) { } if evt.down { allow_err!(en.key_down(key.clone())); - KEYS_DOWN.lock().unwrap().insert(ck.value(), Instant::now()); + KEYS_DOWN + .lock() + .unwrap() + .insert(ck.value() as _, Instant::now()); } else { en.key_up(key.clone()); - KEYS_DOWN.lock().unwrap().remove(&ck.value()); + KEYS_DOWN.lock().unwrap().remove(&(ck.value() as _)); } } else if ck.value() == ControlKey::CtrlAltDel.value() { // have to spawn new thread because send_sas is tokio_main, the caller can not be tokio_main. @@ -621,17 +629,17 @@ fn handle_key_(evt: &KeyEvent) { } Some(key_event::Union::chr(chr)) => { if evt.down { - allow_err!(en.key_down(Key::Layout(chr as u8 as _))); + allow_err!(en.key_down(get_layout(chr))); KEYS_DOWN .lock() .unwrap() - .insert(chr as i32 + KEY_CHAR_START, Instant::now()); + .insert(chr as u64 + KEY_CHAR_START, Instant::now()); } else { - en.key_up(Key::Layout(chr as u8 as _)); + en.key_up(get_layout(chr)); KEYS_DOWN .lock() .unwrap() - .remove(&(chr as i32 + KEY_CHAR_START)); + .remove(&(chr as u64 + KEY_CHAR_START)); } } Some(key_event::Union::unicode(chr)) => {