diff --git a/src/rustdesk/connection.rs b/src/rustdesk/connection.rs index 599ab9a1..f954f6df 100644 --- a/src/rustdesk/connection.rs +++ b/src/rustdesk/connection.rs @@ -26,7 +26,9 @@ use super::bytes_codec::{read_frame_with_limit, write_frame_vectored}; use super::config::RustDeskConfig; use super::crypto::{self, KeyPair, SigningKeyPair}; use super::frame_adapters::{AudioFrameAdapter, VideoCodec, VideoFrameAdapter}; -use super::hid_adapter::{convert_key_events, convert_mouse_event, mouse_type}; +use super::hid_adapter::{convert_key_events_in_code_space, convert_mouse_event, mouse_type}; +use super::keyboard_mapping::KeyboardCodeSpace; +use super::protocol::hbb::message::key_event as ke_union; use super::protocol::{ decode_message, login_response, message, misc, Clipboard, ControlKey, DisplayInfo, Hash, HbbMessage, IdPk, KeyEvent, LoginRequest, LoginResponse, Misc, MouseEvent, OptionMessage, @@ -50,10 +52,46 @@ const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(15); const MAX_PASSWORD_ATTEMPTS: u8 = 5; const CONNECTION_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(3); -/// Advertised RustDesk version for client compatibility. -const RUSTDESK_COMPAT_VERSION: &str = "1.4.5"; -// Advertised platform for RustDesk clients. This affects which UI options are shown. -const RUSTDESK_COMPAT_PLATFORM: &str = "Windows"; +/// RustDesk-facing identity and the physical key code space implied by it. +struct RustDeskCompatibility { + version: &'static str, + platform: RustDeskCompatibilityPlatform, +} + +#[derive(Clone, Copy)] +enum RustDeskCompatibilityPlatform { + Windows, +} + +impl RustDeskCompatibilityPlatform { + const fn name(self) -> &'static str { + match self { + Self::Windows => "Windows", + } + } + + const fn keyboard_code_space(self) -> KeyboardCodeSpace { + match self { + Self::Windows => KeyboardCodeSpace::WindowsSet1, + } + } +} + +const RUSTDESK_COMPATIBILITY: RustDeskCompatibility = RustDeskCompatibility { + version: "1.4.5", + platform: RustDeskCompatibilityPlatform::Windows, +}; + +fn key_event_union_details(event: &KeyEvent) -> (&'static str, String) { + match &event.union { + Some(ke_union::Union::ControlKey(key)) => ("ControlKey", format!("0x{:X}", key.value())), + Some(ke_union::Union::Chr(code)) => ("Chr", format!("0x{code:X}")), + Some(ke_union::Union::Unicode(code)) => ("Unicode", format!("0x{code:X}")), + Some(ke_union::Union::Seq(seq)) => ("Seq", format!("{seq:?}")), + Some(ke_union::Union::Win2winHotkey(code)) => ("Win2winHotkey", format!("0x{code:X}")), + None => ("None", "none".to_string()), + } +} /// Input event throttler /// @@ -1124,11 +1162,11 @@ impl Connection { let mut peer_info = PeerInfo::new(); peer_info.username = "one-kvm".to_string(); peer_info.hostname = hostname_from_etc(); - peer_info.platform = RUSTDESK_COMPAT_PLATFORM.to_string(); + peer_info.platform = RUSTDESK_COMPATIBILITY.platform.name().to_string(); peer_info.displays.push(display_info); peer_info.current_display = 0; peer_info.sas_enabled = false; - peer_info.version = RUSTDESK_COMPAT_VERSION.to_string(); + peer_info.version = RUSTDESK_COMPATIBILITY.version.to_string(); peer_info.encoding = protobuf::MessageField::some(encoding); let mut login_response = LoginResponse::new(); @@ -1265,9 +1303,15 @@ impl Connection { /// Handle key event async fn handle_key_event(&mut self, ke: &KeyEvent) -> anyhow::Result<()> { + let (union_type, raw_value) = key_event_union_details(ke); debug!( - "Key event: down={}, press={}, chr={:?}, modifiers={:?}", - ke.down, ke.press, ke.union, ke.modifiers + mode = ke.mode.value(), + union = union_type, + raw = %raw_value, + down = ke.down, + press = ke.press, + modifiers = ?ke.modifiers, + "RustDesk key event" ); // Check for CapsLock state change in modifiers @@ -1305,15 +1349,21 @@ impl Connection { } // Convert RustDesk key event to One-KVM key events - let kb_events = convert_key_events(ke); + let kb_events = convert_key_events_in_code_space( + ke, + RUSTDESK_COMPATIBILITY.platform.keyboard_code_space(), + ); if !kb_events.is_empty() { if let Some(ref hid) = self.hid { for kb_event in kb_events { debug!( - "Converted to HID: key=0x{:02X}, event_type={:?}, modifiers={:02X}", - kb_event.key.to_hid_usage(), - kb_event.event_type, - kb_event.modifiers.to_hid_byte() + mode = ke.mode.value(), + union = union_type, + raw = %raw_value, + hid = format_args!("0x{:02X}", kb_event.key.to_hid_usage()), + event_type = ?kb_event.event_type, + modifiers = format_args!("0x{:02X}", kb_event.modifiers.to_hid_byte()), + "Converted RustDesk key event" ); if let Err(e) = hid.send_keyboard(kb_event).await { @@ -1324,7 +1374,12 @@ impl Connection { debug!("HID controller not available, skipping key event"); } } else { - warn!("Could not convert key event to HID: chr={:?}", ke.union); + debug!( + mode = ke.mode.value(), + union = union_type, + raw = %raw_value, + "RustDesk key event produced no HID event" + ); } Ok(()) diff --git a/src/rustdesk/hid_adapter.rs b/src/rustdesk/hid_adapter.rs index cfc106ac..d819aef9 100644 --- a/src/rustdesk/hid_adapter.rs +++ b/src/rustdesk/hid_adapter.rs @@ -1,10 +1,11 @@ +use super::keyboard_mapping::{self, CharacterMapping, KeyboardCodeSpace}; use super::protocol::hbb::message::key_event as ke_union; use super::protocol::{ControlKey, KeyEvent, KeyboardMode, MouseEvent}; use crate::hid::{ CanonicalKey, KeyEventType, KeyboardEvent, KeyboardModifiers, MouseButton, MouseEvent as OneKvmMouseEvent, MouseEventType, }; -use protobuf::Enum; +use tracing::debug; pub mod mouse_type { pub const MOVE: i32 = 0; @@ -105,14 +106,23 @@ fn button_id_to_button(button_id: i32) -> Option { } } +/// Convert using the code space associated with One-KVM's compatibility platform. pub fn convert_key_events(event: &KeyEvent) -> Vec { + convert_key_events_in_code_space(event, KeyboardCodeSpace::WindowsSet1) +} + +pub(super) fn convert_key_events_in_code_space( + event: &KeyEvent, + code_space: KeyboardCodeSpace, +) -> Vec { let base_modifiers = if is_modifier_control_key(event) { KeyboardModifiers::default() } else { parse_modifiers(event) }; - let Some(mapping) = key_event_to_hid(event, base_modifiers) else { + let Some(mapping) = key_event_to_mapping(event, code_space, base_modifiers) else { + log_rejected_key_event(event, code_space); return Vec::new(); }; @@ -135,13 +145,12 @@ pub fn convert_key_events(event: &KeyEvent) -> Vec { }, ] } else { - let event_type = if event.down { - KeyEventType::Down - } else { - KeyEventType::Up - }; vec![KeyboardEvent { - event_type, + event_type: if event.down { + KeyEventType::Down + } else { + KeyEventType::Up + }, key: mapping.key, modifiers: mapping.modifiers, }] @@ -159,534 +168,337 @@ struct KeyMapping { added_shift: bool, } -fn key_event_to_hid(event: &KeyEvent, modifiers: KeyboardModifiers) -> Option { +fn key_event_to_mapping( + event: &KeyEvent, + code_space: KeyboardCodeSpace, + modifiers: KeyboardModifiers, +) -> Option { + let mode = event.mode.enum_value().ok()?; match &event.union { - Some(ke_union::Union::ControlKey(ck)) => { - let key = CanonicalKey::from_hid_usage(control_key_to_hid(ck.value())?)?; - Some(KeyMapping { - key, + Some(ke_union::Union::ControlKey(key)) => { + plain_mapping(keyboard_mapping::control_key(key.value())?, modifiers) + } + Some(ke_union::Union::Unicode(ch)) => character_mapping(*ch, modifiers), + Some(ke_union::Union::Chr(code)) => match mode { + KeyboardMode::Map | KeyboardMode::Translate => plain_mapping( + keyboard_mapping::physical_key(code_space, *code)?, modifiers, - added_shift: false, - }) - } - Some(ke_union::Union::Chr(chr)) => { - if event.mode.value() != KeyboardMode::Map.value() { - if let Some(mapping) = shifted_printable_char_to_hid(*chr, modifiers) { - return Some(mapping); - } - } - let key = CanonicalKey::from_hid_usage(keycode_to_hid(*chr)?)?; - Some(KeyMapping { - key, - modifiers, - added_shift: false, - }) - } - Some(ke_union::Union::Unicode(unicode)) => { - let mapping = printable_char_to_hid(*unicode, modifiers)?; - Some(mapping) - } - _ => None, + ), + KeyboardMode::Legacy | KeyboardMode::Auto => legacy_character_mapping(*code, modifiers), + }, + Some(ke_union::Union::Seq(_)) | Some(ke_union::Union::Win2winHotkey(_)) | None => None, + } +} + +fn plain_mapping(key: CanonicalKey, modifiers: KeyboardModifiers) -> Option { + Some(KeyMapping { + key, + modifiers, + added_shift: false, + }) +} + +fn character_mapping(ch: u32, modifiers: KeyboardModifiers) -> Option { + let CharacterMapping { key, needs_shift } = keyboard_mapping::character(ch)?; + if !needs_shift { + return plain_mapping(key, modifiers); + } + + let added_shift = !modifiers.left_shift && !modifiers.right_shift; + let mut shifted_modifiers = modifiers; + shifted_modifiers.left_shift = true; + Some(KeyMapping { + key, + modifiers: shifted_modifiers, + added_shift, + }) +} + +fn legacy_character_mapping(ch: u32, modifiers: KeyboardModifiers) -> Option { + let CharacterMapping { key, needs_shift } = keyboard_mapping::character(ch)?; + // Legacy Chr historically relied on the event's modifier list for uppercase + // letters, while synthesizing Shift for printable US-layout symbols. + if needs_shift && !(0x41..=0x5A).contains(&ch) { + character_mapping(ch, modifiers) + } else { + plain_mapping(key, modifiers) } } fn is_modifier_control_key(event: &KeyEvent) -> bool { - if let Some(ke_union::Union::ControlKey(ck)) = &event.union { - let val = ck.value(); - return val == ControlKey::Control.value() - || val == ControlKey::Shift.value() - || val == ControlKey::Alt.value() - || val == ControlKey::Meta.value() - || val == ControlKey::RControl.value() - || val == ControlKey::RShift.value() - || val == ControlKey::RAlt.value(); - } - false + let Some(ke_union::Union::ControlKey(key)) = &event.union else { + return false; + }; + matches!( + key.enum_value(), + Ok(ControlKey::Control) + | Ok(ControlKey::Shift) + | Ok(ControlKey::Alt) + | Ok(ControlKey::Meta) + | Ok(ControlKey::RControl) + | Ok(ControlKey::RShift) + | Ok(ControlKey::RAlt) + | Ok(ControlKey::RWin) + ) } fn parse_modifiers(event: &KeyEvent) -> KeyboardModifiers { let mut modifiers = KeyboardModifiers::default(); - for modifier in &event.modifiers { - let val = modifier.value(); - match val { - x if x == ControlKey::Control.value() => modifiers.left_ctrl = true, - x if x == ControlKey::Shift.value() => modifiers.left_shift = true, - x if x == ControlKey::Alt.value() => modifiers.left_alt = true, - x if x == ControlKey::Meta.value() => modifiers.left_meta = true, - x if x == ControlKey::RControl.value() => modifiers.right_ctrl = true, - x if x == ControlKey::RShift.value() => modifiers.right_shift = true, - x if x == ControlKey::RAlt.value() => modifiers.right_alt = true, + match modifier.enum_value() { + Ok(ControlKey::Control) => modifiers.left_ctrl = true, + Ok(ControlKey::Shift) => modifiers.left_shift = true, + Ok(ControlKey::Alt) => modifiers.left_alt = true, + Ok(ControlKey::Meta) => modifiers.left_meta = true, + Ok(ControlKey::RControl) => modifiers.right_ctrl = true, + Ok(ControlKey::RShift) => modifiers.right_shift = true, + Ok(ControlKey::RAlt) => modifiers.right_alt = true, + Ok(ControlKey::RWin) => modifiers.right_meta = true, _ => {} } } - modifiers } -fn with_shift(mut modifiers: KeyboardModifiers) -> KeyboardModifiers { - modifiers.left_shift = true; - modifiers -} - -fn shifted_mapping(key: CanonicalKey, modifiers: KeyboardModifiers) -> KeyMapping { - let added_shift = !modifiers.left_shift && !modifiers.right_shift; - KeyMapping { - key, - modifiers: with_shift(modifiers), - added_shift, - } -} - -fn plain_mapping(key: CanonicalKey, modifiers: KeyboardModifiers) -> KeyMapping { - KeyMapping { - key, - modifiers, - added_shift: false, - } -} - -fn shifted_printable_char_to_hid(ch: u32, modifiers: KeyboardModifiers) -> Option { - match ch { - 33 => Some(shifted_mapping(CanonicalKey::Digit1, modifiers)), - 64 => Some(shifted_mapping(CanonicalKey::Digit2, modifiers)), - 35 => Some(shifted_mapping(CanonicalKey::Digit3, modifiers)), - 36 => Some(shifted_mapping(CanonicalKey::Digit4, modifiers)), - 37 => Some(shifted_mapping(CanonicalKey::Digit5, modifiers)), - 94 => Some(shifted_mapping(CanonicalKey::Digit6, modifiers)), - 38 => Some(shifted_mapping(CanonicalKey::Digit7, modifiers)), - 42 => Some(shifted_mapping(CanonicalKey::Digit8, modifiers)), - 40 => Some(shifted_mapping(CanonicalKey::Digit9, modifiers)), - 41 => Some(shifted_mapping(CanonicalKey::Digit0, modifiers)), - 95 => Some(shifted_mapping(CanonicalKey::Minus, modifiers)), - 43 => Some(shifted_mapping(CanonicalKey::Equal, modifiers)), - 123 => Some(shifted_mapping(CanonicalKey::BracketLeft, modifiers)), - 125 => Some(shifted_mapping(CanonicalKey::BracketRight, modifiers)), - 124 => Some(shifted_mapping(CanonicalKey::Backslash, modifiers)), - 58 => Some(shifted_mapping(CanonicalKey::Semicolon, modifiers)), - 34 => Some(shifted_mapping(CanonicalKey::Quote, modifiers)), - 126 => Some(shifted_mapping(CanonicalKey::Backquote, modifiers)), - 60 => Some(shifted_mapping(CanonicalKey::Comma, modifiers)), - 62 => Some(shifted_mapping(CanonicalKey::Period, modifiers)), - 63 => Some(shifted_mapping(CanonicalKey::Slash, modifiers)), - _ => None, - } -} - -fn printable_char_to_hid(ch: u32, modifiers: KeyboardModifiers) -> Option { - match ch { - 65..=90 => Some(shifted_mapping( - CanonicalKey::from_hid_usage((ch - 65 + 0x04) as u8)?, - modifiers, - )), - 97..=122 => Some(plain_mapping( - CanonicalKey::from_hid_usage((ch - 97 + 0x04) as u8)?, - modifiers, - )), - 48 => Some(plain_mapping(CanonicalKey::Digit0, modifiers)), - 49 => Some(plain_mapping(CanonicalKey::Digit1, modifiers)), - 50 => Some(plain_mapping(CanonicalKey::Digit2, modifiers)), - 51 => Some(plain_mapping(CanonicalKey::Digit3, modifiers)), - 52 => Some(plain_mapping(CanonicalKey::Digit4, modifiers)), - 53 => Some(plain_mapping(CanonicalKey::Digit5, modifiers)), - 54 => Some(plain_mapping(CanonicalKey::Digit6, modifiers)), - 55 => Some(plain_mapping(CanonicalKey::Digit7, modifiers)), - 56 => Some(plain_mapping(CanonicalKey::Digit8, modifiers)), - 57 => Some(plain_mapping(CanonicalKey::Digit9, modifiers)), - 32 => Some(plain_mapping(CanonicalKey::Space, modifiers)), - 13 | 10 => Some(plain_mapping(CanonicalKey::Enter, modifiers)), - 9 => Some(plain_mapping(CanonicalKey::Tab, modifiers)), - 27 => Some(plain_mapping(CanonicalKey::Escape, modifiers)), - 8 => Some(plain_mapping(CanonicalKey::Backspace, modifiers)), - 127 => Some(plain_mapping(CanonicalKey::Delete, modifiers)), - 45 => Some(plain_mapping(CanonicalKey::Minus, modifiers)), - 61 => Some(plain_mapping(CanonicalKey::Equal, modifiers)), - 91 => Some(plain_mapping(CanonicalKey::BracketLeft, modifiers)), - 93 => Some(plain_mapping(CanonicalKey::BracketRight, modifiers)), - 92 => Some(plain_mapping(CanonicalKey::Backslash, modifiers)), - 59 => Some(plain_mapping(CanonicalKey::Semicolon, modifiers)), - 39 => Some(plain_mapping(CanonicalKey::Quote, modifiers)), - 96 => Some(plain_mapping(CanonicalKey::Backquote, modifiers)), - 44 => Some(plain_mapping(CanonicalKey::Comma, modifiers)), - 46 => Some(plain_mapping(CanonicalKey::Period, modifiers)), - 47 => Some(plain_mapping(CanonicalKey::Slash, modifiers)), - _ => shifted_printable_char_to_hid(ch, modifiers), - } -} - -fn control_key_to_hid(key: i32) -> Option { - match key { - x if x == ControlKey::Alt as i32 => Some(0xE2), // Left Alt - x if x == ControlKey::Backspace as i32 => Some(0x2A), - x if x == ControlKey::CapsLock as i32 => Some(0x39), - x if x == ControlKey::Control as i32 => Some(0xE0), // Left Ctrl - x if x == ControlKey::Delete as i32 => Some(0x4C), - x if x == ControlKey::DownArrow as i32 => Some(0x51), - x if x == ControlKey::End as i32 => Some(0x4D), - x if x == ControlKey::Escape as i32 => Some(0x29), - x if x == ControlKey::F1 as i32 => Some(0x3A), - x if x == ControlKey::F2 as i32 => Some(0x3B), - x if x == ControlKey::F3 as i32 => Some(0x3C), - x if x == ControlKey::F4 as i32 => Some(0x3D), - x if x == ControlKey::F5 as i32 => Some(0x3E), - x if x == ControlKey::F6 as i32 => Some(0x3F), - x if x == ControlKey::F7 as i32 => Some(0x40), - x if x == ControlKey::F8 as i32 => Some(0x41), - x if x == ControlKey::F9 as i32 => Some(0x42), - x if x == ControlKey::F10 as i32 => Some(0x43), - x if x == ControlKey::F11 as i32 => Some(0x44), - x if x == ControlKey::F12 as i32 => Some(0x45), - x if x == ControlKey::Home as i32 => Some(0x4A), - x if x == ControlKey::LeftArrow as i32 => Some(0x50), - x if x == ControlKey::Meta as i32 => Some(0xE3), // Left GUI/Windows - x if x == ControlKey::PageDown as i32 => Some(0x4E), - x if x == ControlKey::PageUp as i32 => Some(0x4B), - x if x == ControlKey::Return as i32 => Some(0x28), - x if x == ControlKey::RightArrow as i32 => Some(0x4F), - x if x == ControlKey::Shift as i32 => Some(0xE1), // Left Shift - x if x == ControlKey::Space as i32 => Some(0x2C), - x if x == ControlKey::Tab as i32 => Some(0x2B), - x if x == ControlKey::UpArrow as i32 => Some(0x52), - x if x == ControlKey::Numpad0 as i32 => Some(0x62), - x if x == ControlKey::Numpad1 as i32 => Some(0x59), - x if x == ControlKey::Numpad2 as i32 => Some(0x5A), - x if x == ControlKey::Numpad3 as i32 => Some(0x5B), - x if x == ControlKey::Numpad4 as i32 => Some(0x5C), - x if x == ControlKey::Numpad5 as i32 => Some(0x5D), - x if x == ControlKey::Numpad6 as i32 => Some(0x5E), - x if x == ControlKey::Numpad7 as i32 => Some(0x5F), - x if x == ControlKey::Numpad8 as i32 => Some(0x60), - x if x == ControlKey::Numpad9 as i32 => Some(0x61), - x if x == ControlKey::Insert as i32 => Some(0x49), - x if x == ControlKey::Pause as i32 => Some(0x48), - x if x == ControlKey::Scroll as i32 => Some(0x47), - x if x == ControlKey::NumLock as i32 => Some(0x53), - x if x == ControlKey::RShift as i32 => Some(0xE5), - x if x == ControlKey::RControl as i32 => Some(0xE4), - x if x == ControlKey::RAlt as i32 => Some(0xE6), - x if x == ControlKey::Multiply as i32 => Some(0x55), - x if x == ControlKey::Add as i32 => Some(0x57), - x if x == ControlKey::Subtract as i32 => Some(0x56), - x if x == ControlKey::Decimal as i32 => Some(0x63), - x if x == ControlKey::Divide as i32 => Some(0x54), - x if x == ControlKey::NumpadEnter as i32 => Some(0x58), - _ => None, - } -} - -fn keycode_to_hid(keycode: u32) -> Option { - if let Some(hid) = ascii_to_hid(keycode) { - return Some(hid); - } - if let Some(hid) = windows_vk_to_hid(keycode) { - return Some(hid); - } - x11_keycode_to_hid(keycode) -} - -fn ascii_to_hid(ascii: u32) -> Option { - match ascii { - 97..=122 => Some((ascii - 97 + 0x04) as u8), - 65..=90 => Some((ascii - 65 + 0x04) as u8), - 48 => Some(0x27), // 0 - 49..=57 => Some((ascii - 49 + 0x1E) as u8), // 1-9 - 32 => Some(0x2C), // Space - 13 => Some(0x28), // Enter (CR) - 10 => Some(0x28), // Enter (LF) - 9 => Some(0x2B), // Tab - 27 => Some(0x29), // Escape - 8 => Some(0x2A), // Backspace - 127 => Some(0x4C), // Delete - 45 => Some(0x2D), // - - 61 => Some(0x2E), // = - 91 => Some(0x2F), // [ - 93 => Some(0x30), // ] - 92 => Some(0x31), // \ - 59 => Some(0x33), // ; - 39 => Some(0x34), // ' - 96 => Some(0x35), // ` - 44 => Some(0x36), // , - 46 => Some(0x37), // . - 47 => Some(0x38), // / - _ => None, - } -} - -fn windows_vk_to_hid(vk: u32) -> Option { - match vk { - 0x41..=0x5A => { - let letter = (vk - 0x41) as u8; - Some(match letter { - 0 => 0x04, // A - 1 => 0x05, // B - 2 => 0x06, // C - 3 => 0x07, // D - 4 => 0x08, // E - 5 => 0x09, // F - 6 => 0x0A, // G - 7 => 0x0B, // H - 8 => 0x0C, // I - 9 => 0x0D, // J - 10 => 0x0E, // K - 11 => 0x0F, // L - 12 => 0x10, // M - 13 => 0x11, // N - 14 => 0x12, // O - 15 => 0x13, // P - 16 => 0x14, // Q - 17 => 0x15, // R - 18 => 0x16, // S - 19 => 0x17, // T - 20 => 0x18, // U - 21 => 0x19, // V - 22 => 0x1A, // W - 23 => 0x1B, // X - 24 => 0x1C, // Y - 25 => 0x1D, // Z - _ => return None, - }) +fn log_rejected_key_event(event: &KeyEvent, code_space: KeyboardCodeSpace) { + let mode = event.mode.value(); + match &event.union { + Some(ke_union::Union::ControlKey(key)) => debug!( + mode, + union = "ControlKey", + raw = format_args!("0x{:X}", key.value()), + ?code_space, + "Dropping unsupported RustDesk keyboard event" + ), + Some(ke_union::Union::Chr(code)) => debug!( + mode, + union = "Chr", + raw = format_args!("0x{code:X}"), + ?code_space, + "Dropping unsupported RustDesk keyboard event" + ), + Some(ke_union::Union::Unicode(code)) => debug!( + mode, + union = "Unicode", + raw = format_args!("0x{code:X}"), + ?code_space, + "Dropping unsupported RustDesk keyboard event" + ), + Some(ke_union::Union::Seq(seq)) => { + debug!(mode, union = "Seq", raw = ?seq, ?code_space, "Dropping unsupported RustDesk keyboard event") } - 0x30 => Some(0x27), // 0 - 0x31..=0x39 => Some((vk - 0x31 + 0x1E) as u8), // 1-9 - 0x60 => Some(0x62), // Numpad 0 - 0x61..=0x69 => Some((vk - 0x61 + 0x59) as u8), // Numpad 1-9 - 0x6A => Some(0x55), // Numpad * - 0x6B => Some(0x57), // Numpad + - 0x6D => Some(0x56), // Numpad - - 0x6E => Some(0x63), // Numpad . - 0x6F => Some(0x54), // Numpad / - 0x70..=0x7B => Some((vk - 0x70 + 0x3A) as u8), - 0x08 => Some(0x2A), // Backspace - 0x09 => Some(0x2B), // Tab - 0x0D => Some(0x28), // Enter - 0x1B => Some(0x29), // Escape - 0x20 => Some(0x2C), // Space - 0x21 => Some(0x4B), // Page Up - 0x22 => Some(0x4E), // Page Down - 0x23 => Some(0x4D), // End - 0x24 => Some(0x4A), // Home - 0x25 => Some(0x50), // Left Arrow - 0x26 => Some(0x52), // Up Arrow - 0x27 => Some(0x4F), // Right Arrow - 0x28 => Some(0x51), // Down Arrow - 0x2D => Some(0x49), // Insert - 0x2E => Some(0x4C), // Delete - 0xBA => Some(0x33), // ; : - 0xBB => Some(0x2E), // = + - 0xBC => Some(0x36), // , < - 0xBD => Some(0x2D), // - _ - 0xBE => Some(0x37), // . > - 0xBF => Some(0x38), // / ? - 0xC0 => Some(0x35), // ` ~ - 0xDB => Some(0x2F), // [ { - 0xDC => Some(0x31), // \ | - 0xDD => Some(0x30), // ] } - 0xDE => Some(0x34), // ' " - 0x14 => Some(0x39), // Caps Lock - 0x90 => Some(0x53), // Num Lock - 0x91 => Some(0x47), // Scroll Lock - 0x2C => Some(0x46), // Print Screen - 0x13 => Some(0x48), // Pause - _ => None, - } -} - -fn x11_keycode_to_hid(keycode: u32) -> Option { - match keycode { - 10..=18 => Some((keycode - 10 + 0x1E) as u8), // 1-9 - 19 => Some(0x27), // 0 - 20 => Some(0x2D), // - - 21 => Some(0x2E), // = - 34 => Some(0x2F), // [ - 35 => Some(0x30), // ] - 24 => Some(0x14), // q - 25 => Some(0x1A), // w - 26 => Some(0x08), // e - 27 => Some(0x15), // r - 28 => Some(0x17), // t - 29 => Some(0x1C), // y - 30 => Some(0x18), // u - 31 => Some(0x0C), // i - 32 => Some(0x12), // o - 33 => Some(0x13), // p - 38 => Some(0x04), // a - 39 => Some(0x16), // s - 40 => Some(0x07), // d - 41 => Some(0x09), // f - 42 => Some(0x0A), // g - 43 => Some(0x0B), // h - 44 => Some(0x0D), // j - 45 => Some(0x0E), // k - 46 => Some(0x0F), // l - 47 => Some(0x33), // ; - 48 => Some(0x34), // ' - 49 => Some(0x35), // ` - 51 => Some(0x31), // \ - 52 => Some(0x1D), // z - 53 => Some(0x1B), // x - 54 => Some(0x06), // c - 55 => Some(0x19), // v - 56 => Some(0x05), // b - 57 => Some(0x11), // n - 58 => Some(0x10), // m - 59 => Some(0x36), // , - 60 => Some(0x37), // . - 61 => Some(0x38), // / - 65 => Some(0x2C), - _ => None, + Some(ke_union::Union::Win2winHotkey(code)) => debug!( + mode, + union = "Win2winHotkey", + raw = format_args!("0x{code:X}"), + ?code_space, + "Dropping unsupported RustDesk keyboard event" + ), + None => debug!( + mode, + union = "None", + ?code_space, + "Dropping unsupported RustDesk keyboard event" + ), } } #[cfg(test)] mod tests { use super::*; + use protobuf::EnumOrUnknown; - #[test] - fn test_control_key_mapping() { - assert_eq!(control_key_to_hid(ControlKey::Escape.value()), Some(0x29)); - assert_eq!(control_key_to_hid(ControlKey::Return.value()), Some(0x28)); - assert_eq!(control_key_to_hid(ControlKey::Space.value()), Some(0x2C)); + fn key_event(mode: KeyboardMode, union: ke_union::Union, down: bool) -> KeyEvent { + let mut event = KeyEvent::new(); + event.mode = EnumOrUnknown::new(mode); + event.union = Some(union); + event.down = down; + event + } + + fn map_chr(code: u32, down: bool) -> KeyEvent { + key_event(KeyboardMode::Map, ke_union::Union::Chr(code), down) } #[test] - fn test_convert_mouse_move() { - let mut event = MouseEvent::new(); - event.x = 500; - event.y = 300; - event.mask = mouse_type::MOVE; // Pure move event - - let events = convert_mouse_event(&event, 1920, 1080); - assert!(!events.is_empty()); - assert_eq!(events[0].event_type, MouseEventType::MoveAbs); - } - - #[test] - fn test_convert_mouse_button_down() { - let mut event = MouseEvent::new(); - event.x = 500; - event.y = 300; - event.mask = (mouse_button::LEFT << 3) | mouse_type::DOWN; - - let events = convert_mouse_event(&event, 1920, 1080); - assert_eq!(events.len(), 1); - assert_eq!(events[0].event_type, MouseEventType::Down); - assert_eq!(events[0].button, Some(MouseButton::Left)); - } - - #[test] - fn test_convert_mouse_button_down_does_not_move() { - let mut event = MouseEvent::new(); - event.mask = (mouse_button::LEFT << 3) | mouse_type::DOWN; - - let events = convert_mouse_event(&event, 1920, 1080); - assert_eq!(events.len(), 1); - assert_eq!(events[0].event_type, MouseEventType::Down); - assert_eq!(events[0].button, Some(MouseButton::Left)); - } - - #[test] - fn test_convert_mouse_wheel_does_not_move() { - let mut event = MouseEvent::new(); - event.x = 500; - event.y = 1; - event.mask = mouse_type::WHEEL; - - let events = convert_mouse_event(&event, 1920, 1080); - assert_eq!(events.len(), 1); - assert_eq!(events[0].event_type, MouseEventType::Scroll); - assert_eq!(events[0].scroll, 1); - } - - #[test] - fn test_convert_mouse_move_relative() { + fn mouse_events_keep_existing_semantics() { let mut event = MouseEvent::new(); event.x = -12; event.y = 8; event.mask = mouse_type::MOVE_RELATIVE; - let events = convert_mouse_event(&event, 1920, 1080); - assert_eq!(events.len(), 1); assert_eq!(events[0].event_type, MouseEventType::Move); - assert_eq!(events[0].x, -12); - assert_eq!(events[0].y, 8); + assert_eq!((events[0].x, events[0].y), (-12, 8)); + + event.mask = (mouse_button::LEFT << 3) | mouse_type::DOWN; + let events = convert_mouse_event(&event, 1920, 1080); + assert_eq!(events[0].event_type, MouseEventType::Down); + assert_eq!(events[0].button, Some(MouseButton::Left)); + assert_eq!((events[0].x, events[0].y), (0, 0)); } #[test] - fn test_convert_key_event() { - use protobuf::EnumOrUnknown; - let mut key_event = KeyEvent::new(); - key_event.down = true; - key_event.press = false; - key_event.union = Some(ke_union::Union::ControlKey(EnumOrUnknown::new( - ControlKey::Return, - ))); - - let result = convert_key_event(&key_event); - assert!(result.is_some()); - - let kb_event = result.unwrap(); - assert_eq!(kb_event.event_type, KeyEventType::Down); - assert_eq!(kb_event.key, CanonicalKey::Enter); + fn map_delete_generates_only_delete_down_and_up() { + let down = convert_key_events(&map_chr(0xE053, true)); + let up = convert_key_events(&map_chr(0xE053, false)); + assert_eq!((down.len(), up.len()), (1, 1)); + assert_eq!( + (down[0].event_type, up[0].event_type), + (KeyEventType::Down, KeyEventType::Up) + ); + assert_eq!( + (down[0].key, up[0].key), + (CanonicalKey::Delete, CanonicalKey::Delete) + ); + assert_eq!( + (down[0].key.to_hid_usage(), up[0].key.to_hid_usage()), + (0x4C, 0x4C) + ); } #[test] - fn test_convert_at_press_to_shift_digit2() { - let mut key_event = KeyEvent::new(); - key_event.press = true; - key_event.union = Some(ke_union::Union::Unicode('@' as u32)); + fn map_scan_codes_do_not_become_ascii_digits() { + let alt = convert_key_events(&map_chr(0x38, true)); + let shift = convert_key_events(&map_chr(0x36, true)); + assert_eq!(alt[0].key, CanonicalKey::AltLeft); + assert_ne!(alt[0].key, CanonicalKey::Digit8); + assert_eq!(shift[0].key, CanonicalKey::ShiftRight); + assert_ne!(shift[0].key, CanonicalKey::Digit6); + } - let events = convert_key_events(&key_event); + #[test] + fn legacy_uses_character_semantics_for_same_values() { + let digit8 = key_event(KeyboardMode::Legacy, ke_union::Union::Chr(0x38), true); + let digit6 = key_event(KeyboardMode::Legacy, ke_union::Union::Chr(0x36), true); + assert_eq!(convert_key_events(&digit8)[0].key, CanonicalKey::Digit8); + assert_eq!(convert_key_events(&digit6)[0].key, CanonicalKey::Digit6); + + let uppercase = key_event(KeyboardMode::Legacy, ke_union::Union::Chr(0x41), true); + let uppercase = convert_key_events(&uppercase); + assert_eq!(uppercase[0].key, CanonicalKey::KeyA); + assert!(!uppercase[0].modifiers.left_shift); + } + + #[test] + fn translate_chr_uses_physical_fallback_semantics() { + let event = key_event(KeyboardMode::Translate, ke_union::Union::Chr(0xE053), true); + assert_eq!(convert_key_events(&event)[0].key, CanonicalKey::Delete); + } + + #[test] + fn unknown_map_codes_never_fall_back() { + for code in [0, 0x59, 0x61, 0x7F, 0xE054, 0x0101] { + assert!( + convert_key_events(&map_chr(code, true)).is_empty(), + "0x{code:X}" + ); + } + + let ascii_a_value = convert_key_events(&map_chr(0x41, true)); + assert_eq!(ascii_a_value[0].key, CanonicalKey::F7); + assert_ne!(ascii_a_value[0].key, CanonicalKey::KeyA); + } + + #[test] + fn unicode_and_control_keys_stay_independent() { + let unicode = key_event( + KeyboardMode::Map, + ke_union::Union::Unicode('@' as u32), + true, + ); + let control = key_event( + KeyboardMode::Translate, + ke_union::Union::ControlKey(EnumOrUnknown::new(ControlKey::Delete)), + true, + ); + let unicode = convert_key_events(&unicode); + assert_eq!(unicode[0].key, CanonicalKey::Digit2); + assert!(unicode[0].modifiers.left_shift); + assert_eq!(convert_key_events(&control)[0].key, CanonicalKey::Delete); + } + + #[test] + fn press_and_repeated_events_preserve_state_model() { + let mut press = map_chr(0x1E, false); + press.press = true; + press + .modifiers + .push(EnumOrUnknown::new(ControlKey::Control)); + let events = convert_key_events(&press); assert_eq!(events.len(), 2); - assert_eq!(events[0].event_type, KeyEventType::Down); - assert_eq!(events[0].key, CanonicalKey::Digit2); + assert_eq!( + (events[0].event_type, events[1].event_type), + (KeyEventType::Down, KeyEventType::Up) + ); + assert_eq!( + (events[0].key, events[1].key), + (CanonicalKey::KeyA, CanonicalKey::KeyA) + ); + assert!(events.iter().all(|event| event.modifiers.left_ctrl)); + + let down = map_chr(0x1E, true); + assert_eq!(convert_key_events(&down)[0].event_type, KeyEventType::Down); + assert_eq!(convert_key_events(&down)[0].event_type, KeyEventType::Down); + } + + #[test] + fn shifted_press_releases_only_synthetic_shift() { + let mut event = key_event( + KeyboardMode::Legacy, + ke_union::Union::Chr('@' as u32), + false, + ); + event.press = true; + let events = convert_key_events(&event); assert!(events[0].modifiers.left_shift); - assert_eq!(events[1].event_type, KeyEventType::Up); - assert_eq!(events[1].key, CanonicalKey::Digit2); assert!(!events[1].modifiers.left_shift); } #[test] - fn test_convert_shifted_chr_to_shift_digit2() { - let mut key_event = KeyEvent::new(); - key_event.down = true; - key_event.union = Some(ke_union::Union::Chr('@' as u32)); - - let events = convert_key_events(&key_event); - assert_eq!(events.len(), 1); - assert_eq!(events[0].event_type, KeyEventType::Down); - assert_eq!(events[0].key, CanonicalKey::Digit2); - assert!(events[0].modifiers.left_shift); + fn modifier_control_key_does_not_duplicate_state() { + let mut event = key_event( + KeyboardMode::Legacy, + ke_union::Union::ControlKey(EnumOrUnknown::new(ControlKey::RWin)), + true, + ); + event.modifiers.push(EnumOrUnknown::new(ControlKey::RWin)); + let events = convert_key_events(&event); + assert_eq!(events[0].key, CanonicalKey::MetaRight); + assert_eq!(events[0].modifiers, KeyboardModifiers::default()); } #[test] - fn test_convert_map_mode_chr_as_physical_key() { - use protobuf::EnumOrUnknown; - let mut key_event = KeyEvent::new(); - key_event.down = true; - key_event.mode = EnumOrUnknown::new(KeyboardMode::Map); - key_event.union = Some(ke_union::Union::Chr(0x41)); - - let events = convert_key_events(&key_event); - assert_eq!(events.len(), 1); - assert_eq!(events[0].event_type, KeyEventType::Down); - assert_eq!(events[0].key, CanonicalKey::KeyA); - assert!(!events[0].modifiers.left_shift); + fn legacy_delete_and_audited_controls_are_mapped() { + for (control, expected) in [ + (ControlKey::Delete, CanonicalKey::Delete), + (ControlKey::Snapshot, CanonicalKey::PrintScreen), + (ControlKey::RWin, CanonicalKey::MetaRight), + (ControlKey::Apps, CanonicalKey::ContextMenu), + ] { + let event = key_event( + KeyboardMode::Legacy, + ke_union::Union::ControlKey(EnumOrUnknown::new(control)), + true, + ); + assert_eq!(convert_key_events(&event)[0].key, expected); + } } #[test] - fn test_convert_press_generates_down_and_up() { - use protobuf::EnumOrUnknown; - let mut key_event = KeyEvent::new(); - key_event.press = true; - key_event.union = Some(ke_union::Union::ControlKey(EnumOrUnknown::new( - ControlKey::Return, - ))); + fn rejects_unknown_modes_and_unsupported_unions() { + let mut unknown = map_chr(0x1E, true); + unknown.mode = EnumOrUnknown::from_i32(99); + assert!(convert_key_events(&unknown).is_empty()); - let events = convert_key_events(&key_event); - assert_eq!(events.len(), 2); - assert_eq!(events[0].event_type, KeyEventType::Down); - assert_eq!(events[1].event_type, KeyEventType::Up); - assert_eq!(events[0].key, CanonicalKey::Enter); - assert_eq!(events[1].key, CanonicalKey::Enter); + let seq = key_event( + KeyboardMode::Translate, + ke_union::Union::Seq("a".to_string()), + true, + ); + assert!(convert_key_events(&seq).is_empty()); + + let mut empty = KeyEvent::new(); + empty.mode = EnumOrUnknown::new(KeyboardMode::Legacy); + assert!(convert_key_events(&empty).is_empty()); } } diff --git a/src/rustdesk/keyboard_mapping.rs b/src/rustdesk/keyboard_mapping.rs new file mode 100644 index 00000000..301e5e85 --- /dev/null +++ b/src/rustdesk/keyboard_mapping.rs @@ -0,0 +1,424 @@ +//! RustDesk keyboard code-space mappings. +//! +//! Keep physical position codes separate from character and control-key values. In +//! particular, a Windows Set-1 scan code must never fall through to ASCII, VK, or +//! X11 interpretation. + +use super::protocol::ControlKey; +use crate::hid::CanonicalKey; + +/// Physical keyboard code space selected by the platform advertised to RustDesk. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum KeyboardCodeSpace { + WindowsSet1, +} + +pub(super) fn physical_key(code_space: KeyboardCodeSpace, code: u32) -> Option { + match code_space { + KeyboardCodeSpace::WindowsSet1 => windows_set1_key(code), + } +} + +/// Convert a Windows Set-1 make code as encoded by RustDesk. +/// +/// Ordinary codes occupy the low byte. Extended codes are encoded as `0xE0xx`. +/// Other multi-byte values (including the special Pause sequence) are rejected. +pub(super) fn windows_set1_key(scan_code: u32) -> Option { + use CanonicalKey as Key; + + match scan_code { + 0x01 => Some(Key::Escape), + 0x02 => Some(Key::Digit1), + 0x03 => Some(Key::Digit2), + 0x04 => Some(Key::Digit3), + 0x05 => Some(Key::Digit4), + 0x06 => Some(Key::Digit5), + 0x07 => Some(Key::Digit6), + 0x08 => Some(Key::Digit7), + 0x09 => Some(Key::Digit8), + 0x0A => Some(Key::Digit9), + 0x0B => Some(Key::Digit0), + 0x0C => Some(Key::Minus), + 0x0D => Some(Key::Equal), + 0x0E => Some(Key::Backspace), + 0x0F => Some(Key::Tab), + 0x10 => Some(Key::KeyQ), + 0x11 => Some(Key::KeyW), + 0x12 => Some(Key::KeyE), + 0x13 => Some(Key::KeyR), + 0x14 => Some(Key::KeyT), + 0x15 => Some(Key::KeyY), + 0x16 => Some(Key::KeyU), + 0x17 => Some(Key::KeyI), + 0x18 => Some(Key::KeyO), + 0x19 => Some(Key::KeyP), + 0x1A => Some(Key::BracketLeft), + 0x1B => Some(Key::BracketRight), + 0x1C => Some(Key::Enter), + 0x1D => Some(Key::ControlLeft), + 0x1E => Some(Key::KeyA), + 0x1F => Some(Key::KeyS), + 0x20 => Some(Key::KeyD), + 0x21 => Some(Key::KeyF), + 0x22 => Some(Key::KeyG), + 0x23 => Some(Key::KeyH), + 0x24 => Some(Key::KeyJ), + 0x25 => Some(Key::KeyK), + 0x26 => Some(Key::KeyL), + 0x27 => Some(Key::Semicolon), + 0x28 => Some(Key::Quote), + 0x29 => Some(Key::Backquote), + 0x2A => Some(Key::ShiftLeft), + 0x2B => Some(Key::Backslash), + 0x2C => Some(Key::KeyZ), + 0x2D => Some(Key::KeyX), + 0x2E => Some(Key::KeyC), + 0x2F => Some(Key::KeyV), + 0x30 => Some(Key::KeyB), + 0x31 => Some(Key::KeyN), + 0x32 => Some(Key::KeyM), + 0x33 => Some(Key::Comma), + 0x34 => Some(Key::Period), + 0x35 => Some(Key::Slash), + 0x36 => Some(Key::ShiftRight), + 0x37 => Some(Key::NumpadMultiply), + 0x38 => Some(Key::AltLeft), + 0x39 => Some(Key::Space), + 0x3A => Some(Key::CapsLock), + 0x3B => Some(Key::F1), + 0x3C => Some(Key::F2), + 0x3D => Some(Key::F3), + 0x3E => Some(Key::F4), + 0x3F => Some(Key::F5), + 0x40 => Some(Key::F6), + 0x41 => Some(Key::F7), + 0x42 => Some(Key::F8), + 0x43 => Some(Key::F9), + 0x44 => Some(Key::F10), + 0x45 => Some(Key::NumLock), + 0x46 => Some(Key::ScrollLock), + 0x47 => Some(Key::Numpad7), + 0x48 => Some(Key::Numpad8), + 0x49 => Some(Key::Numpad9), + 0x4A => Some(Key::NumpadSubtract), + 0x4B => Some(Key::Numpad4), + 0x4C => Some(Key::Numpad5), + 0x4D => Some(Key::Numpad6), + 0x4E => Some(Key::NumpadAdd), + 0x4F => Some(Key::Numpad1), + 0x50 => Some(Key::Numpad2), + 0x51 => Some(Key::Numpad3), + 0x52 => Some(Key::Numpad0), + 0x53 => Some(Key::NumpadDecimal), + 0x56 => Some(Key::IntlBackslash), + 0x57 => Some(Key::F11), + 0x58 => Some(Key::F12), + + 0xE01C => Some(Key::NumpadEnter), + 0xE01D => Some(Key::ControlRight), + 0xE035 => Some(Key::NumpadDivide), + 0xE037 => Some(Key::PrintScreen), + 0xE038 => Some(Key::AltRight), + 0xE047 => Some(Key::Home), + 0xE048 => Some(Key::ArrowUp), + 0xE049 => Some(Key::PageUp), + 0xE04B => Some(Key::ArrowLeft), + 0xE04D => Some(Key::ArrowRight), + 0xE04F => Some(Key::End), + 0xE050 => Some(Key::ArrowDown), + 0xE051 => Some(Key::PageDown), + 0xE052 => Some(Key::Insert), + 0xE053 => Some(Key::Delete), + 0xE05B => Some(Key::MetaLeft), + 0xE05C => Some(Key::MetaRight), + 0xE05D => Some(Key::ContextMenu), + _ => None, + } +} + +pub(super) fn control_key(key: i32) -> Option { + use CanonicalKey as Key; + + match key { + x if x == ControlKey::Alt as i32 => Some(Key::AltLeft), + x if x == ControlKey::Backspace as i32 => Some(Key::Backspace), + x if x == ControlKey::CapsLock as i32 => Some(Key::CapsLock), + x if x == ControlKey::Control as i32 => Some(Key::ControlLeft), + x if x == ControlKey::Delete as i32 => Some(Key::Delete), + x if x == ControlKey::DownArrow as i32 => Some(Key::ArrowDown), + x if x == ControlKey::End as i32 => Some(Key::End), + x if x == ControlKey::Escape as i32 => Some(Key::Escape), + x if x == ControlKey::F1 as i32 => Some(Key::F1), + x if x == ControlKey::F2 as i32 => Some(Key::F2), + x if x == ControlKey::F3 as i32 => Some(Key::F3), + x if x == ControlKey::F4 as i32 => Some(Key::F4), + x if x == ControlKey::F5 as i32 => Some(Key::F5), + x if x == ControlKey::F6 as i32 => Some(Key::F6), + x if x == ControlKey::F7 as i32 => Some(Key::F7), + x if x == ControlKey::F8 as i32 => Some(Key::F8), + x if x == ControlKey::F9 as i32 => Some(Key::F9), + x if x == ControlKey::F10 as i32 => Some(Key::F10), + x if x == ControlKey::F11 as i32 => Some(Key::F11), + x if x == ControlKey::F12 as i32 => Some(Key::F12), + x if x == ControlKey::Home as i32 => Some(Key::Home), + x if x == ControlKey::LeftArrow as i32 => Some(Key::ArrowLeft), + x if x == ControlKey::Meta as i32 => Some(Key::MetaLeft), + x if x == ControlKey::PageDown as i32 => Some(Key::PageDown), + x if x == ControlKey::PageUp as i32 => Some(Key::PageUp), + x if x == ControlKey::Return as i32 => Some(Key::Enter), + x if x == ControlKey::RightArrow as i32 => Some(Key::ArrowRight), + x if x == ControlKey::Shift as i32 => Some(Key::ShiftLeft), + x if x == ControlKey::Space as i32 => Some(Key::Space), + x if x == ControlKey::Tab as i32 => Some(Key::Tab), + x if x == ControlKey::UpArrow as i32 => Some(Key::ArrowUp), + x if x == ControlKey::Numpad0 as i32 => Some(Key::Numpad0), + x if x == ControlKey::Numpad1 as i32 => Some(Key::Numpad1), + x if x == ControlKey::Numpad2 as i32 => Some(Key::Numpad2), + x if x == ControlKey::Numpad3 as i32 => Some(Key::Numpad3), + x if x == ControlKey::Numpad4 as i32 => Some(Key::Numpad4), + x if x == ControlKey::Numpad5 as i32 => Some(Key::Numpad5), + x if x == ControlKey::Numpad6 as i32 => Some(Key::Numpad6), + x if x == ControlKey::Numpad7 as i32 => Some(Key::Numpad7), + x if x == ControlKey::Numpad8 as i32 => Some(Key::Numpad8), + x if x == ControlKey::Numpad9 as i32 => Some(Key::Numpad9), + x if x == ControlKey::Pause as i32 => Some(Key::Pause), + x if x == ControlKey::Snapshot as i32 => Some(Key::PrintScreen), + x if x == ControlKey::Insert as i32 => Some(Key::Insert), + x if x == ControlKey::Scroll as i32 => Some(Key::ScrollLock), + x if x == ControlKey::NumLock as i32 => Some(Key::NumLock), + x if x == ControlKey::RWin as i32 => Some(Key::MetaRight), + x if x == ControlKey::Apps as i32 => Some(Key::ContextMenu), + x if x == ControlKey::Multiply as i32 => Some(Key::NumpadMultiply), + x if x == ControlKey::Add as i32 => Some(Key::NumpadAdd), + x if x == ControlKey::Subtract as i32 => Some(Key::NumpadSubtract), + x if x == ControlKey::Decimal as i32 => Some(Key::NumpadDecimal), + x if x == ControlKey::Divide as i32 => Some(Key::NumpadDivide), + x if x == ControlKey::NumpadEnter as i32 => Some(Key::NumpadEnter), + x if x == ControlKey::RShift as i32 => Some(Key::ShiftRight), + x if x == ControlKey::RControl as i32 => Some(Key::ControlRight), + x if x == ControlKey::RAlt as i32 => Some(Key::AltRight), + _ => None, + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) struct CharacterMapping { + pub key: CanonicalKey, + pub needs_shift: bool, +} + +pub(super) fn character(ch: u32) -> Option { + use CanonicalKey as Key; + + let plain = |key| CharacterMapping { + key, + needs_shift: false, + }; + let shifted = |key| CharacterMapping { + key, + needs_shift: true, + }; + + Some(match ch { + 0x61..=0x7A => plain(CanonicalKey::from_hid_usage((ch - 0x61 + 0x04) as u8)?), + 0x41..=0x5A => shifted(CanonicalKey::from_hid_usage((ch - 0x41 + 0x04) as u8)?), + 0x30 => plain(Key::Digit0), + 0x31 => plain(Key::Digit1), + 0x32 => plain(Key::Digit2), + 0x33 => plain(Key::Digit3), + 0x34 => plain(Key::Digit4), + 0x35 => plain(Key::Digit5), + 0x36 => plain(Key::Digit6), + 0x37 => plain(Key::Digit7), + 0x38 => plain(Key::Digit8), + 0x39 => plain(Key::Digit9), + 0x20 => plain(Key::Space), + 0x0D | 0x0A => plain(Key::Enter), + 0x09 => plain(Key::Tab), + 0x1B => plain(Key::Escape), + 0x08 => plain(Key::Backspace), + 0x7F => plain(Key::Delete), + 0x2D => plain(Key::Minus), + 0x3D => plain(Key::Equal), + 0x5B => plain(Key::BracketLeft), + 0x5D => plain(Key::BracketRight), + 0x5C => plain(Key::Backslash), + 0x3B => plain(Key::Semicolon), + 0x27 => plain(Key::Quote), + 0x60 => plain(Key::Backquote), + 0x2C => plain(Key::Comma), + 0x2E => plain(Key::Period), + 0x2F => plain(Key::Slash), + 0x21 => shifted(Key::Digit1), + 0x40 => shifted(Key::Digit2), + 0x23 => shifted(Key::Digit3), + 0x24 => shifted(Key::Digit4), + 0x25 => shifted(Key::Digit5), + 0x5E => shifted(Key::Digit6), + 0x26 => shifted(Key::Digit7), + 0x2A => shifted(Key::Digit8), + 0x28 => shifted(Key::Digit9), + 0x29 => shifted(Key::Digit0), + 0x5F => shifted(Key::Minus), + 0x2B => shifted(Key::Equal), + 0x7B => shifted(Key::BracketLeft), + 0x7D => shifted(Key::BracketRight), + 0x7C => shifted(Key::Backslash), + 0x3A => shifted(Key::Semicolon), + 0x22 => shifted(Key::Quote), + 0x7E => shifted(Key::Backquote), + 0x3C => shifted(Key::Comma), + 0x3E => shifted(Key::Period), + 0x3F => shifted(Key::Slash), + _ => return None, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn windows_set1_matrix_is_complete() { + use CanonicalKey as Key; + + let cases = [ + (0x01, Key::Escape), + (0x02, Key::Digit1), + (0x03, Key::Digit2), + (0x04, Key::Digit3), + (0x05, Key::Digit4), + (0x06, Key::Digit5), + (0x07, Key::Digit6), + (0x08, Key::Digit7), + (0x09, Key::Digit8), + (0x0A, Key::Digit9), + (0x0B, Key::Digit0), + (0x0C, Key::Minus), + (0x0D, Key::Equal), + (0x0E, Key::Backspace), + (0x0F, Key::Tab), + (0x10, Key::KeyQ), + (0x11, Key::KeyW), + (0x12, Key::KeyE), + (0x13, Key::KeyR), + (0x14, Key::KeyT), + (0x15, Key::KeyY), + (0x16, Key::KeyU), + (0x17, Key::KeyI), + (0x18, Key::KeyO), + (0x19, Key::KeyP), + (0x1A, Key::BracketLeft), + (0x1B, Key::BracketRight), + (0x1C, Key::Enter), + (0x1D, Key::ControlLeft), + (0x1E, Key::KeyA), + (0x1F, Key::KeyS), + (0x20, Key::KeyD), + (0x21, Key::KeyF), + (0x22, Key::KeyG), + (0x23, Key::KeyH), + (0x24, Key::KeyJ), + (0x25, Key::KeyK), + (0x26, Key::KeyL), + (0x27, Key::Semicolon), + (0x28, Key::Quote), + (0x29, Key::Backquote), + (0x2A, Key::ShiftLeft), + (0x2B, Key::Backslash), + (0x2C, Key::KeyZ), + (0x2D, Key::KeyX), + (0x2E, Key::KeyC), + (0x2F, Key::KeyV), + (0x30, Key::KeyB), + (0x31, Key::KeyN), + (0x32, Key::KeyM), + (0x33, Key::Comma), + (0x34, Key::Period), + (0x35, Key::Slash), + (0x36, Key::ShiftRight), + (0x37, Key::NumpadMultiply), + (0x38, Key::AltLeft), + (0x39, Key::Space), + (0x3A, Key::CapsLock), + (0x3B, Key::F1), + (0x3C, Key::F2), + (0x3D, Key::F3), + (0x3E, Key::F4), + (0x3F, Key::F5), + (0x40, Key::F6), + (0x41, Key::F7), + (0x42, Key::F8), + (0x43, Key::F9), + (0x44, Key::F10), + (0x45, Key::NumLock), + (0x46, Key::ScrollLock), + (0x47, Key::Numpad7), + (0x48, Key::Numpad8), + (0x49, Key::Numpad9), + (0x4A, Key::NumpadSubtract), + (0x4B, Key::Numpad4), + (0x4C, Key::Numpad5), + (0x4D, Key::Numpad6), + (0x4E, Key::NumpadAdd), + (0x4F, Key::Numpad1), + (0x50, Key::Numpad2), + (0x51, Key::Numpad3), + (0x52, Key::Numpad0), + (0x53, Key::NumpadDecimal), + (0x56, Key::IntlBackslash), + (0x57, Key::F11), + (0x58, Key::F12), + (0xE01C, Key::NumpadEnter), + (0xE01D, Key::ControlRight), + (0xE035, Key::NumpadDivide), + (0xE037, Key::PrintScreen), + (0xE038, Key::AltRight), + (0xE047, Key::Home), + (0xE048, Key::ArrowUp), + (0xE049, Key::PageUp), + (0xE04B, Key::ArrowLeft), + (0xE04D, Key::ArrowRight), + (0xE04F, Key::End), + (0xE050, Key::ArrowDown), + (0xE051, Key::PageDown), + (0xE052, Key::Insert), + (0xE053, Key::Delete), + (0xE05B, Key::MetaLeft), + (0xE05C, Key::MetaRight), + (0xE05D, Key::ContextMenu), + ]; + + for (scan_code, expected) in cases { + assert_eq!( + windows_set1_key(scan_code), + Some(expected), + "0x{scan_code:04X}" + ); + } + } + + #[test] + fn windows_set1_rejects_unknown_and_invalid_multibyte_codes() { + for scan_code in [0, 0x54, 0x59, 0xD3, 0xE000, 0xE054, 0xE11D45, 0x0101] { + assert_eq!(windows_set1_key(scan_code), None, "0x{scan_code:X}"); + } + } + + #[test] + fn audited_control_keys_map_without_hid_round_trip() { + assert_eq!( + control_key(ControlKey::RWin as i32), + Some(CanonicalKey::MetaRight) + ); + assert_eq!( + control_key(ControlKey::Apps as i32), + Some(CanonicalKey::ContextMenu) + ); + assert_eq!( + control_key(ControlKey::Snapshot as i32), + Some(CanonicalKey::PrintScreen) + ); + assert_eq!(control_key(ControlKey::Power as i32), None); + } +} diff --git a/src/rustdesk/mod.rs b/src/rustdesk/mod.rs index 3e443641..035df233 100644 --- a/src/rustdesk/mod.rs +++ b/src/rustdesk/mod.rs @@ -6,6 +6,7 @@ pub mod connection; pub mod crypto; pub mod frame_adapters; pub mod hid_adapter; +mod keyboard_mapping; pub mod protocol; pub mod punch; pub mod rendezvous;