mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-09-13 02:54:26 +08:00
fix(rustdesk): 按键盘模式和码空间转换 HID 输入
将对外兼容平台与 Windows Set-1 扫描码映射绑定,分离物理键、字符和控制键语义,拒绝未知码值跨码空间回退。 保留按下、释放、重复输入与临时 Shift 的状态语义,补充键位矩阵和事件转换回归测试,并记录输入模式及原始码值。
This commit is contained in:
@@ -26,7 +26,9 @@ use super::bytes_codec::{read_frame_with_limit, write_frame_vectored};
|
|||||||
use super::config::RustDeskConfig;
|
use super::config::RustDeskConfig;
|
||||||
use super::crypto::{self, KeyPair, SigningKeyPair};
|
use super::crypto::{self, KeyPair, SigningKeyPair};
|
||||||
use super::frame_adapters::{AudioFrameAdapter, VideoCodec, VideoFrameAdapter};
|
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::{
|
use super::protocol::{
|
||||||
decode_message, login_response, message, misc, Clipboard, ControlKey, DisplayInfo, Hash,
|
decode_message, login_response, message, misc, Clipboard, ControlKey, DisplayInfo, Hash,
|
||||||
HbbMessage, IdPk, KeyEvent, LoginRequest, LoginResponse, Misc, MouseEvent, OptionMessage,
|
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 MAX_PASSWORD_ATTEMPTS: u8 = 5;
|
||||||
const CONNECTION_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(3);
|
const CONNECTION_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(3);
|
||||||
|
|
||||||
/// Advertised RustDesk version for client compatibility.
|
/// RustDesk-facing identity and the physical key code space implied by it.
|
||||||
const RUSTDESK_COMPAT_VERSION: &str = "1.4.5";
|
struct RustDeskCompatibility {
|
||||||
// Advertised platform for RustDesk clients. This affects which UI options are shown.
|
version: &'static str,
|
||||||
const RUSTDESK_COMPAT_PLATFORM: &str = "Windows";
|
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
|
/// Input event throttler
|
||||||
///
|
///
|
||||||
@@ -1124,11 +1162,11 @@ impl Connection {
|
|||||||
let mut peer_info = PeerInfo::new();
|
let mut peer_info = PeerInfo::new();
|
||||||
peer_info.username = "one-kvm".to_string();
|
peer_info.username = "one-kvm".to_string();
|
||||||
peer_info.hostname = hostname_from_etc();
|
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.displays.push(display_info);
|
||||||
peer_info.current_display = 0;
|
peer_info.current_display = 0;
|
||||||
peer_info.sas_enabled = false;
|
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);
|
peer_info.encoding = protobuf::MessageField::some(encoding);
|
||||||
|
|
||||||
let mut login_response = LoginResponse::new();
|
let mut login_response = LoginResponse::new();
|
||||||
@@ -1265,9 +1303,15 @@ impl Connection {
|
|||||||
|
|
||||||
/// Handle key event
|
/// Handle key event
|
||||||
async fn handle_key_event(&mut self, ke: &KeyEvent) -> anyhow::Result<()> {
|
async fn handle_key_event(&mut self, ke: &KeyEvent) -> anyhow::Result<()> {
|
||||||
|
let (union_type, raw_value) = key_event_union_details(ke);
|
||||||
debug!(
|
debug!(
|
||||||
"Key event: down={}, press={}, chr={:?}, modifiers={:?}",
|
mode = ke.mode.value(),
|
||||||
ke.down, ke.press, ke.union, ke.modifiers
|
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
|
// Check for CapsLock state change in modifiers
|
||||||
@@ -1305,15 +1349,21 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert RustDesk key event to One-KVM key events
|
// 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 !kb_events.is_empty() {
|
||||||
if let Some(ref hid) = self.hid {
|
if let Some(ref hid) = self.hid {
|
||||||
for kb_event in kb_events {
|
for kb_event in kb_events {
|
||||||
debug!(
|
debug!(
|
||||||
"Converted to HID: key=0x{:02X}, event_type={:?}, modifiers={:02X}",
|
mode = ke.mode.value(),
|
||||||
kb_event.key.to_hid_usage(),
|
union = union_type,
|
||||||
kb_event.event_type,
|
raw = %raw_value,
|
||||||
kb_event.modifiers.to_hid_byte()
|
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 {
|
if let Err(e) = hid.send_keyboard(kb_event).await {
|
||||||
@@ -1324,7 +1374,12 @@ impl Connection {
|
|||||||
debug!("HID controller not available, skipping key event");
|
debug!("HID controller not available, skipping key event");
|
||||||
}
|
}
|
||||||
} else {
|
} 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(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
use super::keyboard_mapping::{self, CharacterMapping, KeyboardCodeSpace};
|
||||||
use super::protocol::hbb::message::key_event as ke_union;
|
use super::protocol::hbb::message::key_event as ke_union;
|
||||||
use super::protocol::{ControlKey, KeyEvent, KeyboardMode, MouseEvent};
|
use super::protocol::{ControlKey, KeyEvent, KeyboardMode, MouseEvent};
|
||||||
use crate::hid::{
|
use crate::hid::{
|
||||||
CanonicalKey, KeyEventType, KeyboardEvent, KeyboardModifiers, MouseButton,
|
CanonicalKey, KeyEventType, KeyboardEvent, KeyboardModifiers, MouseButton,
|
||||||
MouseEvent as OneKvmMouseEvent, MouseEventType,
|
MouseEvent as OneKvmMouseEvent, MouseEventType,
|
||||||
};
|
};
|
||||||
use protobuf::Enum;
|
use tracing::debug;
|
||||||
|
|
||||||
pub mod mouse_type {
|
pub mod mouse_type {
|
||||||
pub const MOVE: i32 = 0;
|
pub const MOVE: i32 = 0;
|
||||||
@@ -105,14 +106,23 @@ fn button_id_to_button(button_id: i32) -> Option<MouseButton> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert using the code space associated with One-KVM's compatibility platform.
|
||||||
pub fn convert_key_events(event: &KeyEvent) -> Vec<KeyboardEvent> {
|
pub fn convert_key_events(event: &KeyEvent) -> Vec<KeyboardEvent> {
|
||||||
|
convert_key_events_in_code_space(event, KeyboardCodeSpace::WindowsSet1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn convert_key_events_in_code_space(
|
||||||
|
event: &KeyEvent,
|
||||||
|
code_space: KeyboardCodeSpace,
|
||||||
|
) -> Vec<KeyboardEvent> {
|
||||||
let base_modifiers = if is_modifier_control_key(event) {
|
let base_modifiers = if is_modifier_control_key(event) {
|
||||||
KeyboardModifiers::default()
|
KeyboardModifiers::default()
|
||||||
} else {
|
} else {
|
||||||
parse_modifiers(event)
|
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();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,13 +145,12 @@ pub fn convert_key_events(event: &KeyEvent) -> Vec<KeyboardEvent> {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
let event_type = if event.down {
|
|
||||||
KeyEventType::Down
|
|
||||||
} else {
|
|
||||||
KeyEventType::Up
|
|
||||||
};
|
|
||||||
vec![KeyboardEvent {
|
vec![KeyboardEvent {
|
||||||
event_type,
|
event_type: if event.down {
|
||||||
|
KeyEventType::Down
|
||||||
|
} else {
|
||||||
|
KeyEventType::Up
|
||||||
|
},
|
||||||
key: mapping.key,
|
key: mapping.key,
|
||||||
modifiers: mapping.modifiers,
|
modifiers: mapping.modifiers,
|
||||||
}]
|
}]
|
||||||
@@ -159,534 +168,337 @@ struct KeyMapping {
|
|||||||
added_shift: bool,
|
added_shift: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_event_to_hid(event: &KeyEvent, modifiers: KeyboardModifiers) -> Option<KeyMapping> {
|
fn key_event_to_mapping(
|
||||||
|
event: &KeyEvent,
|
||||||
|
code_space: KeyboardCodeSpace,
|
||||||
|
modifiers: KeyboardModifiers,
|
||||||
|
) -> Option<KeyMapping> {
|
||||||
|
let mode = event.mode.enum_value().ok()?;
|
||||||
match &event.union {
|
match &event.union {
|
||||||
Some(ke_union::Union::ControlKey(ck)) => {
|
Some(ke_union::Union::ControlKey(key)) => {
|
||||||
let key = CanonicalKey::from_hid_usage(control_key_to_hid(ck.value())?)?;
|
plain_mapping(keyboard_mapping::control_key(key.value())?, modifiers)
|
||||||
Some(KeyMapping {
|
}
|
||||||
key,
|
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,
|
modifiers,
|
||||||
added_shift: false,
|
),
|
||||||
})
|
KeyboardMode::Legacy | KeyboardMode::Auto => legacy_character_mapping(*code, modifiers),
|
||||||
}
|
},
|
||||||
Some(ke_union::Union::Chr(chr)) => {
|
Some(ke_union::Union::Seq(_)) | Some(ke_union::Union::Win2winHotkey(_)) | None => None,
|
||||||
if event.mode.value() != KeyboardMode::Map.value() {
|
}
|
||||||
if let Some(mapping) = shifted_printable_char_to_hid(*chr, modifiers) {
|
}
|
||||||
return Some(mapping);
|
|
||||||
}
|
fn plain_mapping(key: CanonicalKey, modifiers: KeyboardModifiers) -> Option<KeyMapping> {
|
||||||
}
|
Some(KeyMapping {
|
||||||
let key = CanonicalKey::from_hid_usage(keycode_to_hid(*chr)?)?;
|
key,
|
||||||
Some(KeyMapping {
|
modifiers,
|
||||||
key,
|
added_shift: false,
|
||||||
modifiers,
|
})
|
||||||
added_shift: false,
|
}
|
||||||
})
|
|
||||||
}
|
fn character_mapping(ch: u32, modifiers: KeyboardModifiers) -> Option<KeyMapping> {
|
||||||
Some(ke_union::Union::Unicode(unicode)) => {
|
let CharacterMapping { key, needs_shift } = keyboard_mapping::character(ch)?;
|
||||||
let mapping = printable_char_to_hid(*unicode, modifiers)?;
|
if !needs_shift {
|
||||||
Some(mapping)
|
return plain_mapping(key, modifiers);
|
||||||
}
|
}
|
||||||
_ => None,
|
|
||||||
|
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<KeyMapping> {
|
||||||
|
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 {
|
fn is_modifier_control_key(event: &KeyEvent) -> bool {
|
||||||
if let Some(ke_union::Union::ControlKey(ck)) = &event.union {
|
let Some(ke_union::Union::ControlKey(key)) = &event.union else {
|
||||||
let val = ck.value();
|
return false;
|
||||||
return val == ControlKey::Control.value()
|
};
|
||||||
|| val == ControlKey::Shift.value()
|
matches!(
|
||||||
|| val == ControlKey::Alt.value()
|
key.enum_value(),
|
||||||
|| val == ControlKey::Meta.value()
|
Ok(ControlKey::Control)
|
||||||
|| val == ControlKey::RControl.value()
|
| Ok(ControlKey::Shift)
|
||||||
|| val == ControlKey::RShift.value()
|
| Ok(ControlKey::Alt)
|
||||||
|| val == ControlKey::RAlt.value();
|
| Ok(ControlKey::Meta)
|
||||||
}
|
| Ok(ControlKey::RControl)
|
||||||
false
|
| Ok(ControlKey::RShift)
|
||||||
|
| Ok(ControlKey::RAlt)
|
||||||
|
| Ok(ControlKey::RWin)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_modifiers(event: &KeyEvent) -> KeyboardModifiers {
|
fn parse_modifiers(event: &KeyEvent) -> KeyboardModifiers {
|
||||||
let mut modifiers = KeyboardModifiers::default();
|
let mut modifiers = KeyboardModifiers::default();
|
||||||
|
|
||||||
for modifier in &event.modifiers {
|
for modifier in &event.modifiers {
|
||||||
let val = modifier.value();
|
match modifier.enum_value() {
|
||||||
match val {
|
Ok(ControlKey::Control) => modifiers.left_ctrl = true,
|
||||||
x if x == ControlKey::Control.value() => modifiers.left_ctrl = true,
|
Ok(ControlKey::Shift) => modifiers.left_shift = true,
|
||||||
x if x == ControlKey::Shift.value() => modifiers.left_shift = true,
|
Ok(ControlKey::Alt) => modifiers.left_alt = true,
|
||||||
x if x == ControlKey::Alt.value() => modifiers.left_alt = true,
|
Ok(ControlKey::Meta) => modifiers.left_meta = true,
|
||||||
x if x == ControlKey::Meta.value() => modifiers.left_meta = true,
|
Ok(ControlKey::RControl) => modifiers.right_ctrl = true,
|
||||||
x if x == ControlKey::RControl.value() => modifiers.right_ctrl = true,
|
Ok(ControlKey::RShift) => modifiers.right_shift = true,
|
||||||
x if x == ControlKey::RShift.value() => modifiers.right_shift = true,
|
Ok(ControlKey::RAlt) => modifiers.right_alt = true,
|
||||||
x if x == ControlKey::RAlt.value() => modifiers.right_alt = true,
|
Ok(ControlKey::RWin) => modifiers.right_meta = true,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
modifiers
|
modifiers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_shift(mut modifiers: KeyboardModifiers) -> KeyboardModifiers {
|
fn log_rejected_key_event(event: &KeyEvent, code_space: KeyboardCodeSpace) {
|
||||||
modifiers.left_shift = true;
|
let mode = event.mode.value();
|
||||||
modifiers
|
match &event.union {
|
||||||
}
|
Some(ke_union::Union::ControlKey(key)) => debug!(
|
||||||
|
mode,
|
||||||
fn shifted_mapping(key: CanonicalKey, modifiers: KeyboardModifiers) -> KeyMapping {
|
union = "ControlKey",
|
||||||
let added_shift = !modifiers.left_shift && !modifiers.right_shift;
|
raw = format_args!("0x{:X}", key.value()),
|
||||||
KeyMapping {
|
?code_space,
|
||||||
key,
|
"Dropping unsupported RustDesk keyboard event"
|
||||||
modifiers: with_shift(modifiers),
|
),
|
||||||
added_shift,
|
Some(ke_union::Union::Chr(code)) => debug!(
|
||||||
}
|
mode,
|
||||||
}
|
union = "Chr",
|
||||||
|
raw = format_args!("0x{code:X}"),
|
||||||
fn plain_mapping(key: CanonicalKey, modifiers: KeyboardModifiers) -> KeyMapping {
|
?code_space,
|
||||||
KeyMapping {
|
"Dropping unsupported RustDesk keyboard event"
|
||||||
key,
|
),
|
||||||
modifiers,
|
Some(ke_union::Union::Unicode(code)) => debug!(
|
||||||
added_shift: false,
|
mode,
|
||||||
}
|
union = "Unicode",
|
||||||
}
|
raw = format_args!("0x{code:X}"),
|
||||||
|
?code_space,
|
||||||
fn shifted_printable_char_to_hid(ch: u32, modifiers: KeyboardModifiers) -> Option<KeyMapping> {
|
"Dropping unsupported RustDesk keyboard event"
|
||||||
match ch {
|
),
|
||||||
33 => Some(shifted_mapping(CanonicalKey::Digit1, modifiers)),
|
Some(ke_union::Union::Seq(seq)) => {
|
||||||
64 => Some(shifted_mapping(CanonicalKey::Digit2, modifiers)),
|
debug!(mode, union = "Seq", raw = ?seq, ?code_space, "Dropping unsupported RustDesk keyboard event")
|
||||||
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<KeyMapping> {
|
|
||||||
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<u8> {
|
|
||||||
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<u8> {
|
|
||||||
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<u8> {
|
|
||||||
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<u8> {
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
0x30 => Some(0x27), // 0
|
Some(ke_union::Union::Win2winHotkey(code)) => debug!(
|
||||||
0x31..=0x39 => Some((vk - 0x31 + 0x1E) as u8), // 1-9
|
mode,
|
||||||
0x60 => Some(0x62), // Numpad 0
|
union = "Win2winHotkey",
|
||||||
0x61..=0x69 => Some((vk - 0x61 + 0x59) as u8), // Numpad 1-9
|
raw = format_args!("0x{code:X}"),
|
||||||
0x6A => Some(0x55), // Numpad *
|
?code_space,
|
||||||
0x6B => Some(0x57), // Numpad +
|
"Dropping unsupported RustDesk keyboard event"
|
||||||
0x6D => Some(0x56), // Numpad -
|
),
|
||||||
0x6E => Some(0x63), // Numpad .
|
None => debug!(
|
||||||
0x6F => Some(0x54), // Numpad /
|
mode,
|
||||||
0x70..=0x7B => Some((vk - 0x70 + 0x3A) as u8),
|
union = "None",
|
||||||
0x08 => Some(0x2A), // Backspace
|
?code_space,
|
||||||
0x09 => Some(0x2B), // Tab
|
"Dropping unsupported RustDesk keyboard event"
|
||||||
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<u8> {
|
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use protobuf::EnumOrUnknown;
|
||||||
|
|
||||||
#[test]
|
fn key_event(mode: KeyboardMode, union: ke_union::Union, down: bool) -> KeyEvent {
|
||||||
fn test_control_key_mapping() {
|
let mut event = KeyEvent::new();
|
||||||
assert_eq!(control_key_to_hid(ControlKey::Escape.value()), Some(0x29));
|
event.mode = EnumOrUnknown::new(mode);
|
||||||
assert_eq!(control_key_to_hid(ControlKey::Return.value()), Some(0x28));
|
event.union = Some(union);
|
||||||
assert_eq!(control_key_to_hid(ControlKey::Space.value()), Some(0x2C));
|
event.down = down;
|
||||||
|
event
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_chr(code: u32, down: bool) -> KeyEvent {
|
||||||
|
key_event(KeyboardMode::Map, ke_union::Union::Chr(code), down)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_convert_mouse_move() {
|
fn mouse_events_keep_existing_semantics() {
|
||||||
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() {
|
|
||||||
let mut event = MouseEvent::new();
|
let mut event = MouseEvent::new();
|
||||||
event.x = -12;
|
event.x = -12;
|
||||||
event.y = 8;
|
event.y = 8;
|
||||||
event.mask = mouse_type::MOVE_RELATIVE;
|
event.mask = mouse_type::MOVE_RELATIVE;
|
||||||
|
|
||||||
let events = convert_mouse_event(&event, 1920, 1080);
|
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].event_type, MouseEventType::Move);
|
||||||
assert_eq!(events[0].x, -12);
|
assert_eq!((events[0].x, events[0].y), (-12, 8));
|
||||||
assert_eq!(events[0].y, 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]
|
#[test]
|
||||||
fn test_convert_key_event() {
|
fn map_delete_generates_only_delete_down_and_up() {
|
||||||
use protobuf::EnumOrUnknown;
|
let down = convert_key_events(&map_chr(0xE053, true));
|
||||||
let mut key_event = KeyEvent::new();
|
let up = convert_key_events(&map_chr(0xE053, false));
|
||||||
key_event.down = true;
|
assert_eq!((down.len(), up.len()), (1, 1));
|
||||||
key_event.press = false;
|
assert_eq!(
|
||||||
key_event.union = Some(ke_union::Union::ControlKey(EnumOrUnknown::new(
|
(down[0].event_type, up[0].event_type),
|
||||||
ControlKey::Return,
|
(KeyEventType::Down, KeyEventType::Up)
|
||||||
)));
|
);
|
||||||
|
assert_eq!(
|
||||||
let result = convert_key_event(&key_event);
|
(down[0].key, up[0].key),
|
||||||
assert!(result.is_some());
|
(CanonicalKey::Delete, CanonicalKey::Delete)
|
||||||
|
);
|
||||||
let kb_event = result.unwrap();
|
assert_eq!(
|
||||||
assert_eq!(kb_event.event_type, KeyEventType::Down);
|
(down[0].key.to_hid_usage(), up[0].key.to_hid_usage()),
|
||||||
assert_eq!(kb_event.key, CanonicalKey::Enter);
|
(0x4C, 0x4C)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_convert_at_press_to_shift_digit2() {
|
fn map_scan_codes_do_not_become_ascii_digits() {
|
||||||
let mut key_event = KeyEvent::new();
|
let alt = convert_key_events(&map_chr(0x38, true));
|
||||||
key_event.press = true;
|
let shift = convert_key_events(&map_chr(0x36, true));
|
||||||
key_event.union = Some(ke_union::Union::Unicode('@' as u32));
|
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.len(), 2);
|
||||||
assert_eq!(events[0].event_type, KeyEventType::Down);
|
assert_eq!(
|
||||||
assert_eq!(events[0].key, CanonicalKey::Digit2);
|
(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!(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);
|
assert!(!events[1].modifiers.left_shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_convert_shifted_chr_to_shift_digit2() {
|
fn modifier_control_key_does_not_duplicate_state() {
|
||||||
let mut key_event = KeyEvent::new();
|
let mut event = key_event(
|
||||||
key_event.down = true;
|
KeyboardMode::Legacy,
|
||||||
key_event.union = Some(ke_union::Union::Chr('@' as u32));
|
ke_union::Union::ControlKey(EnumOrUnknown::new(ControlKey::RWin)),
|
||||||
|
true,
|
||||||
let events = convert_key_events(&key_event);
|
);
|
||||||
assert_eq!(events.len(), 1);
|
event.modifiers.push(EnumOrUnknown::new(ControlKey::RWin));
|
||||||
assert_eq!(events[0].event_type, KeyEventType::Down);
|
let events = convert_key_events(&event);
|
||||||
assert_eq!(events[0].key, CanonicalKey::Digit2);
|
assert_eq!(events[0].key, CanonicalKey::MetaRight);
|
||||||
assert!(events[0].modifiers.left_shift);
|
assert_eq!(events[0].modifiers, KeyboardModifiers::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_convert_map_mode_chr_as_physical_key() {
|
fn legacy_delete_and_audited_controls_are_mapped() {
|
||||||
use protobuf::EnumOrUnknown;
|
for (control, expected) in [
|
||||||
let mut key_event = KeyEvent::new();
|
(ControlKey::Delete, CanonicalKey::Delete),
|
||||||
key_event.down = true;
|
(ControlKey::Snapshot, CanonicalKey::PrintScreen),
|
||||||
key_event.mode = EnumOrUnknown::new(KeyboardMode::Map);
|
(ControlKey::RWin, CanonicalKey::MetaRight),
|
||||||
key_event.union = Some(ke_union::Union::Chr(0x41));
|
(ControlKey::Apps, CanonicalKey::ContextMenu),
|
||||||
|
] {
|
||||||
let events = convert_key_events(&key_event);
|
let event = key_event(
|
||||||
assert_eq!(events.len(), 1);
|
KeyboardMode::Legacy,
|
||||||
assert_eq!(events[0].event_type, KeyEventType::Down);
|
ke_union::Union::ControlKey(EnumOrUnknown::new(control)),
|
||||||
assert_eq!(events[0].key, CanonicalKey::KeyA);
|
true,
|
||||||
assert!(!events[0].modifiers.left_shift);
|
);
|
||||||
|
assert_eq!(convert_key_events(&event)[0].key, expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_convert_press_generates_down_and_up() {
|
fn rejects_unknown_modes_and_unsupported_unions() {
|
||||||
use protobuf::EnumOrUnknown;
|
let mut unknown = map_chr(0x1E, true);
|
||||||
let mut key_event = KeyEvent::new();
|
unknown.mode = EnumOrUnknown::from_i32(99);
|
||||||
key_event.press = true;
|
assert!(convert_key_events(&unknown).is_empty());
|
||||||
key_event.union = Some(ke_union::Union::ControlKey(EnumOrUnknown::new(
|
|
||||||
ControlKey::Return,
|
|
||||||
)));
|
|
||||||
|
|
||||||
let events = convert_key_events(&key_event);
|
let seq = key_event(
|
||||||
assert_eq!(events.len(), 2);
|
KeyboardMode::Translate,
|
||||||
assert_eq!(events[0].event_type, KeyEventType::Down);
|
ke_union::Union::Seq("a".to_string()),
|
||||||
assert_eq!(events[1].event_type, KeyEventType::Up);
|
true,
|
||||||
assert_eq!(events[0].key, CanonicalKey::Enter);
|
);
|
||||||
assert_eq!(events[1].key, CanonicalKey::Enter);
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
424
src/rustdesk/keyboard_mapping.rs
Normal file
424
src/rustdesk/keyboard_mapping.rs
Normal file
@@ -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<CanonicalKey> {
|
||||||
|
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<CanonicalKey> {
|
||||||
|
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<CanonicalKey> {
|
||||||
|
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<CharacterMapping> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ pub mod connection;
|
|||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
pub mod frame_adapters;
|
pub mod frame_adapters;
|
||||||
pub mod hid_adapter;
|
pub mod hid_adapter;
|
||||||
|
mod keyboard_mapping;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
pub mod punch;
|
pub mod punch;
|
||||||
pub mod rendezvous;
|
pub mod rendezvous;
|
||||||
|
|||||||
Reference in New Issue
Block a user