#[cfg(feature = "flutter")] use crate::flutter::{CUR_SESSION_ID, SESSIONS}; #[cfg(target_os = "windows")] use crate::platform::windows::{get_char_from_vk, get_unicode_from_vk}; #[cfg(not(any(feature = "flutter", feature = "cli")))] use crate::ui::CUR_SESSION; #[cfg(not(any(target_os = "android", target_os = "ios")))] use crate::{client::get_key_state, common::GrabState}; #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::log; use hbb_common::message_proto::*; #[cfg(any(target_os = "windows", target_os = "macos"))] use rdev::KeyCode; use rdev::{Event, EventType, Key}; #[cfg(any(target_os = "windows", target_os = "macos"))] use std::sync::atomic::{AtomicBool, Ordering}; #[cfg(not(any(target_os = "android", target_os = "ios")))] use std::time::SystemTime; use std::{ collections::{HashMap, HashSet}, sync::{Arc, Mutex}, }; #[cfg(windows)] static mut IS_ALT_GR: bool = false; #[allow(dead_code)] const OS_LOWER_WINDOWS: &str = "windows"; #[allow(dead_code)] const OS_LOWER_LINUX: &str = "linux"; #[allow(dead_code)] const OS_LOWER_MACOS: &str = "macos"; #[cfg(any(target_os = "windows", target_os = "macos"))] static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false); lazy_static::lazy_static! { static ref TO_RELEASE: Arc>> = Arc::new(Mutex::new(HashSet::::new())); static ref MODIFIERS_STATE: Mutex> = { let mut m = HashMap::new(); m.insert(Key::ShiftLeft, false); m.insert(Key::ShiftRight, false); m.insert(Key::ControlLeft, false); m.insert(Key::ControlRight, false); m.insert(Key::Alt, false); m.insert(Key::AltGr, false); m.insert(Key::MetaLeft, false); m.insert(Key::MetaRight, false); Mutex::new(m) }; } pub mod client { use super::*; pub fn get_keyboard_mode() -> String { #[cfg(not(any(feature = "flutter", feature = "cli")))] if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() { return session.get_keyboard_mode(); } #[cfg(feature = "flutter")] if let Some(session) = SESSIONS .read() .unwrap() .get(&*CUR_SESSION_ID.read().unwrap()) { return session.get_keyboard_mode(); } "legacy".to_string() } pub fn start_grab_loop() { super::start_grab_loop(); } #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn change_grab_status(state: GrabState) { match state { GrabState::Ready => {} GrabState::Run => { #[cfg(windows)] update_grab_get_key_name(); #[cfg(any(target_os = "windows", target_os = "macos"))] KEYBOARD_HOOKED.swap(true, Ordering::SeqCst); #[cfg(target_os = "linux")] rdev::enable_grab(); } GrabState::Wait => { release_remote_keys(); #[cfg(any(target_os = "windows", target_os = "macos"))] KEYBOARD_HOOKED.swap(false, Ordering::SeqCst); #[cfg(target_os = "linux")] rdev::disable_grab(); } GrabState::Exit => { #[cfg(target_os = "linux")] rdev::exit_grab_listen(); } } } pub fn process_event(event: &Event, lock_modes: Option) -> KeyboardMode { let keyboard_mode = get_keyboard_mode_enum(); if is_long_press(&event) { return keyboard_mode; } for key_event in event_to_key_events(&event, keyboard_mode, lock_modes) { send_key_event(&key_event); } keyboard_mode } pub fn get_modifiers_state( alt: bool, ctrl: bool, shift: bool, command: bool, ) -> (bool, bool, bool, bool) { let modifiers_lock = MODIFIERS_STATE.lock().unwrap(); let ctrl = *modifiers_lock.get(&Key::ControlLeft).unwrap() || *modifiers_lock.get(&Key::ControlRight).unwrap() || ctrl; let shift = *modifiers_lock.get(&Key::ShiftLeft).unwrap() || *modifiers_lock.get(&Key::ShiftRight).unwrap() || shift; let command = *modifiers_lock.get(&Key::MetaLeft).unwrap() || *modifiers_lock.get(&Key::MetaRight).unwrap() || command; let alt = *modifiers_lock.get(&Key::Alt).unwrap() || *modifiers_lock.get(&Key::AltGr).unwrap() || alt; (alt, ctrl, shift, command) } pub fn legacy_modifiers( key_event: &mut KeyEvent, alt: bool, ctrl: bool, shift: bool, command: bool, ) { if alt && !crate::is_control_key(&key_event, &ControlKey::Alt) && !crate::is_control_key(&key_event, &ControlKey::RAlt) { key_event.modifiers.push(ControlKey::Alt.into()); } if shift && !crate::is_control_key(&key_event, &ControlKey::Shift) && !crate::is_control_key(&key_event, &ControlKey::RShift) { key_event.modifiers.push(ControlKey::Shift.into()); } if ctrl && !crate::is_control_key(&key_event, &ControlKey::Control) && !crate::is_control_key(&key_event, &ControlKey::RControl) { key_event.modifiers.push(ControlKey::Control.into()); } if command && !crate::is_control_key(&key_event, &ControlKey::Meta) && !crate::is_control_key(&key_event, &ControlKey::RWin) { key_event.modifiers.push(ControlKey::Meta.into()); } } pub fn event_lock_screen() -> KeyEvent { let mut key_event = KeyEvent::new(); key_event.set_control_key(ControlKey::LockScreen); key_event.down = true; key_event.mode = KeyboardMode::Legacy.into(); key_event } #[inline] #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn lock_screen() { send_key_event(&event_lock_screen()); } pub fn event_ctrl_alt_del() -> KeyEvent { let mut key_event = KeyEvent::new(); if get_peer_platform() == "Windows" { key_event.set_control_key(ControlKey::CtrlAltDel); key_event.down = true; } else { key_event.set_control_key(ControlKey::Delete); legacy_modifiers(&mut key_event, true, true, false, false); key_event.press = true; } key_event.mode = KeyboardMode::Legacy.into(); key_event } #[inline] #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn ctrl_alt_del() { send_key_event(&event_ctrl_alt_del()); } } #[cfg(windows)] pub fn update_grab_get_key_name() { match get_keyboard_mode_enum() { KeyboardMode::Map => rdev::set_get_key_unicode(false), KeyboardMode::Translate => rdev::set_get_key_unicode(true), _ => {} }; } #[cfg(target_os = "windows")] static mut IS_0X021D_DOWN: bool = false; #[cfg(target_os = "macos")] static mut IS_LEFT_OPTION_DOWN: bool = false; pub fn start_grab_loop() { std::env::set_var("KEYBOARD_ONLY", "y"); #[cfg(any(target_os = "windows", target_os = "macos"))] std::thread::spawn(move || { let try_handle_keyboard = move |event: Event, key: Key, is_press: bool| -> Option { // fix #2211:CAPS LOCK don't work if key == Key::CapsLock || key == Key::NumLock { return Some(event); } let mut _keyboard_mode = KeyboardMode::Map; let _scan_code = event.position_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 { None } else { Some(event) } } else { Some(event) }; #[cfg(target_os = "windows")] match _scan_code { 0x1D | 0x021D => rdev::set_modifier(Key::ControlLeft, is_press), 0xE01D => rdev::set_modifier(Key::ControlRight, is_press), 0x2A => rdev::set_modifier(Key::ShiftLeft, is_press), 0x36 => rdev::set_modifier(Key::ShiftRight, is_press), 0x38 => rdev::set_modifier(Key::Alt, is_press), // Right Alt 0xE038 => rdev::set_modifier(Key::AltGr, is_press), 0xE05B => rdev::set_modifier(Key::MetaLeft, is_press), 0xE05C => rdev::set_modifier(Key::MetaRight, is_press), _ => {} } #[cfg(target_os = "windows")] unsafe { // AltGr if _scan_code == 0x021D { IS_0X021D_DOWN = is_press; } } #[cfg(target_os = "macos")] unsafe { if _code == rdev::kVK_Option { IS_LEFT_OPTION_DOWN = is_press; } } return res; }; let func = move |event: Event| match event.event_type { EventType::KeyPress(key) => try_handle_keyboard(event, key, true), EventType::KeyRelease(key) => try_handle_keyboard(event, key, false), _ => Some(event), }; #[cfg(target_os = "macos")] rdev::set_is_main_thread(false); #[cfg(target_os = "windows")] rdev::set_event_popup(false); if let Err(error) = rdev::grab(func) { log::error!("rdev Error: {:?}", error) } }); #[cfg(target_os = "linux")] if let Err(err) = rdev::start_grab_listen(move |event: Event| match event.event_type { EventType::KeyPress(key) | EventType::KeyRelease(key) => { if let Key::Unknown(keycode) = key { log::error!("rdev get unknown key, keycode is : {:?}", keycode); } else { client::process_event(&event, None); } None } _ => Some(event), }) { log::error!("Failed to init rdev grab thread: {:?}", err); }; } pub fn is_long_press(event: &Event) -> bool { let keys = MODIFIERS_STATE.lock().unwrap(); match event.event_type { EventType::KeyPress(k) => { if let Some(&state) = keys.get(&k) { if state == true { return true; } } } _ => {} }; return false; } #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn release_remote_keys() { // todo!: client quit suddenly, how to release keys? let to_release = TO_RELEASE.lock().unwrap().clone(); TO_RELEASE.lock().unwrap().clear(); for key in to_release { let event_type = EventType::KeyRelease(key); let event = event_type_to_event(event_type); // to-do: BUG // Release events should be sent to the corresponding sessions, instead of current session. client::process_event(&event, None); } } pub fn get_keyboard_mode_enum() -> KeyboardMode { match client::get_keyboard_mode().as_str() { "map" => KeyboardMode::Map, "translate" => KeyboardMode::Translate, "legacy" => KeyboardMode::Legacy, _ => { // Set "map" as default mode if version >= 1.2.0. if crate::is_peer_version_ge("1.2.0") { KeyboardMode::Map } else { KeyboardMode::Legacy } } } } #[inline] #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn is_modifier(key: &rdev::Key) -> bool { matches!( key, Key::ShiftLeft | Key::ShiftRight | Key::ControlLeft | Key::ControlRight | Key::MetaLeft | Key::MetaRight | Key::Alt | Key::AltGr ) } #[inline] #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn is_numpad_rdev_key(key: &rdev::Key) -> bool { matches!( 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 ) } #[inline] #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn is_letter_rdev_key(key: &rdev::Key) -> bool { matches!( key, Key::KeyA | Key::KeyB | Key::KeyC | Key::KeyD | Key::KeyE | Key::KeyF | Key::KeyG | Key::KeyH | Key::KeyI | Key::KeyJ | Key::KeyK | Key::KeyL | Key::KeyM | Key::KeyN | Key::KeyO | Key::KeyP | Key::KeyQ | Key::KeyR | Key::KeyS | Key::KeyT | Key::KeyU | Key::KeyV | Key::KeyW | Key::KeyX | Key::KeyY | Key::KeyZ ) } #[inline] #[cfg(not(any(target_os = "android", target_os = "ios")))] fn is_numpad_key(event: &Event) -> bool { matches!(event.event_type, EventType::KeyPress(key) | EventType::KeyRelease(key) if is_numpad_rdev_key(&key)) } #[inline] #[cfg(not(any(target_os = "android", target_os = "ios")))] fn is_letter_key(event: &Event) -> bool { matches!(event.event_type, EventType::KeyPress(key) | EventType::KeyRelease(key) if is_letter_rdev_key(&key)) } #[cfg(not(any(target_os = "android", target_os = "ios")))] fn parse_add_lock_modes_modifiers( key_event: &mut KeyEvent, lock_modes: i32, is_numpad_key: bool, is_letter_key: bool, ) { const CAPS_LOCK: i32 = 1; const NUM_LOCK: i32 = 2; // const SCROLL_LOCK: i32 = 3; if is_letter_key && (lock_modes & (1 << CAPS_LOCK) != 0) { key_event.modifiers.push(ControlKey::CapsLock.into()); } if is_numpad_key && lock_modes & (1 << NUM_LOCK) != 0 { key_event.modifiers.push(ControlKey::NumLock.into()); } // if lock_modes & (1 << SCROLL_LOCK) != 0 { // key_event.modifiers.push(ControlKey::ScrollLock.into()); // } } #[cfg(not(any(target_os = "android", target_os = "ios")))] fn add_lock_modes_modifiers(key_event: &mut KeyEvent, is_numpad_key: bool, is_letter_key: bool) { if is_letter_key && get_key_state(enigo::Key::CapsLock) { key_event.modifiers.push(ControlKey::CapsLock.into()); } if is_numpad_key && get_key_state(enigo::Key::NumLock) { key_event.modifiers.push(ControlKey::NumLock.into()); } } #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn convert_numpad_keys(key: Key) -> Key { if get_key_state(enigo::Key::NumLock) { return key; } match key { Key::Kp0 => Key::Insert, Key::KpDecimal => Key::Delete, Key::Kp1 => Key::End, Key::Kp2 => Key::DownArrow, Key::Kp3 => Key::PageDown, Key::Kp4 => Key::LeftArrow, Key::Kp5 => Key::Clear, Key::Kp6 => Key::RightArrow, Key::Kp7 => Key::Home, Key::Kp8 => Key::UpArrow, Key::Kp9 => Key::PageUp, _ => key, } } fn update_modifiers_state(event: &Event) { // for mouse let mut keys = MODIFIERS_STATE.lock().unwrap(); match event.event_type { EventType::KeyPress(k) => { if keys.contains_key(&k) { keys.insert(k, true); } } EventType::KeyRelease(k) => { if keys.contains_key(&k) { keys.insert(k, false); } } _ => {} }; } pub fn event_to_key_events( event: &Event, keyboard_mode: KeyboardMode, _lock_modes: Option, ) -> Vec { let mut key_event = KeyEvent::new(); update_modifiers_state(event); match event.event_type { EventType::KeyPress(key) => { TO_RELEASE.lock().unwrap().insert(key); } EventType::KeyRelease(key) => { TO_RELEASE.lock().unwrap().remove(&key); } _ => {} } let mut peer = get_peer_platform().to_lowercase(); peer.retain(|c| !c.is_whitespace()); key_event.mode = keyboard_mode.into(); #[cfg(not(any(target_os = "android", target_os = "ios")))] let mut key_events; #[cfg(any(target_os = "android", target_os = "ios"))] let key_events; key_events = match keyboard_mode { KeyboardMode::Map => match map_keyboard_mode(peer.as_str(), event, key_event) { Some(event) => [event].to_vec(), None => Vec::new(), }, KeyboardMode::Translate => translate_keyboard_mode(peer.as_str(), event, key_event), _ => { #[cfg(not(any(target_os = "android", target_os = "ios")))] { legacy_keyboard_mode(event, key_event) } #[cfg(any(target_os = "android", target_os = "ios"))] { Vec::new() } } }; #[cfg(not(any(target_os = "android", target_os = "ios")))] let is_numpad_key = is_numpad_key(&event); #[cfg(not(any(target_os = "android", target_os = "ios")))] if keyboard_mode != KeyboardMode::Translate || is_numpad_key { let is_letter_key = is_letter_key(&event); for key_event in &mut key_events { if let Some(lock_modes) = _lock_modes { parse_add_lock_modes_modifiers(key_event, lock_modes, is_numpad_key, is_letter_key); } else { add_lock_modes_modifiers(key_event, is_numpad_key, is_letter_key); } } } key_events } #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn event_type_to_event(event_type: EventType) -> Event { Event { event_type, time: SystemTime::now(), unicode: None, platform_code: 0, position_code: 0, } } pub fn send_key_event(key_event: &KeyEvent) { #[cfg(not(any(feature = "flutter", feature = "cli")))] if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() { session.send_key_event(key_event); } #[cfg(feature = "flutter")] if let Some(session) = SESSIONS .read() .unwrap() .get(&*CUR_SESSION_ID.read().unwrap()) { session.send_key_event(key_event); } } pub fn get_peer_platform() -> String { #[cfg(not(any(feature = "flutter", feature = "cli")))] if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() { return session.peer_platform(); } #[cfg(feature = "flutter")] if let Some(session) = SESSIONS .read() .unwrap() .get(&*CUR_SESSION_ID.read().unwrap()) { return session.peer_platform(); } "Windows".to_string() } #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec { let mut events = Vec::new(); // legacy mode(0): Generate characters locally, look for keycode on other side. let (mut key, down_or_up) = match event.event_type { EventType::KeyPress(key) => (key, true), EventType::KeyRelease(key) => (key, false), _ => { return events; } }; let peer = get_peer_platform(); let is_win = peer == "Windows"; if is_win { key = convert_numpad_keys(key); } let alt = get_key_state(enigo::Key::Alt); #[cfg(windows)] let ctrl = { let mut tmp = get_key_state(enigo::Key::Control) || get_key_state(enigo::Key::RightControl); unsafe { if IS_ALT_GR { if alt || key == Key::AltGr { if tmp { tmp = false; } } else { IS_ALT_GR = false; } } } tmp }; #[cfg(not(windows))] let ctrl = get_key_state(enigo::Key::Control) || get_key_state(enigo::Key::RightControl); let shift = get_key_state(enigo::Key::Shift) || get_key_state(enigo::Key::RightShift); #[cfg(windows)] let command = crate::platform::windows::get_win_key_state(); #[cfg(not(windows))] let command = get_key_state(enigo::Key::Meta); let control_key = match key { Key::Alt => Some(ControlKey::Alt), Key::AltGr => Some(ControlKey::RAlt), Key::Backspace => Some(ControlKey::Backspace), Key::ControlLeft => { // when pressing AltGr, an extra VK_LCONTROL with a special // scancode with bit 9 set is sent, let's ignore this. #[cfg(windows)] if (event.position_code >> 8) == 0xE0 { unsafe { IS_ALT_GR = true; } return events; } Some(ControlKey::Control) } Key::ControlRight => Some(ControlKey::RControl), Key::DownArrow => Some(ControlKey::DownArrow), Key::Escape => Some(ControlKey::Escape), Key::F1 => Some(ControlKey::F1), Key::F10 => Some(ControlKey::F10), Key::F11 => Some(ControlKey::F11), Key::F12 => Some(ControlKey::F12), Key::F2 => Some(ControlKey::F2), Key::F3 => Some(ControlKey::F3), Key::F4 => Some(ControlKey::F4), Key::F5 => Some(ControlKey::F5), Key::F6 => Some(ControlKey::F6), Key::F7 => Some(ControlKey::F7), Key::F8 => Some(ControlKey::F8), Key::F9 => Some(ControlKey::F9), Key::LeftArrow => Some(ControlKey::LeftArrow), Key::MetaLeft => Some(ControlKey::Meta), Key::MetaRight => Some(ControlKey::RWin), Key::Return => Some(ControlKey::Return), Key::RightArrow => Some(ControlKey::RightArrow), Key::ShiftLeft => Some(ControlKey::Shift), Key::ShiftRight => Some(ControlKey::RShift), Key::Space => Some(ControlKey::Space), Key::Tab => Some(ControlKey::Tab), Key::UpArrow => Some(ControlKey::UpArrow), Key::Delete => { if is_win && ctrl && alt { client::ctrl_alt_del(); return events; } Some(ControlKey::Delete) } Key::Apps => Some(ControlKey::Apps), Key::Cancel => Some(ControlKey::Cancel), Key::Clear => Some(ControlKey::Clear), Key::Kana => Some(ControlKey::Kana), Key::Hangul => Some(ControlKey::Hangul), Key::Junja => Some(ControlKey::Junja), Key::Final => Some(ControlKey::Final), Key::Hanja => Some(ControlKey::Hanja), Key::Hanji => Some(ControlKey::Hanja), Key::Convert => Some(ControlKey::Convert), Key::Print => Some(ControlKey::Print), Key::Select => Some(ControlKey::Select), Key::Execute => Some(ControlKey::Execute), Key::PrintScreen => Some(ControlKey::Snapshot), Key::Help => Some(ControlKey::Help), Key::Sleep => Some(ControlKey::Sleep), Key::Separator => Some(ControlKey::Separator), Key::KpReturn => Some(ControlKey::NumpadEnter), Key::Kp0 => Some(ControlKey::Numpad0), Key::Kp1 => Some(ControlKey::Numpad1), Key::Kp2 => Some(ControlKey::Numpad2), Key::Kp3 => Some(ControlKey::Numpad3), Key::Kp4 => Some(ControlKey::Numpad4), Key::Kp5 => Some(ControlKey::Numpad5), Key::Kp6 => Some(ControlKey::Numpad6), Key::Kp7 => Some(ControlKey::Numpad7), Key::Kp8 => Some(ControlKey::Numpad8), Key::Kp9 => Some(ControlKey::Numpad9), Key::KpDivide => Some(ControlKey::Divide), Key::KpMultiply => Some(ControlKey::Multiply), Key::KpDecimal => Some(ControlKey::Decimal), Key::KpMinus => Some(ControlKey::Subtract), Key::KpPlus => Some(ControlKey::Add), Key::CapsLock | Key::NumLock | Key::ScrollLock => { return events; } Key::Home => Some(ControlKey::Home), Key::End => Some(ControlKey::End), Key::Insert => Some(ControlKey::Insert), Key::PageUp => Some(ControlKey::PageUp), Key::PageDown => Some(ControlKey::PageDown), Key::Pause => Some(ControlKey::Pause), _ => None, }; if let Some(k) = control_key { key_event.set_control_key(k); } else { let name = event .unicode .as_ref() .and_then(|unicode| unicode.name.clone()); let mut chr = match &name { Some(ref s) => { if s.len() <= 2 { // exclude chinese characters s.chars().next().unwrap_or('\0') } else { '\0' } } _ => '\0', }; if chr == '·' { // special for Chinese chr = '`'; } if chr == '\0' { chr = match key { Key::Num1 => '1', Key::Num2 => '2', Key::Num3 => '3', Key::Num4 => '4', Key::Num5 => '5', Key::Num6 => '6', Key::Num7 => '7', Key::Num8 => '8', Key::Num9 => '9', Key::Num0 => '0', Key::KeyA => 'a', Key::KeyB => 'b', Key::KeyC => 'c', Key::KeyD => 'd', Key::KeyE => 'e', Key::KeyF => 'f', Key::KeyG => 'g', Key::KeyH => 'h', Key::KeyI => 'i', Key::KeyJ => 'j', Key::KeyK => 'k', Key::KeyL => 'l', Key::KeyM => 'm', Key::KeyN => 'n', Key::KeyO => 'o', Key::KeyP => 'p', Key::KeyQ => 'q', Key::KeyR => 'r', Key::KeyS => 's', Key::KeyT => 't', Key::KeyU => 'u', Key::KeyV => 'v', Key::KeyW => 'w', Key::KeyX => 'x', Key::KeyY => 'y', Key::KeyZ => 'z', Key::Comma => ',', Key::Dot => '.', Key::SemiColon => ';', Key::Quote => '\'', Key::LeftBracket => '[', Key::RightBracket => ']', Key::Slash => '/', Key::BackSlash => '\\', Key::Minus => '-', Key::Equal => '=', Key::BackQuote => '`', _ => '\0', } } if chr != '\0' { if chr == 'l' && is_win && command { client::lock_screen(); return events; } key_event.set_chr(chr as _); } else { log::error!("Unknown key {:?}", &event); return events; } } let (alt, ctrl, shift, command) = client::get_modifiers_state(alt, ctrl, shift, command); client::legacy_modifiers(&mut key_event, alt, ctrl, shift, command); if down_or_up == true { key_event.down = true; } events.push(key_event); events } pub fn map_keyboard_mode(_peer: &str, event: &Event, mut key_event: KeyEvent) -> Option { match event.event_type { EventType::KeyPress(..) => { key_event.down = true; } EventType::KeyRelease(..) => { key_event.down = false; } _ => return None, }; #[cfg(target_os = "windows")] let keycode = match _peer { OS_LOWER_WINDOWS => { // https://github.com/rustdesk/rustdesk/issues/1371 // Filter scancodes that are greater than 255 and the hight word is not 0xE0. if event.position_code > 255 && (event.position_code >> 8) != 0xE0 { return None; } event.position_code } OS_LOWER_MACOS => { if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" { rdev::win_scancode_to_macos_iso_code(event.position_code)? } else { rdev::win_scancode_to_macos_code(event.position_code)? } } _ => rdev::win_scancode_to_linux_code(event.position_code)?, }; #[cfg(target_os = "macos")] let keycode = match _peer { OS_LOWER_WINDOWS => rdev::macos_code_to_win_scancode(event.platform_code as _)?, OS_LOWER_MACOS => event.platform_code as _, _ => rdev::macos_code_to_linux_code(event.platform_code as _)?, }; #[cfg(target_os = "linux")] let keycode = match _peer { OS_LOWER_WINDOWS => rdev::linux_code_to_win_scancode(event.position_code as _)?, OS_LOWER_MACOS => { if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" { rdev::linux_code_to_macos_iso_code(event.position_code as _)? } else { rdev::linux_code_to_macos_code(event.position_code as _)? } } _ => event.position_code as _, }; #[cfg(any(target_os = "android", target_os = "ios"))] let keycode = 0; key_event.set_chr(keycode as _); Some(key_event) } #[cfg(not(any(target_os = "android", target_os = "ios")))] fn try_fill_unicode(_peer: &str, event: &Event, key_event: &KeyEvent, events: &mut Vec) { match &event.unicode { Some(unicode_info) => { if let Some(name) = &unicode_info.name { if name.len() > 0 { let mut evt = key_event.clone(); evt.set_seq(name.to_string()); evt.down = true; events.push(evt); } } } None => { #[cfg(target_os = "windows")] if _peer == OS_LOWER_LINUX { if is_hot_key_modifiers_down() && unsafe { !IS_0X021D_DOWN } { if let Some(chr) = get_char_from_vk(event.platform_code as u32) { let mut evt = key_event.clone(); evt.set_seq(chr.to_string()); evt.down = true; events.push(evt); } } } } } } #[cfg(target_os = "windows")] fn try_file_win2win_hotkey( peer: &str, event: &Event, key_event: &KeyEvent, events: &mut Vec, ) { if peer == OS_LOWER_WINDOWS && is_hot_key_modifiers_down() && unsafe { !IS_0X021D_DOWN } { let mut down = false; let win2win_hotkey = match event.event_type { EventType::KeyPress(..) => { down = true; if let Some(unicode) = get_unicode_from_vk(event.platform_code as u32) { Some((unicode as u32 & 0x0000FFFF) | (event.platform_code << 16)) } else { None } } EventType::KeyRelease(..) => Some(event.platform_code << 16), _ => None, }; if let Some(code) = win2win_hotkey { let mut evt = key_event.clone(); evt.set_win2win_hotkey(code); evt.down = down; events.push(evt); } } } #[cfg(target_os = "windows")] fn is_hot_key_modifiers_down() -> bool { if rdev::get_modifier(Key::ControlLeft) || rdev::get_modifier(Key::ControlRight) { return true; } if rdev::get_modifier(Key::Alt) || rdev::get_modifier(Key::AltGr) { return true; } if rdev::get_modifier(Key::MetaLeft) || rdev::get_modifier(Key::MetaRight) { return true; } return false; } #[inline] #[cfg(any(target_os = "linux", target_os = "windows"))] fn is_altgr(event: &Event) -> bool { #[cfg(target_os = "linux")] if event.platform_code == 0xFE03 { true } else { false } #[cfg(target_os = "windows")] if unsafe { IS_0X021D_DOWN } && event.position_code == 0xE038 { true } else { false } } #[inline] #[cfg(any(target_os = "linux", target_os = "windows"))] fn is_press(event: &Event) -> bool { matches!(event.event_type, EventType::KeyPress(_)) } // https://github.com/fufesou/rustdesk/wiki/Keyboard-mode----Translate-Mode pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) -> Vec { let mut events: Vec = Vec::new(); if let Some(unicode_info) = &event.unicode { if unicode_info.is_dead { #[cfg(target_os = "macos")] if peer != OS_LOWER_MACOS && unsafe { IS_LEFT_OPTION_DOWN } { // try clear dead key state // rdev::clear_dead_key_state(); } else { return events; } #[cfg(not(target_os = "macos"))] return events; } } #[cfg(not(any(target_os = "android", target_os = "ios")))] if is_numpad_key(&event) { if let Some(evt) = map_keyboard_mode(peer, event, key_event) { events.push(evt); } return events; } #[cfg(target_os = "macos")] // ignore right option key if event.platform_code == rdev::kVK_RightOption as u32 { return events; } #[cfg(any(target_os = "linux", target_os = "windows"))] if is_altgr(event) { return events; } #[cfg(target_os = "windows")] if event.position_code == 0x021D { return events; } #[cfg(target_os = "windows")] try_file_win2win_hotkey(peer, event, &key_event, &mut events); #[cfg(any(target_os = "linux", target_os = "windows"))] if events.is_empty() && is_press(event) { try_fill_unicode(peer, event, &key_event, &mut events); } // If AltGr is down, no need to send events other than unicode. #[cfg(target_os = "windows")] unsafe { if IS_0X021D_DOWN { return events; } } #[cfg(target_os = "macos")] if !unsafe { IS_LEFT_OPTION_DOWN } { try_fill_unicode(peer, event, &key_event, &mut events); } if events.is_empty() { if let Some(evt) = map_keyboard_mode(peer, event, key_event) { events.push(evt); } } events } #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn keycode_to_rdev_key(keycode: u32) -> Key { #[cfg(target_os = "windows")] return rdev::win_key_from_scancode(keycode); #[cfg(target_os = "linux")] return rdev::linux_key_from_code(keycode); #[cfg(target_os = "macos")] return rdev::macos_key_from_code(keycode.try_into().unwrap_or_default()); }