From 01ade733049c456c0603476c0afb8b8db657d713 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 29 Dec 2022 14:28:15 +0800 Subject: [PATCH 1/4] fix macos sticky fn, https://stackoverflow.com/questions/74938870/sticky-fn-after-home-is-simulated-programmatically-macos Signed-off-by: fufesou --- Cargo.lock | 2 +- libs/enigo/src/macos/macos_impl.rs | 1 + src/server/input_service.rs | 13 ++++++++----- src/tray.rs | 12 +++++++----- src/ui_interface.rs | 3 +++ 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ebf82edac..edb74b010 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4305,7 +4305,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/fufesou/rdev#bc2db2f13dfdc95df8a02eb03f71325c173283dc" +source = "git+https://github.com/fufesou/rdev#edddb71a88bd8a4737ef4216861b426490c49f2e" dependencies = [ "cocoa", "core-foundation 0.9.3", diff --git a/libs/enigo/src/macos/macos_impl.rs b/libs/enigo/src/macos/macos_impl.rs index 1ae41f0c3..68457a4a2 100644 --- a/libs/enigo/src/macos/macos_impl.rs +++ b/libs/enigo/src/macos/macos_impl.rs @@ -40,6 +40,7 @@ const BUF_LEN: usize = 4; #[allow(improper_ctypes)] #[allow(non_snake_case)] #[link(name = "ApplicationServices", kind = "framework")] +#[link(name = "Carbon", kind = "framework")] extern "C" { fn CFDataGetBytePtr(theData: CFDataRef) -> *const u8; fn TISCopyCurrentKeyboardInputSource() -> TISInputSourceRef; diff --git a/src/server/input_service.rs b/src/server/input_service.rs index a94a7a2ef..755238c27 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -288,6 +288,7 @@ fn modifier_sleep() { } #[inline] +#[cfg(not(target_os = "macos"))] fn is_pressed(key: &Key, en: &mut Enigo) -> bool { get_modifier_state(key.clone(), en) } @@ -780,13 +781,13 @@ fn click_capslock(en: &mut Enigo) { #[cfg(not(targe_os = "macos"))] en.key_click(enigo::Key::CapsLock); #[cfg(target_os = "macos")] - en.key_down(enigo::Key::CapsLock); + let _ = en.key_down(enigo::Key::CapsLock); } -fn click_numlock(en: &mut Enigo) { +fn click_numlock(_en: &mut Enigo) { // without numlock in macos #[cfg(not(target_os = "macos"))] - en.key_click(enigo::Key::NumLock); + _en.key_click(enigo::Key::NumLock); } fn sync_numlock_capslock_status(key_event: &KeyEvent) { @@ -872,6 +873,7 @@ fn is_altgr_pressed() -> bool { .is_some() } +#[cfg(not(target_os = "macos"))] fn press_modifiers(en: &mut Enigo, key_event: &KeyEvent, to_release: &mut Vec) { for ref ck in key_event.modifiers.iter() { if let Some(key) = control_key_value_to_key(ck.value()) { @@ -889,14 +891,14 @@ fn press_modifiers(en: &mut Enigo, key_event: &KeyEvent, to_release: &mut Vec) { +fn sync_modifiers(en: &mut Enigo, key_event: &KeyEvent, _to_release: &mut Vec) { #[cfg(target_os = "macos")] add_flags_to_enigo(en, key_event); if key_event.down { release_unpressed_modifiers(en, key_event); #[cfg(not(target_os = "macos"))] - press_modifiers(en, key_event, to_release); + press_modifiers(en, key_event, _to_release); } } @@ -944,6 +946,7 @@ fn process_seq(en: &mut Enigo, sequence: &str) { en.key_sequence(&sequence); } +#[cfg(not(target_os = "macos"))] fn release_keys(en: &mut Enigo, to_release: &Vec) { for key in to_release { en.key_up(key.clone()); diff --git a/src/tray.rs b/src/tray.rs index 1afd988ae..98a4127a3 100644 --- a/src/tray.rs +++ b/src/tray.rs @@ -1,3 +1,4 @@ +#[cfg(any(target_os = "linux", target_os = "windows"))] use super::ui_interface::get_option_opt; #[cfg(target_os = "linux")] use hbb_common::log::{debug, error, info}; @@ -44,7 +45,7 @@ pub fn start_tray() { } else { *control_flow = ControlFlow::Wait; } - let stopped = is_service_stoped(); + let stopped = is_service_stopped(); let state = if stopped { 2 } else { 1 }; let old = *old_state.lock().unwrap(); if state != old { @@ -101,7 +102,7 @@ pub fn start_tray() { } if let Some(mut appindicator) = get_default_app_indicator() { let mut menu = gtk::Menu::new(); - let stoped = is_service_stoped(); + let stoped = is_service_stopped(); // start/stop service let label = if stoped { crate::client::translate("Start Service".to_owned()) @@ -137,7 +138,7 @@ pub fn start_tray() { #[cfg(target_os = "linux")] fn change_service_state() { - if is_service_stoped() { + if is_service_stopped() { debug!("Now try to start service"); crate::ipc::set_option("stop-service", ""); } else { @@ -151,7 +152,7 @@ fn change_service_state() { fn update_tray_service_item(item: >k::MenuItem) { use gtk::traits::GtkMenuItemExt; - if is_service_stoped() { + if is_service_stopped() { item.set_label(&crate::client::translate("Start Service".to_owned())); } else { item.set_label(&crate::client::translate("Stop service".to_owned())); @@ -194,7 +195,8 @@ fn get_default_app_indicator() -> Option { /// Check if service is stoped. /// Return [`true`] if service is stoped, [`false`] otherwise. #[inline] -fn is_service_stoped() -> bool { +#[cfg(any(target_os = "linux", target_os = "windows"))] +fn is_service_stopped() -> bool { if let Some(v) = get_option_opt("stop-service") { v == "Y" } else { diff --git a/src/ui_interface.rs b/src/ui_interface.rs index ffcf110b3..2e4ca4ea3 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -158,6 +158,7 @@ pub fn get_license() -> String { } #[inline] +#[cfg(any(target_os = "linux", target_os = "windows"))] pub fn get_option_opt(key: &str) -> Option { OPTIONS.lock().unwrap().get(key).map(|x| x.clone()) } @@ -199,11 +200,13 @@ pub fn set_local_flutter_config(key: String, value: String) { LocalConfig::set_flutter_config(key, value); } +#[cfg(feature = "flutter")] #[inline] pub fn get_kb_layout_type() -> String { LocalConfig::get_kb_layout_type() } +#[cfg(feature = "flutter")] #[inline] pub fn set_kb_layout_type(kb_layout_type: String) { LocalConfig::set_kb_layout_type(kb_layout_type); From 94cecb186059e27706501c52b0ee2c24e601f3b3 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 29 Dec 2022 17:11:10 +0800 Subject: [PATCH 2/4] macos, use private CGEventSource Signed-off-by: fufesou --- Cargo.lock | 2 +- src/server/connection.rs | 2 ++ src/server/input_service.rs | 66 +++++++++++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index edb74b010..b0b29503f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4305,7 +4305,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/fufesou/rdev#edddb71a88bd8a4737ef4216861b426490c49f2e" +source = "git+https://github.com/fufesou/rdev#196b589573f90703a601e6b105dd7c917fc388f9" dependencies = [ "cocoa", "core-foundation 0.9.3", diff --git a/src/server/connection.rs b/src/server/connection.rs index e1a360e7a..b569ef708 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -523,6 +523,8 @@ impl Connection { rdev::set_dw_mouse_extra_info(enigo::ENIGO_INPUT_EXTRA_VALUE); rdev::set_dw_keyboard_extra_info(enigo::ENIGO_INPUT_EXTRA_VALUE); } + #[cfg(target_os = "macos")] + reset_input_ondisconn(); loop { match receiver.recv_timeout(std::time::Duration::from_millis(500)) { Ok(v) => match v { diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 755238c27..709513867 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -5,7 +5,9 @@ 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, simulate, EventType, Key as RdevKey, RawKey}; +use rdev::{self, EventType, Key as RdevKey, RawKey}; +#[cfg(target_os = "macos")] +use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput}; use std::time::Duration; use std::{ convert::TryFrom, @@ -221,6 +223,10 @@ lazy_static::lazy_static! { static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned()); } +// virtual_input must be used in main thread. +// No need to wrap mutex. +static mut VIRTUAL_INPUT: Option = None; + // First call set_uinput() will create keyboard and mouse clients. // The clients are ipc connections that must live shorter than tokio runtime. // Thus this function must not be called in a temporary runtime. @@ -392,13 +398,15 @@ fn record_key_to_key(record_key: u64) -> Option { fn release_record_key(record_key: u64) { let func = move || { if record_key_is_rdev_layout(record_key) { - rdev_key_down_or_up(RdevKey::Unknown((record_key - KEY_RDEV_START) as _), false); + simulate_(&EventType::KeyRelease(RdevKey::Unknown( + (record_key - KEY_RDEV_START) as _, + ))); } else if let Some(key) = record_key_to_key(record_key) { ENIGO.lock().unwrap().key_up(key); log::debug!("Fixed {:?} timeout", key); } }; - + #[cfg(target_os = "macos")] QUEUE.exec_async(func); #[cfg(not(target_os = "macos"))] @@ -687,7 +695,27 @@ pub fn handle_key(evt: &KeyEvent) { handle_key_(evt); } -fn sim_rdev_rawkey(code: u32, down_or_up: bool) { +#[inline] +fn reset_input() { + unsafe { + VIRTUAL_INPUT = VirtualInput::new( + CGEventSourceStateID::Private, + CGEventTapLocation::AnnotatedSession, + ) + .ok(); + } +} + +#[cfg(target_os = "macos")] +pub fn reset_input_ondisconn() { + if !*IS_SERVER { + QUEUE.exec_async(reset_input); + } else { + reset_input(); + } +} + +fn sim_rdev_rawkey(code: u32, keydown: bool) { #[cfg(target_os = "windows")] let rawkey = RawKey::ScanCode(code); #[cfg(target_os = "linux")] @@ -698,22 +726,34 @@ fn sim_rdev_rawkey(code: u32, down_or_up: bool) { #[cfg(target_os = "macos")] let rawkey = RawKey::MacVirtualKeycode(code); - rdev_key_down_or_up(RdevKey::RawKey(rawkey), down_or_up); + let event_type = if keydown { + EventType::KeyPress(RdevKey::RawKey(rawkey)) + } else { + EventType::KeyRelease(RdevKey::RawKey(rawkey)) + }; + simulate_(&event_type); } -fn rdev_key_down_or_up(key: RdevKey, down_or_up: bool) { - let event_type = match down_or_up { - true => EventType::KeyPress(key), - false => EventType::KeyRelease(key), - }; - match simulate(&event_type) { +#[cfg(target_os = "macos")] +#[inline] +fn simulate_(event_type: &EventType) { + unsafe { + if let Some(virtual_input) = &VIRTUAL_INPUT { + let _ = virtual_input.simulate(&event_type); + std::thread::sleep(Duration::from_millis(20)); + } + } +} + +#[cfg(not(target_os = "macos"))] +#[inline] +fn simulate_(event_type: &EventType) { + match rdev::simulate(&event_type) { Ok(()) => (), Err(_simulate_error) => { log::error!("Could not send {:?}", &event_type); } } - #[cfg(target_os = "macos")] - std::thread::sleep(Duration::from_millis(20)); } fn is_modifier_in_key_event(control_key: ControlKey, key_event: &KeyEvent) -> bool { From 0b9b71e4fca5f0397f0d5c9e964b55da23ec99c5 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 29 Dec 2022 18:16:06 +0800 Subject: [PATCH 3/4] move sleep from main thread Signed-off-by: fufesou --- src/server/input_service.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 709513867..dc126be5f 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -223,8 +223,7 @@ lazy_static::lazy_static! { static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned()); } -// virtual_input must be used in main thread. -// No need to wrap mutex. +static mut VIRTUAL_INPUT_MTX: Mutex<()> = Mutex::new(()); static mut VIRTUAL_INPUT: Option = None; // First call set_uinput() will create keyboard and mouse clients. @@ -687,17 +686,21 @@ 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)); return; } #[cfg(windows)] crate::portable_service::client::handle_key(evt); #[cfg(not(windows))] handle_key_(evt); + #[cfg(target_os = "macos")] + std::thread::sleep(Duration::from_millis(20)); } #[inline] fn reset_input() { unsafe { + let _lock = VIRTUAL_INPUT_MTX.lock(); VIRTUAL_INPUT = VirtualInput::new( CGEventSourceStateID::Private, CGEventTapLocation::AnnotatedSession, @@ -738,9 +741,9 @@ fn sim_rdev_rawkey(code: u32, keydown: bool) { #[inline] 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); - std::thread::sleep(Duration::from_millis(20)); } } } From fb5cfabf5184c818521fc021689c384d6b77dbcf Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 29 Dec 2022 19:10:25 +0800 Subject: [PATCH 4/4] fix build Signed-off-by: fufesou --- src/server/input_service.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/input_service.rs b/src/server/input_service.rs index dc126be5f..bd2ad9a16 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -223,7 +223,9 @@ lazy_static::lazy_static! { static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned()); } +#[cfg(target_os = "macos")] static mut VIRTUAL_INPUT_MTX: Mutex<()> = Mutex::new(()); +#[cfg(target_os = "macos")] static mut VIRTUAL_INPUT: Option = None; // First call set_uinput() will create keyboard and mouse clients. @@ -697,6 +699,7 @@ pub fn handle_key(evt: &KeyEvent) { std::thread::sleep(Duration::from_millis(20)); } +#[cfg(target_os = "macos")] #[inline] fn reset_input() { unsafe {