diff --git a/Cargo.lock b/Cargo.lock index 55949fe8b..48bfd0e20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1514,6 +1514,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "epoll" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20df693c700404f7e19d4d6fae6b15215d2913c27955d2b9d6f2c0f537511cd0" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "err-derive" version = "0.3.1" @@ -2574,6 +2584,26 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "inotify" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf888f9575c290197b2c948dc9e9ff10bd1a39ad1ea8585f734585fa6b9d3f9" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -4073,15 +4103,19 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/asur4s/rdev#bff57a29e3f14d032ab7441b2d6cf029df8adaca" +source = "git+https://github.com/asur4s/rdev#ea223720532f32652dab803db43f9ce437f2b019" dependencies = [ "cocoa", "core-foundation 0.9.3", "core-foundation-sys 0.8.3", "core-graphics 0.22.3", "enum-map", + "epoll", + "inotify", "lazy_static", "libc", + "strum 0.24.1", + "strum_macros 0.24.3", "widestring 1.0.2", "winapi 0.3.9", "x11 2.20.0", @@ -4899,6 +4933,12 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + [[package]] name = "strum_macros" version = "0.18.0" @@ -4911,6 +4951,19 @@ dependencies = [ "syn", ] +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "1.0.99" @@ -4993,8 +5046,8 @@ checksum = "0f3ecc17269a19353b3558b313bba738b25d82993e30d62a18406a24aba4649b" dependencies = [ "heck 0.3.3", "pkg-config", - "strum", - "strum_macros", + "strum 0.18.0", + "strum_macros 0.18.0", "thiserror", "toml", "version-compare 0.0.10", diff --git a/src/client.rs b/src/client.rs index e65c16f33..df18d9b0a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -48,7 +48,7 @@ pub use super::lang::*; pub mod file_trait; pub mod helper; pub mod io_loop; - +use crate::ui_session_interface::global_save_keyboard_mode; pub static SERVER_KEYBOARD_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_FILE_TRANSFER_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_CLIPBOARD_ENABLED: AtomicBool = AtomicBool::new(true); @@ -1294,6 +1294,9 @@ impl LoginConfigHandler { if !pi.version.is_empty() { self.version = hbb_common::get_version_number(&pi.version); } + if hbb_common::get_version_number(&pi.version) < hbb_common::get_version_number("1.2.0") { + global_save_keyboard_mode("legacy".to_owned()); + } self.features = pi.features.clone().into_option(); let serde = PeerInfoSerde { username: pi.username.clone(), diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 9e2a09545..f5cc0499d 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -6,6 +6,7 @@ use crate::client::{ load_config, send_mouse, start_video_audio_threads, FileManager, Key, LoginConfigHandler, QualityStatus, KEY_MAP, SERVER_KEYBOARD_ENABLED, }; +use crate::common::IS_X11; use crate::{client::Data, client::Interface}; use async_trait::async_trait; use hbb_common::config::{Config, LocalConfig, PeerConfig}; @@ -13,7 +14,7 @@ use hbb_common::rendezvous_proto::ConnType; use hbb_common::tokio::{self, sync::mpsc}; #[cfg(not(any(target_os = "android", target_os = "ios")))] use rdev::Keyboard as RdevKeyboard; -use rdev::{Event, EventType::*, Key as RdevKey, KeyboardState}; +use rdev::{Event, EventType, EventType::*, Key as RdevKey, KeyboardState}; use hbb_common::{allow_err, message_proto::*}; use hbb_common::{fs, get_version_number, log, Stream}; @@ -25,7 +26,9 @@ use std::sync::{Arc, Mutex, RwLock}; /// IS_IN KEYBOARD_HOOKED sciter only pub static IS_IN: AtomicBool = AtomicBool::new(false); pub static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(true); - +pub static HOTKEY_HOOK_ENABLED: AtomicBool = AtomicBool::new(false); +#[cfg(target_os = "linux")] +use rdev::IS_GRAB; #[cfg(windows)] static mut IS_ALT_GR: bool = false; @@ -38,6 +41,21 @@ lazy_static::lazy_static! { static ref KEYBOARD: Arc> = Arc::new(Mutex::new(RdevKeyboard::new().unwrap())); } +lazy_static::lazy_static! { + static ref MUTEX_SPECIAL_KEYS: Mutex> = { + let mut m = HashMap::new(); + m.insert(RdevKey::ShiftLeft, false); + m.insert(RdevKey::ShiftRight, false); + m.insert(RdevKey::ControlLeft, false); + m.insert(RdevKey::ControlRight, false); + m.insert(RdevKey::Alt, false); + m.insert(RdevKey::AltGr, false); + m.insert(RdevKey::MetaLeft, false); + m.insert(RdevKey::MetaRight, false); + Mutex::new(m) + }; +} + #[derive(Clone, Default)] pub struct Session { pub id: String, @@ -63,13 +81,11 @@ impl Session { } pub fn get_keyboard_mode(&self) -> String { - return std::env::var("KEYBOARD_MODE") - .unwrap_or(String::from("legacy")) - .to_lowercase(); + global_get_keyboard_mode() } pub fn save_keyboard_mode(&self, value: String) { - std::env::set_var("KEYBOARD_MODE", value); + global_save_keyboard_mode(value); } pub fn save_view_style(&mut self, value: String) { @@ -146,7 +162,12 @@ impl Session { let decoder = scrap::codec::Decoder::video_codec_state(&self.id); let mut h264 = decoder.score_h264 > 0; let mut h265 = decoder.score_h265 > 0; - let (encoding_264, encoding_265) = self.lc.read().unwrap().supported_encoding.unwrap_or_default(); + let (encoding_264, encoding_265) = self + .lc + .read() + .unwrap() + .supported_encoding + .unwrap_or_default(); h264 = h264 && encoding_264; h265 = h265 && encoding_265; return (h264, h265); @@ -622,6 +643,7 @@ impl Session { RdevKey::Quote => '\'', RdevKey::LeftBracket => '[', RdevKey::RightBracket => ']', + RdevKey::Slash => '/', RdevKey::BackSlash => '\\', RdevKey::Minus => '-', RdevKey::Equal => '=', @@ -746,12 +768,24 @@ impl Session { } pub fn enter(&self) { + HOTKEY_HOOK_ENABLED.store(true, Ordering::SeqCst); + #[cfg(target_os = "linux")] + unsafe { + IS_GRAB.store(true, Ordering::SeqCst); + } + #[cfg(windows)] crate::platform::windows::stop_system_key_propagate(true); IS_IN.store(true, Ordering::SeqCst); } pub fn leave(&self) { + HOTKEY_HOOK_ENABLED.store(false, Ordering::SeqCst); + #[cfg(target_os = "linux")] + unsafe { + IS_GRAB.store(false, Ordering::SeqCst); + } + for key in TO_RELEASE.lock().unwrap().iter() { self.map_keyboard_mode(false, *key, None) } @@ -865,27 +899,19 @@ impl Session { ControlKey::Numpad9 => ControlKey::PageUp, _ => key, } - }else{ + } else { key }; key_event.set_control_key(key.clone()); } Key::_Raw(raw) => { - if raw > 'z' as u32 || raw < 'a' as u32 { - key_event.set_unicode(raw); - // TODO - // if down_or_up == 0 { - // // ignore up, avoiding trigger twice - // return; - // } - // down_or_up = 1; // if press, turn into down for avoiding trigger twice on server side - } else { - // to make ctrl+c works on windows - key_event.set_chr(raw); - } + key_event.set_chr(raw); } } + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let (alt, ctrl, shift, command) = get_all_hotkey_state(alt, ctrl, shift, command); + self.legacy_modifiers(&mut key_event, alt, ctrl, shift, command); if v == 1 { key_event.down = true; @@ -915,6 +941,9 @@ impl Session { } } + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let (alt, ctrl, shift, command) = get_all_hotkey_state(alt, ctrl, shift, command); + send_mouse(mask, x, y, alt, ctrl, shift, command, self); // on macos, ctrl + left button down = right button down, up won't emit, so we need to // emit up myself if peer is not macos @@ -1164,8 +1193,11 @@ impl Interface for Session { crate::platform::windows::add_recent_document(&path); } } + #[cfg(not(any(target_os = "android", target_os = "ios")))] self.start_keyboard_hook(); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + self.start_hotkey_grab(); } async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream) { @@ -1208,6 +1240,93 @@ impl Interface for Session { #[cfg(not(any(target_os = "android", target_os = "ios")))] impl Session { + fn handle_hot_key_event(&self, event: Event) { + // keyboard long press + match event.event_type { + EventType::KeyPress(k) => { + if MUTEX_SPECIAL_KEYS.lock().unwrap().contains_key(&k) { + if *MUTEX_SPECIAL_KEYS.lock().unwrap().get(&k).unwrap() { + return; + } + MUTEX_SPECIAL_KEYS.lock().unwrap().insert(k, true); + } + } + EventType::KeyRelease(k) => { + if MUTEX_SPECIAL_KEYS.lock().unwrap().contains_key(&k) { + MUTEX_SPECIAL_KEYS.lock().unwrap().insert(k, false); + } + } + _ => return, + }; + + // keyboard short press + match event.event_type { + EventType::KeyPress(key) => { + self.key_down_or_up(true, key, event); + } + EventType::KeyRelease(key) => { + self.key_down_or_up(false, key, event); + } + _ => {} + } + } + + fn start_hotkey_grab(&self) { + #[cfg(target_os = "linux")] + if !*IS_X11.lock().unwrap() { + return; + } + if self.is_port_forward() || self.is_file_transfer() { + return; + } + let me = self.clone(); + + log::info!("hotkey grabing"); + std::thread::spawn(move || { + std::env::set_var("KEYBOARD_ONLY", "y"); + + let func = move |event: Event| { + #[cfg(any(target_os = "windows", target_os = "macos"))] + if !HOTKEY_HOOK_ENABLED.load(Ordering::SeqCst) { + return Some(event); + }; + match event.event_type { + EventType::KeyPress(_key) | EventType::KeyRelease(_key) => { + #[cfg(any(target_os = "windows", target_os = "macos"))] + if MUTEX_SPECIAL_KEYS.lock().unwrap().contains_key(&_key) { + me.handle_hot_key_event(event); + return None; + } else { + return Some(event); + } + + #[cfg(target_os = "linux")] + me.handle_hot_key_event(event); + + None + } + _ => Some(event), + } + }; + + #[cfg(target_os = "linux")] + { + use rdev::GRABED_KEYS; + GRABED_KEYS.lock().unwrap().insert(RdevKey::ShiftLeft); + GRABED_KEYS.lock().unwrap().insert(RdevKey::ShiftRight); + GRABED_KEYS.lock().unwrap().insert(RdevKey::ControlLeft); + GRABED_KEYS.lock().unwrap().insert(RdevKey::ControlRight); + GRABED_KEYS.lock().unwrap().insert(RdevKey::Alt); + GRABED_KEYS.lock().unwrap().insert(RdevKey::AltGr); + GRABED_KEYS.lock().unwrap().insert(RdevKey::MetaLeft); + GRABED_KEYS.lock().unwrap().insert(RdevKey::MetaRight); + } + if let Err(error) = rdev::grab(func) { + log::error!("Error: {:?}", error) + } + }); + } + fn start_keyboard_hook(&self) { if self.is_port_forward() || self.is_file_transfer() { return; @@ -1215,6 +1334,11 @@ impl Session { if !KEYBOARD_HOOKED.load(Ordering::SeqCst) { return; } + // rdev::grab and rdev::listen use the same api on macOS + #[cfg(target_os = "macos")] + if HOTKEY_HOOK_ENABLED.load(Ordering::SeqCst) { + return; + } log::info!("keyboard hooked"); let me = self.clone(); #[cfg(windows)] @@ -1222,20 +1346,6 @@ impl Session { std::thread::spawn(move || { // This will block. std::env::set_var("KEYBOARD_ONLY", "y"); - lazy_static::lazy_static! { - static ref MUTEX_SPECIAL_KEYS: Mutex> = { - let mut m = HashMap::new(); - m.insert(RdevKey::ShiftLeft, false); - m.insert(RdevKey::ShiftRight, false); - m.insert(RdevKey::ControlLeft, false); - m.insert(RdevKey::ControlRight, false); - m.insert(RdevKey::Alt, false); - m.insert(RdevKey::AltGr, false); - m.insert(RdevKey::MetaLeft, false); - m.insert(RdevKey::MetaRight, false); - Mutex::new(m) - }; - } let func = move |evt: Event| { if !IS_IN.load(Ordering::SeqCst) || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst) @@ -1421,3 +1531,34 @@ async fn send_note(url: String, id: String, conn_id: i32, note: String) { let body = serde_json::json!({ "id": id, "Id": conn_id, "note": note }); allow_err!(crate::post_request(url, body.to_string(), "").await); } + +fn get_hotkey_state(key: RdevKey) -> bool { + *MUTEX_SPECIAL_KEYS.lock().unwrap().get(&key).unwrap() +} + +fn get_all_hotkey_state( + alt: bool, + ctrl: bool, + shift: bool, + command: bool, +) -> (bool, bool, bool, bool) { + let ctrl = + get_hotkey_state(RdevKey::ControlLeft) || get_hotkey_state(RdevKey::ControlRight) || ctrl; + let shift = + get_hotkey_state(RdevKey::ShiftLeft) || get_hotkey_state(RdevKey::ShiftRight) || shift; + let command = + get_hotkey_state(RdevKey::MetaLeft) || get_hotkey_state(RdevKey::MetaRight) || command; + let alt = get_hotkey_state(RdevKey::Alt) || get_hotkey_state(RdevKey::AltGr) || alt; + + (alt, ctrl, shift, command) +} + +pub fn global_get_keyboard_mode() -> String { + return std::env::var("KEYBOARD_MODE") + .unwrap_or(String::from("map")) + .to_lowercase(); +} + +pub fn global_save_keyboard_mode(value: String) { + std::env::set_var("KEYBOARD_MODE", value); +}