load keyboard layout for mac

This commit is contained in:
rustdesk 2022-01-14 01:15:03 +08:00
parent 7b1806b0f3
commit 780b57f597
3 changed files with 301 additions and 32 deletions

126
Cargo.lock generated
View File

@ -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",

View File

@ -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<std::time::Instant>,
multiple_click: i64,
flags: CGEventFlags,
char_to_vkey_map: Map<String, Map<char, CGKeyCode>>,
}
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<String> {
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<char, CGKeyCode> {
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 {}

View File

@ -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<Mutex<Enigo>> = Arc::new(Mutex::new(Enigo::new()));
static ref KEYS_DOWN: Arc<Mutex<HashMap<i32, Instant>>> = Default::default();
static ref KEYS_DOWN: Arc<Mutex<HashMap<u64, Instant>>> = Default::default();
static ref LATEST_INPUT: Arc<Mutex<Input>> = 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)) => {