From 87ee35953633ec770f65fb25ddbea8624eb3043a Mon Sep 17 00:00:00 2001 From: Chieh Wang Date: Fri, 7 Oct 2022 13:48:46 +0800 Subject: [PATCH] Feat: Grab hot key --- Cargo.lock | 83 ++++++++++++++++- src/ui_session_interface.rs | 176 ++++++++++++++++++++++++++++++++---- 2 files changed, 238 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55949fe8b..4348e05cb 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" @@ -1569,6 +1579,29 @@ dependencies = [ "nix 0.23.1", ] +[[package]] +name = "evdev-rs" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46504075975d14f0463e5a41efa06820c94d4c04fecd01f70b95365d60de1caf" +dependencies = [ + "bitflags", + "evdev-sys", + "libc", + "log", +] + +[[package]] +name = "evdev-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14ead42b547b15d47089c1243d907bcf0eb94e457046d3b315a26ac9c9e9ea6d" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -2574,6 +2607,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 +4126,20 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/asur4s/rdev#bff57a29e3f14d032ab7441b2d6cf029df8adaca" +source = "git+https://github.com/asur4s/rdev#71a2b9e014b5aaeb85d7bb4a5b7562e3a68cc509" dependencies = [ "cocoa", "core-foundation 0.9.3", "core-foundation-sys 0.8.3", "core-graphics 0.22.3", "enum-map", + "epoll", + "evdev-rs", + "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 +4957,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 +4975,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 +5070,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/ui_session_interface.rs b/src/ui_session_interface.rs index 9e2a09545..8288adbe0 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -13,7 +13,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 +25,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 +40,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, @@ -146,7 +163,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 +644,7 @@ impl Session { RdevKey::Quote => '\'', RdevKey::LeftBracket => '[', RdevKey::RightBracket => ']', + RdevKey::Slash => '/', RdevKey::BackSlash => '\\', RdevKey::Minus => '-', RdevKey::Equal => '=', @@ -746,12 +769,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,7 +900,7 @@ impl Session { ControlKey::Numpad9 => ControlKey::PageUp, _ => key, } - }else{ + } else { key }; key_event.set_control_key(key.clone()); @@ -886,6 +921,16 @@ impl Session { } } + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let ctrl = + get_hotkey_state(RdevKey::ControlLeft) || get_hotkey_state(RdevKey::ControlRight); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let shift = get_hotkey_state(RdevKey::ShiftLeft) || get_hotkey_state(RdevKey::ShiftRight); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let command = get_hotkey_state(RdevKey::MetaLeft) || get_hotkey_state(RdevKey::MetaRight); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let alt = get_hotkey_state(RdevKey::Alt) || get_hotkey_state(RdevKey::AltGr); + self.legacy_modifiers(&mut key_event, alt, ctrl, shift, command); if v == 1 { key_event.down = true; @@ -915,6 +960,16 @@ impl Session { } } + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let ctrl = + get_hotkey_state(RdevKey::ControlLeft) || get_hotkey_state(RdevKey::ControlRight); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let shift = get_hotkey_state(RdevKey::ShiftLeft) || get_hotkey_state(RdevKey::ShiftRight); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let command = get_hotkey_state(RdevKey::MetaLeft) || get_hotkey_state(RdevKey::MetaRight); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let alt = get_hotkey_state(RdevKey::Alt) || get_hotkey_state(RdevKey::AltGr); + 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 +1219,11 @@ impl Interface for Session { crate::platform::windows::add_recent_document(&path); } } + // rdev::grab and rdev::listen use the same api on macOS #[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 +1266,93 @@ impl Interface for Session { #[cfg(not(any(target_os = "android", target_os = "ios")))] impl Session { + fn send_hotkey(&self, key: RdevKey, is_press: bool) { + log::info!("{:?} {:?}", key, is_press); + } + + 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.send_hotkey(key, true); + self.key_down_or_up(true, key, event); + } + EventType::KeyRelease(key) => { + self.send_hotkey(key, false); + self.key_down_or_up(false, key, event); + } + _ => {} + } + } + + fn start_hotkey_grab(&self) { + 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; + } + + #[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 +1360,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 +1372,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 +1557,7 @@ 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() +}