diff --git a/Cargo.lock b/Cargo.lock index e632114af..6fdb513e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4674,7 +4674,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/fufesou/rdev#eaa35ff9af22891b4aae3a0a5e83472c16177cd8" +source = "git+https://github.com/fufesou/rdev#89d2cb5c4bac81da4aafaedcf78af6af7c80c9d0" dependencies = [ "cocoa", "core-foundation 0.9.3", diff --git a/src/keyboard.rs b/src/keyboard.rs index ce68657ea..dd0fae615 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -10,7 +10,7 @@ use crate::ui::CUR_SESSION; #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::log; use hbb_common::message_proto::*; -use rdev::{Event, EventType, Key}; +use rdev::{Event, EventType, Key, KeyCode}; #[cfg(any(target_os = "windows", target_os = "macos"))] use std::sync::atomic::{AtomicBool, Ordering}; use std::{ @@ -228,7 +228,7 @@ pub fn start_grab_loop() { let mut _keyboard_mode = KeyboardMode::Map; let _scan_code = event.position_code; - let _code = event.platform_code; + let _code = event.platform_code as KeyCode; let res = if KEYBOARD_HOOKED.load(Ordering::SeqCst) { _keyboard_mode = client::process_event(&event, None); if is_press { @@ -264,7 +264,7 @@ pub fn start_grab_loop() { #[cfg(target_os = "macos")] unsafe { - if _code as u32 == rdev::kVK_Option { + if _code == rdev::kVK_Option { IS_LEFT_OPTION_DOWN = is_press; } } @@ -333,19 +333,53 @@ pub fn get_keyboard_mode_enum() -> KeyboardMode { match client::get_keyboard_mode().as_str() { "map" => KeyboardMode::Map, "translate" => KeyboardMode::Translate, - _ => KeyboardMode::Legacy, + "legacy" => KeyboardMode::Legacy, + _ => { + // Set "map" as default mode if version > 1.2.0. + let mut is_peer_version_gt_1_2_0 = false; + + #[cfg(not(any(feature = "flutter", feature = "cli")))] + if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() { + is_peer_version_gt_1_2_0 = + session.get_peer_version() > hbb_common::get_version_number("1.2.0"); + } + #[cfg(feature = "flutter")] + if let Some(session) = SESSIONS + .read() + .unwrap() + .get(&*CUR_SESSION_ID.read().unwrap()) + { + is_peer_version_gt_1_2_0 = + session.get_peer_version() > hbb_common::get_version_number("1.2.0"); + } + if is_peer_version_gt_1_2_0 { + KeyboardMode::Map + } else { + KeyboardMode::Legacy + } + } } } +#[inline] +fn is_numpad_key(event: &Event) -> bool { + matches!(event.event_type, EventType::KeyPress(key) | EventType::KeyRelease(key) if match key { + Key::Kp0 | Key::Kp1 | Key::Kp2 | Key::Kp3| Key::Kp4| Key::Kp5| Key::Kp6| + Key::Kp7| Key::Kp8| Key::Kp9 | Key::KpMinus | Key::KpMultiply | + Key::KpDivide | Key::KpPlus | Key::KpDecimal => true, + _ => false + }) +} + #[cfg(not(any(target_os = "android", target_os = "ios")))] -fn add_numlock_capslock_with_lock_modes(key_event: &mut KeyEvent, lock_modes: i32) { +fn parse_add_lock_modes_modifiers(key_event: &mut KeyEvent, lock_modes: i32, is_numpad_key: bool) { const CAPS_LOCK: i32 = 1; const NUM_LOCK: i32 = 2; // const SCROLL_LOCK: i32 = 3; - if lock_modes & (1 << CAPS_LOCK) != 0 { + if !is_numpad_key && (lock_modes & (1 << CAPS_LOCK) != 0) { key_event.modifiers.push(ControlKey::CapsLock.into()); } - if lock_modes & (1 << NUM_LOCK) != 0 { + if is_numpad_key && (lock_modes & (1 << NUM_LOCK) != 0) { key_event.modifiers.push(ControlKey::NumLock.into()); } // if lock_modes & (1 << SCROLL_LOCK) != 0 { @@ -354,11 +388,11 @@ fn add_numlock_capslock_with_lock_modes(key_event: &mut KeyEvent, lock_modes: i3 } #[cfg(not(any(target_os = "android", target_os = "ios")))] -fn add_numlock_capslock_status(key_event: &mut KeyEvent) { - if get_key_state(enigo::Key::CapsLock) { +fn add_lock_modes_modifiers(key_event: &mut KeyEvent, is_numpad_key: bool) { + if !is_numpad_key && get_key_state(enigo::Key::CapsLock) { key_event.modifiers.push(ControlKey::CapsLock.into()); } - if get_key_state(enigo::Key::NumLock) { + if is_numpad_key && get_key_state(enigo::Key::NumLock) { key_event.modifiers.push(ControlKey::NumLock.into()); } } @@ -443,12 +477,13 @@ pub fn event_to_key_events( }; if keyboard_mode != KeyboardMode::Translate { + let is_numpad_key = is_numpad_key(&event); for key_event in &mut key_events { #[cfg(not(any(target_os = "android", target_os = "ios")))] if let Some(lock_modes) = lock_modes { - add_numlock_capslock_with_lock_modes(key_event, lock_modes); + parse_add_lock_modes_modifiers(key_event, lock_modes, is_numpad_key); } else { - add_numlock_capslock_status(key_event); + add_lock_modes_modifiers(key_event, is_numpad_key); } } } @@ -776,7 +811,7 @@ pub fn map_keyboard_mode(peer: &str, event: &Event, mut key_event: KeyEvent) -> #[cfg(any(target_os = "android", target_os = "ios"))] let keycode = 0; - key_event.set_chr(keycode); + key_event.set_chr(keycode as _); Some(key_event) } @@ -851,6 +886,8 @@ fn is_altgr(event: &Event) -> bool { } } +#[inline] +#[cfg(any(target_os = "linux", target_os = "windows"))] fn is_press(event: &Event) -> bool { matches!(event.event_type, EventType::KeyPress(_)) } @@ -873,7 +910,7 @@ pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) - #[cfg(target_os = "macos")] // ignore right option key - if event.platform_code as u32 == rdev::kVK_RightOption { + if event.platform_code == rdev::kVK_RightOption as u32 { return events; } diff --git a/src/server/input_service.rs b/src/server/input_service.rs index bb9ba167c..797488845 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -5,7 +5,7 @@ use crate::common::IS_X11; use dispatch::Queue; use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable}; use hbb_common::{config::COMPRESS_LEVEL, get_time, protobuf::EnumOrUnknown}; -use rdev::{self, EventType, Key as RdevKey, RawKey}; +use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey}; #[cfg(target_os = "macos")] use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput}; use std::time::Duration; @@ -113,6 +113,87 @@ impl Subscriber for MouseCursorSub { } } +#[cfg(not(target_os = "macos"))] +struct LockModesHandler { + caps_lock_changed: bool, + num_lock_changed: bool, +} + +#[cfg(target_os = "macos")] +struct LockModesHandler; + +impl LockModesHandler { + #[inline] + fn is_modifier_enabled(key_event: &KeyEvent, modifier: ControlKey) -> bool { + key_event.modifiers.contains(&modifier.into()) + } + + #[cfg(not(target_os = "macos"))] + fn new(key_event: &KeyEvent) -> Self { + let mut en = ENIGO.lock().unwrap(); + let event_caps_enabled = Self::is_modifier_enabled(key_event, ControlKey::CapsLock); + let local_caps_enabled = en.get_key_state(enigo::Key::CapsLock); + let caps_lock_changed = event_caps_enabled != local_caps_enabled; + if caps_lock_changed { + en.key_click(enigo::Key::CapsLock); + } + + let event_num_enabled = Self::is_modifier_enabled(key_event, ControlKey::NumLock); + let local_num_enabled = en.get_key_state(enigo::Key::NumLock); + #[cfg(not(target_os = "windows"))] + let disable_numlock = false; + #[cfg(target_os = "windows")] + let disable_numlock = is_numlock_disabled(key_event); + let num_lock_changed = event_num_enabled != local_num_enabled && !disable_numlock; + if num_lock_changed { + en.key_click(enigo::Key::NumLock); + } + + Self { + caps_lock_changed, + num_lock_changed, + } + } + + #[cfg(target_os = "macos")] + fn new(key_event: &KeyEvent) -> Self { + let event_caps_enabled = Self::is_modifier_enabled(key_event, ControlKey::CapsLock); + // Do not use the following code to detect `local_caps_enabled`. + // Because the state of get_key_state will not affect simuation of `VIRTUAL_INPUT_STATE` in this file. + // + // let local_caps_enabled = VirtualInput::get_key_state( + // CGEventSourceStateID::CombinedSessionState, + // rdev::kVK_CapsLock, + // ); + let local_caps_enabled = unsafe { + let _lock = VIRTUAL_INPUT_MTX.lock(); + VIRTUAL_INPUT_STATE + .as_ref() + .map_or(false, |input| input.capslock_down) + }; + if event_caps_enabled && !local_caps_enabled { + press_capslock(); + } else if !event_caps_enabled && local_caps_enabled { + release_capslock(); + } + + Self {} + } +} + +#[cfg(not(target_os = "macos"))] +impl Drop for LockModesHandler { + fn drop(&mut self) { + let mut en = ENIGO.lock().unwrap(); + if self.caps_lock_changed { + en.key_click(enigo::Key::CapsLock); + } + if self.num_lock_changed { + en.key_click(enigo::Key::NumLock); + } + } +} + pub const NAME_CURSOR: &'static str = "mouse_cursor"; pub const NAME_POS: &'static str = "mouse_pos"; pub type MouseCursorService = ServiceTmpl; @@ -268,10 +349,33 @@ lazy_static::lazy_static! { static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned()); } +#[cfg(target_os = "macos")] +struct VirtualInputState { + virtual_input: VirtualInput, + capslock_down: bool, +} + +#[cfg(target_os = "macos")] +impl VirtualInputState { + fn new() -> Option { + VirtualInput::new(CGEventSourceStateID::Private, CGEventTapLocation::Session) + .map(|virtual_input| Self { + virtual_input, + capslock_down: false, + }) + .ok() + } + + #[inline] + fn simulate(&self, event_type: &EventType) -> ResultType<()> { + Ok(self.virtual_input.simulate(&event_type)?) + } +} + #[cfg(target_os = "macos")] static mut VIRTUAL_INPUT_MTX: Mutex<()> = Mutex::new(()); #[cfg(target_os = "macos")] -static mut VIRTUAL_INPUT: Option = None; +static mut VIRTUAL_INPUT_STATE: Option = None; // First call set_uinput() will create keyboard and mouse clients. // The clients are ipc connections that must live shorter than tokio runtime. @@ -345,6 +449,12 @@ fn is_pressed(key: &Key, en: &mut Enigo) -> bool { get_modifier_state(key.clone(), en) } +#[inline] +#[cfg(target_os = "macos")] +fn key_sleep() { + std::thread::sleep(Duration::from_millis(20)); +} + #[inline] fn get_modifier_state(key: Key, en: &mut Enigo) -> bool { // https://github.com/rustdesk/rustdesk/issues/332 @@ -413,6 +523,7 @@ pub fn fix_key_down_timeout_at_exit() { log::info!("fix_key_down_timeout_at_exit"); } +#[inline] #[cfg(target_os = "linux")] pub fn clear_remapped_keycode() { ENIGO.lock().unwrap().tfc_clear_remapped(); @@ -746,7 +857,7 @@ pub fn handle_key(evt: &KeyEvent) { // having GUI, run main GUI thread, otherwise crash let evt = evt.clone(); QUEUE.exec_async(move || handle_key_(&evt)); - std::thread::sleep(Duration::from_millis(20)); + key_sleep(); return; } #[cfg(windows)] @@ -754,7 +865,7 @@ pub fn handle_key(evt: &KeyEvent) { #[cfg(not(windows))] handle_key_(evt); #[cfg(target_os = "macos")] - std::thread::sleep(Duration::from_millis(20)); + key_sleep(); } #[cfg(target_os = "macos")] @@ -762,8 +873,7 @@ pub fn handle_key(evt: &KeyEvent) { fn reset_input() { unsafe { let _lock = VIRTUAL_INPUT_MTX.lock(); - VIRTUAL_INPUT = - VirtualInput::new(CGEventSourceStateID::Private, CGEventTapLocation::Session).ok(); + VIRTUAL_INPUT_STATE = VirtualInputState::new(); } } @@ -776,7 +886,7 @@ pub fn reset_input_ondisconn() { } } -fn sim_rdev_rawkey_position(code: u32, keydown: bool) { +fn sim_rdev_rawkey_position(code: KeyCode, keydown: bool) { #[cfg(target_os = "windows")] let rawkey = RawKey::ScanCode(code); #[cfg(target_os = "linux")] @@ -810,13 +920,43 @@ fn sim_rdev_rawkey_virtual(code: u32, keydown: bool) { simulate_(&event_type); } -#[cfg(target_os = "macos")] #[inline] +#[cfg(target_os = "macos")] fn simulate_(event_type: &EventType) { unsafe { let _lock = VIRTUAL_INPUT_MTX.lock(); - if let Some(virtual_input) = &VIRTUAL_INPUT { - let _ = virtual_input.simulate(&event_type); + if let Some(input) = &VIRTUAL_INPUT_STATE { + let _ = input.simulate(&event_type); + } + } +} + +#[inline] +#[cfg(target_os = "macos")] +fn press_capslock() { + let caps_key = RdevKey::RawKey(rdev::RawKey::MacVirtualKeycode(rdev::kVK_CapsLock)); + unsafe { + let _lock = VIRTUAL_INPUT_MTX.lock(); + if let Some(input) = &mut VIRTUAL_INPUT_STATE { + if input.simulate(&EventType::KeyPress(caps_key)).is_ok() { + input.capslock_down = true; + key_sleep(); + } + } + } +} + +#[cfg(target_os = "macos")] +#[inline] +fn release_capslock() { + let caps_key = RdevKey::RawKey(rdev::RawKey::MacVirtualKeycode(rdev::kVK_CapsLock)); + unsafe { + let _lock = VIRTUAL_INPUT_MTX.lock(); + if let Some(input) = &mut VIRTUAL_INPUT_STATE { + if input.simulate(&EventType::KeyRelease(caps_key)).is_ok() { + input.capslock_down = false; + key_sleep(); + } } } } @@ -832,14 +972,6 @@ fn simulate_(event_type: &EventType) { } } -fn is_modifier_in_key_event(control_key: ControlKey, key_event: &KeyEvent) -> bool { - key_event - .modifiers - .iter() - .position(|&m| m == control_key.into()) - .is_some() -} - #[inline] fn control_key_value_to_key(value: i32) -> Option { KEY_MAP.get(&value).and_then(|k| Some(*k)) @@ -850,10 +982,6 @@ fn char_value_to_key(value: u32) -> Key { Key::Layout(std::char::from_u32(value).unwrap_or('\0')) } -fn is_not_same_status(client_locking: bool, remote_locking: bool) -> bool { - client_locking != remote_locking -} - #[cfg(target_os = "windows")] fn has_numpad_key(key_event: &KeyEvent) -> bool { key_event @@ -893,44 +1021,6 @@ fn is_numlock_disabled(key_event: &KeyEvent) -> bool { } } -fn click_capslock(en: &mut Enigo) { - #[cfg(not(targe_os = "macos"))] - en.key_click(enigo::Key::CapsLock); - #[cfg(target_os = "macos")] - let _ = en.key_down(enigo::Key::CapsLock); -} - -fn click_numlock(_en: &mut Enigo) { - // without numlock in macos - #[cfg(not(target_os = "macos"))] - _en.key_click(enigo::Key::NumLock); -} - -fn sync_numlock_capslock_status(key_event: &KeyEvent) { - let mut en = ENIGO.lock().unwrap(); - - let client_caps_locking = is_modifier_in_key_event(ControlKey::CapsLock, key_event); - let client_num_locking = is_modifier_in_key_event(ControlKey::NumLock, key_event); - let remote_caps_locking = en.get_key_state(enigo::Key::CapsLock); - let remote_num_locking = en.get_key_state(enigo::Key::NumLock); - - let need_click_capslock = is_not_same_status(client_caps_locking, remote_caps_locking); - let need_click_numlock = is_not_same_status(client_num_locking, remote_num_locking); - - #[cfg(not(target_os = "windows"))] - let disable_numlock = false; - #[cfg(target_os = "windows")] - let disable_numlock = is_numlock_disabled(key_event); - - if need_click_capslock { - click_capslock(&mut en); - } - - if need_click_numlock && !disable_numlock { - click_numlock(&mut en); - } -} - fn map_keyboard_mode(evt: &KeyEvent) { #[cfg(windows)] crate::platform::windows::try_change_desktop(); @@ -949,7 +1039,7 @@ fn map_keyboard_mode(evt: &KeyEvent) { return; } - sim_rdev_rawkey_position(evt.chr(), evt.down); + sim_rdev_rawkey_position(evt.chr() as _, evt.down); } #[cfg(target_os = "macos")] @@ -1127,7 +1217,7 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { fn translate_process_code(code: u32, down: bool) { crate::platform::windows::try_change_desktop(); match code >> 16 { - 0 => sim_rdev_rawkey_position(code, down), + 0 => sim_rdev_rawkey_position(code as _, down), vk_code => sim_rdev_rawkey_virtual(vk_code, down), }; } @@ -1158,7 +1248,7 @@ fn translate_keyboard_mode(evt: &KeyEvent) { #[cfg(target_os = "windows")] translate_process_code(evt.chr(), evt.down); #[cfg(not(target_os = "windows"))] - sim_rdev_rawkey_position(evt.chr(), evt.down); + sim_rdev_rawkey_position(evt.chr() as _, evt.down); } Some(key_event::Union::Unicode(..)) => { // Do not handle unicode for now. @@ -1174,9 +1264,19 @@ pub fn handle_key_(evt: &KeyEvent) { return; } - if evt.down { - sync_numlock_capslock_status(evt) - } + let _lock_mode_handler = match &evt.union { + Some(key_event::Union::Unicode(..)) | Some(key_event::Union::Seq(..)) => { + Some(LockModesHandler::new(&evt)) + } + _ => { + if evt.down { + Some(LockModesHandler::new(&evt)) + } else { + None + } + } + }; + match evt.mode.unwrap() { KeyboardMode::Map => { map_keyboard_mode(evt); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 8a1da6b8f..824f8befa 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -10,7 +10,7 @@ use std::time::{Duration, SystemTime}; use async_trait::async_trait; use bytes::Bytes; -use rdev::{Event, EventType::*}; +use rdev::{Event, EventType::*, KeyCode}; use uuid::Uuid; use hbb_common::config::{Config, LocalConfig, PeerConfig}; @@ -421,7 +421,7 @@ impl Session { rdev::win_scancode_from_key(key).unwrap_or_default() } "macos" => { - let key = rdev::macos_key_from_code(code); + let key = rdev::macos_key_from_code(code as _); let key = match key { rdev::Key::ControlLeft => rdev::Key::MetaLeft, rdev::Key::MetaLeft => rdev::Key::ControlLeft, @@ -429,7 +429,7 @@ impl Session { rdev::Key::MetaRight => rdev::Key::ControlLeft, _ => key, }; - rdev::macos_keycode_from_key(key).unwrap_or_default() + rdev::macos_keycode_from_key(key).unwrap_or_default() as _ } _ => { let key = rdev::linux_key_from_code(code); @@ -545,8 +545,8 @@ impl Session { if scancode < 0 || keycode < 0 { return; } - let keycode: u32 = keycode as u32; - let scancode: u32 = scancode as u32; + let keycode: KeyCode = keycode as _; + let scancode: u32 = scancode as _; #[cfg(not(target_os = "windows"))] let key = rdev::key_from_code(keycode) as rdev::Key;