Files
One-KVM/src/rustdesk/hid_adapter.rs
mofeng-git 3749bafae0 fix(rustdesk): 按键盘模式和码空间转换 HID 输入
将对外兼容平台与 Windows Set-1 扫描码映射绑定,分离物理键、字符和控制键语义,拒绝未知码值跨码空间回退。

保留按下、释放、重复输入与临时 Shift 的状态语义,补充键位矩阵和事件转换回归测试,并记录输入模式及原始码值。
2026-09-05 14:10:53 +08:00

505 lines
16 KiB
Rust

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 tracing::debug;
pub mod mouse_type {
pub const MOVE: i32 = 0;
pub const DOWN: i32 = 1;
pub const UP: i32 = 2;
pub const WHEEL: i32 = 3;
pub const TRACKPAD: i32 = 4;
pub const MOVE_RELATIVE: i32 = 5;
}
pub mod mouse_button {
pub const LEFT: i32 = 0x01;
pub const RIGHT: i32 = 0x02;
pub const WHEEL: i32 = 0x04;
pub const BACK: i32 = 0x08;
pub const FORWARD: i32 = 0x10;
}
pub fn convert_mouse_event(
event: &MouseEvent,
screen_width: u32,
screen_height: u32,
) -> Vec<OneKvmMouseEvent> {
let mut events = Vec::new();
let event_type = event.mask & 0x07;
let button_id = event.mask >> 3;
match event_type {
mouse_type::MOVE => {
let x = event.x.max(0) as u32;
let y = event.y.max(0) as u32;
let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32;
let abs_y = ((y as u64 * 32767) / screen_height.max(1) as u64) as i32;
events.push(OneKvmMouseEvent {
event_type: MouseEventType::MoveAbs,
x: abs_x,
y: abs_y,
button: None,
scroll: 0,
});
}
mouse_type::MOVE_RELATIVE => {
events.push(OneKvmMouseEvent {
event_type: MouseEventType::Move,
x: event.x,
y: event.y,
button: None,
scroll: 0,
});
}
mouse_type::DOWN => {
if let Some(button) = button_id_to_button(button_id) {
events.push(OneKvmMouseEvent {
event_type: MouseEventType::Down,
x: 0,
y: 0,
button: Some(button),
scroll: 0,
});
}
}
mouse_type::UP => {
if let Some(button) = button_id_to_button(button_id) {
events.push(OneKvmMouseEvent {
event_type: MouseEventType::Up,
x: 0,
y: 0,
button: Some(button),
scroll: 0,
});
}
}
mouse_type::WHEEL => {
let scroll = if event.y > 0 { 1i8 } else { -1i8 };
events.push(OneKvmMouseEvent {
event_type: MouseEventType::Scroll,
x: 0,
y: 0,
button: None,
scroll,
});
}
_ => {}
}
events
}
fn button_id_to_button(button_id: i32) -> Option<MouseButton> {
match button_id {
mouse_button::LEFT => Some(MouseButton::Left),
mouse_button::RIGHT => Some(MouseButton::Right),
mouse_button::WHEEL => Some(MouseButton::Middle),
_ => None,
}
}
/// Convert using the code space associated with One-KVM's compatibility platform.
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) {
KeyboardModifiers::default()
} else {
parse_modifiers(event)
};
let Some(mapping) = key_event_to_mapping(event, code_space, base_modifiers) else {
log_rejected_key_event(event, code_space);
return Vec::new();
};
if event.press {
let up_modifiers = if mapping.added_shift {
base_modifiers
} else {
mapping.modifiers
};
vec![
KeyboardEvent {
event_type: KeyEventType::Down,
key: mapping.key,
modifiers: mapping.modifiers,
},
KeyboardEvent {
event_type: KeyEventType::Up,
key: mapping.key,
modifiers: up_modifiers,
},
]
} else {
vec![KeyboardEvent {
event_type: if event.down {
KeyEventType::Down
} else {
KeyEventType::Up
},
key: mapping.key,
modifiers: mapping.modifiers,
}]
}
}
pub fn convert_key_event(event: &KeyEvent) -> Option<KeyboardEvent> {
convert_key_events(event).into_iter().next()
}
#[derive(Debug, Clone, Copy)]
struct KeyMapping {
key: CanonicalKey,
modifiers: KeyboardModifiers,
added_shift: bool,
}
fn key_event_to_mapping(
event: &KeyEvent,
code_space: KeyboardCodeSpace,
modifiers: KeyboardModifiers,
) -> Option<KeyMapping> {
let mode = event.mode.enum_value().ok()?;
match &event.union {
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,
),
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<KeyMapping> {
Some(KeyMapping {
key,
modifiers,
added_shift: false,
})
}
fn character_mapping(ch: u32, modifiers: KeyboardModifiers) -> Option<KeyMapping> {
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<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 {
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 {
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 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")
}
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;
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 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[0].event_type, MouseEventType::Move);
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 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 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);
}
#[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, 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[1].modifiers.left_shift);
}
#[test]
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 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 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 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());
}
}