fix(aten-kvm): send USB HID keycodes instead of X11 keysyms

The ATEN BMC expects USB HID keycodes in the key event packet, not X11
keysyms. Sending keysyms caused wrong keys (e.g. Q→Down Arrow, C→F10)
because the BMC interpreted the keysym value as a HID keycode.

Rewrote keymap to use HID keycodes from Java KeyMap.initHidKeyMap(),
renamed keysym→keycode throughout, and added toolbar width compensation
to window sizing so the framebuffer image gets its full width.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Davíð Steinn Geirsson 2026-03-12 10:46:15 +00:00
parent 4e3d24064e
commit 4f6c577ea7
6 changed files with 225 additions and 220 deletions

View file

@ -28,7 +28,7 @@ Rust workspace with four crates:
### Module responsibilities (aten-gui)
- `keymap.rs` — Physical key (winit KeyCode) to X11 keysym mapping (US QWERTY)
- `keymap.rs` — Physical key (winit KeyCode) to USB HID keycode mapping
- `ui/main.slint` — Window layout: left toolbar (Ctrl/Alt toggles, Ctrl+Alt+Del), framebuffer display, error dialog overlay
### Module responsibilities (aten-mount)

View file

@ -756,7 +756,7 @@ never calls it. Only mouse events support AES-128-CBC encryption.
│ padding: u8 = 0x00 │ Always zero (no encryption flag)
│ down_flag: u8 │ 0 = up, 1 = down
│ padding: 2 bytes (zero) │
│ keysym: u32 │ X11 keysym (translated from VK code)
│ keycode: u32 │ USB HID keycode
│ padding: 9 bytes (zero) │
└─────────────────────────────────┘
```
@ -767,11 +767,11 @@ The `down_flag` values:
- `0` = key released (up)
- `1` = key pressed (down)
The `keysym` field contains **X11 keysyms** (not HID usage codes as previously
documented). The native `processVK()` function translates Java VK codes through
three lookup tables to produce X11 keysyms. The Java `KeyMap.VKtoHID()` provides
the initial VK-to-HID mapping, but the native side further translates through
its own tables. See the processVK section below for the complete mapping.
The `keycode` field contains **USB HID keycodes**. The Java client's
`KeyMap.VKtoHID()` maps Java VK codes to HID keycodes before passing them
to the native layer. The native `processVK()` tables documented below show
the intermediate X11 keysym representation used internally, but the BMC
firmware ultimately interprets the value field as a HID keycode.
**Keyboard send flow** (`RFBKeyboard::Sendkey` at 0x0011a670):
1. `StreamWriteStart()` - begin write batch
@ -780,7 +780,7 @@ its own tables. See the processVK section below for the complete mapping.
4. `StreamWrite8(0x00)` - padding byte (always 0, no encryption flag)
5. `StreamWrite8(down_flag)` - key state
6. `StreamWriteSkip(2)` - 2 zero bytes padding
7. `StreamWrite32(keysym)` - the translated keysym value
7. `StreamWrite32(keycode)` - the HID keycode value
8. `StreamWriteSkip(9)` - 9 zero bytes padding
9. `StreamWriteFlush()` - send
@ -1964,7 +1964,7 @@ Parameters: `(this, mode, keysize, input, num_blocks, key, output, iv)`
Keyboard events are always sent unencrypted. Despite having an `RFBKMCryto*` at
RFBKeyboard offset 0x20, the `Sendkey()` function never references it. The
message is simply: type 0x04, padding 0x00, down_flag, 2 zero bytes, keysym (u32),
message is simply: type 0x04, padding 0x00, down_flag, 2 zero bytes, keycode (u32),
9 zero bytes.
## Connection Flow (Java Side)
@ -2131,7 +2131,7 @@ All multi-byte values are big-endian (network byte order).
```
KeyEvent (18 bytes, always cleartext):
04 00 [down] 00 00 [keysym:4] 00 00 00 00 00 00 00 00 00
04 00 [down] 00 00 [keycode:4] 00 00 00 00 00 00 00 00 00
PointerEvent unencrypted (18 bytes):
05 00 [buttons] [x:2] [y:2] 00 00 00 00 00 00 00 00 00 00 00
@ -2692,16 +2692,16 @@ For most modern ATEN/ASPEED BMCs, expect encoding 0x58. See the
Keyboard events are always sent in cleartext (never encrypted):
```
send: 04 00 [down_flag:1] 00 00 [keysym:4] 00 00 00 00 00 00 00 00 00
send: 04 00 [down_flag:1] 00 00 [keycode:4] 00 00 00 00 00 00 00 00 00
```
Total: 18 bytes. The `keysym` is an X11 keysym value (e.g., 0xFF0D for Enter,
0x0041 for 'A'). The `down_flag` is 0 for key-up and 1 for key-down.
Total: 18 bytes. The `keycode` is a USB HID keycode (e.g., 0x28 for Enter,
0x04 for 'A'). The `down_flag` is 0 for key-up and 1 for key-down.
Example -- pressing and releasing the 'a' key (uppercase keysym per Table 1):
Example -- pressing and releasing the 'a' key (HID keycode 0x04):
```
send: 04 00 01 00 00 00 00 00 41 00 00 00 00 00 00 00 00 00 (key down, keysym=0x41)
send: 04 00 00 00 00 00 00 00 41 00 00 00 00 00 00 00 00 00 (key up, keysym=0x41)
send: 04 00 01 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 (key down, keycode=0x04)
send: 04 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 (key up, keycode=0x04)
```
### (d) Sending Mouse Input

View file

@ -1,130 +1,131 @@
use slint::winit_030::winit::keyboard::KeyCode;
pub fn physical_key_to_keysym(key: KeyCode) -> Option<u32> {
// The ATEN BMC firmware maps keysyms to USB HID keycodes using tables
// derived from the original Java client's processVK() function. Letters
// must be uppercase (0x41-0x5A) to match those lookup tables.
let keysym = match key {
KeyCode::KeyA => 0x0041,
KeyCode::KeyB => 0x0042,
KeyCode::KeyC => 0x0043,
KeyCode::KeyD => 0x0044,
KeyCode::KeyE => 0x0045,
KeyCode::KeyF => 0x0046,
KeyCode::KeyG => 0x0047,
KeyCode::KeyH => 0x0048,
KeyCode::KeyI => 0x0049,
KeyCode::KeyJ => 0x004A,
KeyCode::KeyK => 0x004B,
KeyCode::KeyL => 0x004C,
KeyCode::KeyM => 0x004D,
KeyCode::KeyN => 0x004E,
KeyCode::KeyO => 0x004F,
KeyCode::KeyP => 0x0050,
KeyCode::KeyQ => 0x0051,
KeyCode::KeyR => 0x0052,
KeyCode::KeyS => 0x0053,
KeyCode::KeyT => 0x0054,
KeyCode::KeyU => 0x0055,
KeyCode::KeyV => 0x0056,
KeyCode::KeyW => 0x0057,
KeyCode::KeyX => 0x0058,
KeyCode::KeyY => 0x0059,
KeyCode::KeyZ => 0x005A,
KeyCode::Digit1 => 0x0031,
KeyCode::Digit2 => 0x0032,
KeyCode::Digit3 => 0x0033,
KeyCode::Digit4 => 0x0034,
KeyCode::Digit5 => 0x0035,
KeyCode::Digit6 => 0x0036,
KeyCode::Digit7 => 0x0037,
KeyCode::Digit8 => 0x0038,
KeyCode::Digit9 => 0x0039,
KeyCode::Digit0 => 0x0030,
KeyCode::F1 => 0xFFBE,
KeyCode::F2 => 0xFFBF,
KeyCode::F3 => 0xFFC0,
KeyCode::F4 => 0xFFC1,
KeyCode::F5 => 0xFFC2,
KeyCode::F6 => 0xFFC3,
KeyCode::F7 => 0xFFC4,
KeyCode::F8 => 0xFFC5,
KeyCode::F9 => 0xFFC6,
KeyCode::F10 => 0xFFC7,
KeyCode::F11 => 0xFFC8,
KeyCode::F12 => 0xFFC9,
KeyCode::F13 => 0xFFCA,
KeyCode::F14 => 0xFFCB,
KeyCode::F15 => 0xFFCC,
KeyCode::F16 => 0xFFCD,
KeyCode::F17 => 0xFFCE,
KeyCode::F18 => 0xFFCF,
KeyCode::F19 => 0xFFD0,
KeyCode::F20 => 0xFFD1,
KeyCode::F21 => 0xFFD2,
KeyCode::F22 => 0xFFD3,
KeyCode::F23 => 0xFFD4,
KeyCode::F24 => 0xFFD5,
KeyCode::ShiftLeft => 0xFFE1,
KeyCode::ShiftRight => 0xFFE2,
KeyCode::ControlLeft => 0xFFE3,
KeyCode::ControlRight => 0xFFE4,
KeyCode::AltLeft => 0xFFE9,
KeyCode::AltRight => 0xFFEA,
KeyCode::SuperLeft => 0xFFEB,
KeyCode::SuperRight => 0xFFEC,
KeyCode::CapsLock => 0xFFE5,
KeyCode::NumLock => 0xFF7F,
KeyCode::ScrollLock => 0xFF14,
KeyCode::Home => 0xFF50,
KeyCode::ArrowLeft => 0xFF51,
KeyCode::ArrowUp => 0xFF52,
KeyCode::ArrowRight => 0xFF53,
KeyCode::ArrowDown => 0xFF54,
KeyCode::PageUp => 0xFF55,
KeyCode::PageDown => 0xFF56,
KeyCode::End => 0xFF57,
KeyCode::Backspace => 0xFF08,
KeyCode::Tab => 0xFF09,
KeyCode::Enter => 0xFF0D,
KeyCode::Escape => 0xFF1B,
KeyCode::Space => 0x0020,
KeyCode::Insert => 0xFF63,
KeyCode::Delete => 0xFFFF,
KeyCode::PrintScreen => 0xFF61,
KeyCode::Pause => 0xFF13,
KeyCode::ContextMenu => 0xFF67,
KeyCode::Numpad0 => 0xFFB0,
KeyCode::Numpad1 => 0xFFB1,
KeyCode::Numpad2 => 0xFFB2,
KeyCode::Numpad3 => 0xFFB3,
KeyCode::Numpad4 => 0xFFB4,
KeyCode::Numpad5 => 0xFFB5,
KeyCode::Numpad6 => 0xFFB6,
KeyCode::Numpad7 => 0xFFB7,
KeyCode::Numpad8 => 0xFFB8,
KeyCode::Numpad9 => 0xFFB9,
KeyCode::NumpadMultiply => 0xFFAA,
KeyCode::NumpadAdd => 0xFFAB,
KeyCode::NumpadSubtract => 0xFFAD,
KeyCode::NumpadDecimal => 0xFFAE,
KeyCode::NumpadDivide => 0xFFAF,
KeyCode::NumpadEnter => 0xFF8D,
KeyCode::Minus => 0x005F,
KeyCode::Equal => 0x002B,
KeyCode::BracketLeft => 0x005b,
KeyCode::BracketRight => 0x005d,
KeyCode::Backslash => 0x005c,
KeyCode::Semicolon => 0x003A,
KeyCode::Quote => 0x0022,
KeyCode::Backquote => 0x0060,
KeyCode::Comma => 0x002c,
KeyCode::Period => 0x002e,
KeyCode::Slash => 0x002f,
KeyCode::IntlBackslash => 0x003c,
/// Maps a physical key to a USB HID keycode.
///
/// The ATEN BMC expects USB HID keycodes in the key event packet.
/// This table is derived from the original Java client's `KeyMap.initHidKeyMap()`.
pub fn physical_key_to_hid(key: KeyCode) -> Option<u32> {
let hid = match key {
KeyCode::KeyA => 0x04,
KeyCode::KeyB => 0x05,
KeyCode::KeyC => 0x06,
KeyCode::KeyD => 0x07,
KeyCode::KeyE => 0x08,
KeyCode::KeyF => 0x09,
KeyCode::KeyG => 0x0A,
KeyCode::KeyH => 0x0B,
KeyCode::KeyI => 0x0C,
KeyCode::KeyJ => 0x0D,
KeyCode::KeyK => 0x0E,
KeyCode::KeyL => 0x0F,
KeyCode::KeyM => 0x10,
KeyCode::KeyN => 0x11,
KeyCode::KeyO => 0x12,
KeyCode::KeyP => 0x13,
KeyCode::KeyQ => 0x14,
KeyCode::KeyR => 0x15,
KeyCode::KeyS => 0x16,
KeyCode::KeyT => 0x17,
KeyCode::KeyU => 0x18,
KeyCode::KeyV => 0x19,
KeyCode::KeyW => 0x1A,
KeyCode::KeyX => 0x1B,
KeyCode::KeyY => 0x1C,
KeyCode::KeyZ => 0x1D,
KeyCode::Digit1 => 0x1E,
KeyCode::Digit2 => 0x1F,
KeyCode::Digit3 => 0x20,
KeyCode::Digit4 => 0x21,
KeyCode::Digit5 => 0x22,
KeyCode::Digit6 => 0x23,
KeyCode::Digit7 => 0x24,
KeyCode::Digit8 => 0x25,
KeyCode::Digit9 => 0x26,
KeyCode::Digit0 => 0x27,
KeyCode::Enter => 0x28,
KeyCode::Escape => 0x29,
KeyCode::Backspace => 0x2A,
KeyCode::Tab => 0x2B,
KeyCode::Space => 0x2C,
KeyCode::Minus => 0x2D,
KeyCode::Equal => 0x2E,
KeyCode::BracketLeft => 0x2F,
KeyCode::BracketRight => 0x30,
KeyCode::Backslash => 0x31,
KeyCode::Semicolon => 0x33,
KeyCode::Quote => 0x34,
KeyCode::Backquote => 0x35,
KeyCode::Comma => 0x36,
KeyCode::Period => 0x37,
KeyCode::Slash => 0x38,
KeyCode::CapsLock => 0x39,
KeyCode::F1 => 0x3A,
KeyCode::F2 => 0x3B,
KeyCode::F3 => 0x3C,
KeyCode::F4 => 0x3D,
KeyCode::F5 => 0x3E,
KeyCode::F6 => 0x3F,
KeyCode::F7 => 0x40,
KeyCode::F8 => 0x41,
KeyCode::F9 => 0x42,
KeyCode::F10 => 0x43,
KeyCode::F11 => 0x44,
KeyCode::F12 => 0x45,
KeyCode::PrintScreen => 0x46,
KeyCode::ScrollLock => 0x47,
KeyCode::Pause => 0x48,
KeyCode::Insert => 0x49,
KeyCode::Home => 0x4A,
KeyCode::PageUp => 0x4B,
KeyCode::Delete => 0x4C,
KeyCode::End => 0x4D,
KeyCode::PageDown => 0x4E,
KeyCode::ArrowRight => 0x4F,
KeyCode::ArrowLeft => 0x50,
KeyCode::ArrowDown => 0x51,
KeyCode::ArrowUp => 0x52,
KeyCode::NumLock => 0x53,
KeyCode::NumpadDivide => 0x54,
KeyCode::NumpadMultiply => 0x55,
KeyCode::NumpadSubtract => 0x56,
KeyCode::NumpadAdd => 0x57,
KeyCode::NumpadEnter => 0x58,
KeyCode::Numpad1 => 0x59,
KeyCode::Numpad2 => 0x5A,
KeyCode::Numpad3 => 0x5B,
KeyCode::Numpad4 => 0x5C,
KeyCode::Numpad5 => 0x5D,
KeyCode::Numpad6 => 0x5E,
KeyCode::Numpad7 => 0x5F,
KeyCode::Numpad8 => 0x60,
KeyCode::Numpad9 => 0x61,
KeyCode::Numpad0 => 0x62,
KeyCode::NumpadDecimal => 0x63,
KeyCode::IntlBackslash => 0x64,
KeyCode::ContextMenu => 0x65,
KeyCode::F13 => 0x68,
KeyCode::F14 => 0x69,
KeyCode::F15 => 0x6A,
KeyCode::F16 => 0x6B,
KeyCode::F17 => 0x6C,
KeyCode::F18 => 0x6D,
KeyCode::F19 => 0x6E,
KeyCode::F20 => 0x6F,
KeyCode::F21 => 0x70,
KeyCode::F22 => 0x71,
KeyCode::F23 => 0x72,
KeyCode::F24 => 0x73,
KeyCode::ControlLeft => 0xE0,
KeyCode::ShiftLeft => 0xE1,
KeyCode::AltLeft => 0xE2,
KeyCode::SuperLeft => 0xE3,
KeyCode::ControlRight => 0xE4,
KeyCode::ShiftRight => 0xE5,
KeyCode::AltRight => 0xE6,
KeyCode::SuperRight => 0xE7,
_ => return None,
};
Some(keysym)
Some(hid)
}
#[cfg(test)]
@ -133,94 +134,96 @@ mod tests {
#[test]
fn letters() {
assert_eq!(physical_key_to_keysym(KeyCode::KeyA), Some(0x41));
assert_eq!(physical_key_to_keysym(KeyCode::KeyZ), Some(0x5A));
assert_eq!(physical_key_to_hid(KeyCode::KeyA), Some(0x04));
assert_eq!(physical_key_to_hid(KeyCode::KeyZ), Some(0x1D));
}
#[test]
fn digits() {
assert_eq!(physical_key_to_keysym(KeyCode::Digit0), Some(0x30));
assert_eq!(physical_key_to_keysym(KeyCode::Digit9), Some(0x39));
assert_eq!(physical_key_to_hid(KeyCode::Digit1), Some(0x1E));
assert_eq!(physical_key_to_hid(KeyCode::Digit0), Some(0x27));
}
#[test]
fn f_keys() {
assert_eq!(physical_key_to_keysym(KeyCode::F1), Some(0xFFBE));
assert_eq!(physical_key_to_keysym(KeyCode::F12), Some(0xFFC9));
assert_eq!(physical_key_to_keysym(KeyCode::F24), Some(0xFFD5));
assert_eq!(physical_key_to_hid(KeyCode::F1), Some(0x3A));
assert_eq!(physical_key_to_hid(KeyCode::F12), Some(0x45));
assert_eq!(physical_key_to_hid(KeyCode::F13), Some(0x68));
assert_eq!(physical_key_to_hid(KeyCode::F24), Some(0x73));
}
#[test]
fn modifiers_left_right() {
assert_eq!(physical_key_to_keysym(KeyCode::ShiftLeft), Some(0xFFE1));
assert_eq!(physical_key_to_keysym(KeyCode::ShiftRight), Some(0xFFE2));
assert_eq!(physical_key_to_keysym(KeyCode::ControlLeft), Some(0xFFE3));
assert_eq!(physical_key_to_keysym(KeyCode::ControlRight), Some(0xFFE4));
assert_eq!(physical_key_to_keysym(KeyCode::AltLeft), Some(0xFFE9));
assert_eq!(physical_key_to_keysym(KeyCode::AltRight), Some(0xFFEA));
assert_eq!(physical_key_to_keysym(KeyCode::SuperLeft), Some(0xFFEB));
assert_eq!(physical_key_to_keysym(KeyCode::SuperRight), Some(0xFFEC));
fn modifiers() {
assert_eq!(physical_key_to_hid(KeyCode::ControlLeft), Some(0xE0));
assert_eq!(physical_key_to_hid(KeyCode::ShiftLeft), Some(0xE1));
assert_eq!(physical_key_to_hid(KeyCode::AltLeft), Some(0xE2));
assert_eq!(physical_key_to_hid(KeyCode::SuperLeft), Some(0xE3));
assert_eq!(physical_key_to_hid(KeyCode::ControlRight), Some(0xE4));
assert_eq!(physical_key_to_hid(KeyCode::ShiftRight), Some(0xE5));
assert_eq!(physical_key_to_hid(KeyCode::AltRight), Some(0xE6));
assert_eq!(physical_key_to_hid(KeyCode::SuperRight), Some(0xE7));
}
#[test]
fn navigation() {
assert_eq!(physical_key_to_keysym(KeyCode::ArrowLeft), Some(0xFF51));
assert_eq!(physical_key_to_keysym(KeyCode::ArrowUp), Some(0xFF52));
assert_eq!(physical_key_to_keysym(KeyCode::ArrowRight), Some(0xFF53));
assert_eq!(physical_key_to_keysym(KeyCode::ArrowDown), Some(0xFF54));
assert_eq!(physical_key_to_keysym(KeyCode::Home), Some(0xFF50));
assert_eq!(physical_key_to_keysym(KeyCode::End), Some(0xFF57));
assert_eq!(physical_key_to_keysym(KeyCode::PageUp), Some(0xFF55));
assert_eq!(physical_key_to_keysym(KeyCode::PageDown), Some(0xFF56));
assert_eq!(physical_key_to_hid(KeyCode::ArrowRight), Some(0x4F));
assert_eq!(physical_key_to_hid(KeyCode::ArrowLeft), Some(0x50));
assert_eq!(physical_key_to_hid(KeyCode::ArrowDown), Some(0x51));
assert_eq!(physical_key_to_hid(KeyCode::ArrowUp), Some(0x52));
assert_eq!(physical_key_to_hid(KeyCode::Home), Some(0x4A));
assert_eq!(physical_key_to_hid(KeyCode::End), Some(0x4D));
assert_eq!(physical_key_to_hid(KeyCode::PageUp), Some(0x4B));
assert_eq!(physical_key_to_hid(KeyCode::PageDown), Some(0x4E));
}
#[test]
fn editing_keys() {
assert_eq!(physical_key_to_keysym(KeyCode::Backspace), Some(0xFF08));
assert_eq!(physical_key_to_keysym(KeyCode::Tab), Some(0xFF09));
assert_eq!(physical_key_to_keysym(KeyCode::Enter), Some(0xFF0D));
assert_eq!(physical_key_to_keysym(KeyCode::Escape), Some(0xFF1B));
assert_eq!(physical_key_to_keysym(KeyCode::Space), Some(0x0020));
assert_eq!(physical_key_to_keysym(KeyCode::Insert), Some(0xFF63));
assert_eq!(physical_key_to_keysym(KeyCode::Delete), Some(0xFFFF));
assert_eq!(physical_key_to_hid(KeyCode::Enter), Some(0x28));
assert_eq!(physical_key_to_hid(KeyCode::Escape), Some(0x29));
assert_eq!(physical_key_to_hid(KeyCode::Backspace), Some(0x2A));
assert_eq!(physical_key_to_hid(KeyCode::Tab), Some(0x2B));
assert_eq!(physical_key_to_hid(KeyCode::Space), Some(0x2C));
assert_eq!(physical_key_to_hid(KeyCode::Insert), Some(0x49));
assert_eq!(physical_key_to_hid(KeyCode::Delete), Some(0x4C));
}
#[test]
fn numpad() {
assert_eq!(physical_key_to_keysym(KeyCode::Numpad0), Some(0xFFB0));
assert_eq!(physical_key_to_keysym(KeyCode::Numpad9), Some(0xFFB9));
assert_eq!(physical_key_to_keysym(KeyCode::NumpadAdd), Some(0xFFAB));
assert_eq!(physical_key_to_keysym(KeyCode::NumpadSubtract), Some(0xFFAD));
assert_eq!(physical_key_to_keysym(KeyCode::NumpadMultiply), Some(0xFFAA));
assert_eq!(physical_key_to_keysym(KeyCode::NumpadDivide), Some(0xFFAF));
assert_eq!(physical_key_to_keysym(KeyCode::NumpadDecimal), Some(0xFFAE));
assert_eq!(physical_key_to_keysym(KeyCode::NumpadEnter), Some(0xFF8D));
assert_eq!(physical_key_to_hid(KeyCode::Numpad0), Some(0x62));
assert_eq!(physical_key_to_hid(KeyCode::Numpad1), Some(0x59));
assert_eq!(physical_key_to_hid(KeyCode::Numpad9), Some(0x61));
assert_eq!(physical_key_to_hid(KeyCode::NumpadAdd), Some(0x57));
assert_eq!(physical_key_to_hid(KeyCode::NumpadSubtract), Some(0x56));
assert_eq!(physical_key_to_hid(KeyCode::NumpadMultiply), Some(0x55));
assert_eq!(physical_key_to_hid(KeyCode::NumpadDivide), Some(0x54));
assert_eq!(physical_key_to_hid(KeyCode::NumpadDecimal), Some(0x63));
assert_eq!(physical_key_to_hid(KeyCode::NumpadEnter), Some(0x58));
}
#[test]
fn symbols_us_qwerty() {
assert_eq!(physical_key_to_keysym(KeyCode::Minus), Some(0x5F));
assert_eq!(physical_key_to_keysym(KeyCode::Equal), Some(0x2B));
assert_eq!(physical_key_to_keysym(KeyCode::BracketLeft), Some(0x5b));
assert_eq!(physical_key_to_keysym(KeyCode::BracketRight), Some(0x5d));
assert_eq!(physical_key_to_keysym(KeyCode::Backslash), Some(0x5c));
assert_eq!(physical_key_to_keysym(KeyCode::Semicolon), Some(0x3A));
assert_eq!(physical_key_to_keysym(KeyCode::Quote), Some(0x22));
assert_eq!(physical_key_to_keysym(KeyCode::Backquote), Some(0x60));
assert_eq!(physical_key_to_keysym(KeyCode::Comma), Some(0x2c));
assert_eq!(physical_key_to_keysym(KeyCode::Period), Some(0x2e));
assert_eq!(physical_key_to_keysym(KeyCode::Slash), Some(0x2f));
fn symbols() {
assert_eq!(physical_key_to_hid(KeyCode::Minus), Some(0x2D));
assert_eq!(physical_key_to_hid(KeyCode::Equal), Some(0x2E));
assert_eq!(physical_key_to_hid(KeyCode::BracketLeft), Some(0x2F));
assert_eq!(physical_key_to_hid(KeyCode::BracketRight), Some(0x30));
assert_eq!(physical_key_to_hid(KeyCode::Backslash), Some(0x31));
assert_eq!(physical_key_to_hid(KeyCode::Semicolon), Some(0x33));
assert_eq!(physical_key_to_hid(KeyCode::Quote), Some(0x34));
assert_eq!(physical_key_to_hid(KeyCode::Backquote), Some(0x35));
assert_eq!(physical_key_to_hid(KeyCode::Comma), Some(0x36));
assert_eq!(physical_key_to_hid(KeyCode::Period), Some(0x37));
assert_eq!(physical_key_to_hid(KeyCode::Slash), Some(0x38));
}
#[test]
fn lock_keys() {
assert_eq!(physical_key_to_keysym(KeyCode::CapsLock), Some(0xFFE5));
assert_eq!(physical_key_to_keysym(KeyCode::NumLock), Some(0xFF7F));
assert_eq!(physical_key_to_keysym(KeyCode::ScrollLock), Some(0xFF14));
assert_eq!(physical_key_to_hid(KeyCode::CapsLock), Some(0x39));
assert_eq!(physical_key_to_hid(KeyCode::NumLock), Some(0x53));
assert_eq!(physical_key_to_hid(KeyCode::ScrollLock), Some(0x47));
}
#[test]
fn unknown_key_returns_none() {
assert_eq!(physical_key_to_keysym(KeyCode::Fn), None);
assert_eq!(physical_key_to_hid(KeyCode::Fn), None);
}
}

View file

@ -12,6 +12,8 @@ use slint::winit_030::{winit, EventResult, WinitWindowAccessor};
use aten_kvm::{InputEvent, KvmConfig, KvmEvent, KvmSession};
const TOOLBAR_WIDTH: f32 = 70.0;
#[derive(Parser)]
#[command(about = "KVM console viewer for ATEN BMC")]
struct Args {
@ -61,7 +63,7 @@ fn main() {
let input_tx_ctrl = input_tx.clone();
app.on_ctrl_toggled(move |checked| {
let _ = input_tx_ctrl.send(InputEvent::Key {
keysym: 0xFFE3, // Control_L
keycode: 0xE0, // ControlLeft
down: checked,
});
});
@ -69,33 +71,33 @@ fn main() {
let input_tx_alt = input_tx.clone();
app.on_alt_toggled(move |checked| {
let _ = input_tx_alt.send(InputEvent::Key {
keysym: 0xFFE9, // Alt_L
keycode: 0xE2, // AltLeft
down: checked,
});
});
let input_tx_cad = input_tx.clone();
app.on_ctrl_alt_del_pressed(move || {
for &(keysym, down) in &[
(0xFFE3u32, true), // Ctrl down
(0xFFE9, true), // Alt down
(0xFFFF, true), // Delete down
(0xFFFF, false), // Delete up
(0xFFE9, false), // Alt up
(0xFFE3, false), // Ctrl up
for &(keycode, down) in &[
(0xE0u32, true), // Ctrl down
(0xE2, true), // Alt down
(0x4C, true), // Delete down
(0x4C, false), // Delete up
(0xE2, false), // Alt up
(0xE0, false), // Ctrl up
] {
let _ = input_tx_cad.send(InputEvent::Key { keysym, down });
let _ = input_tx_cad.send(InputEvent::Key { keycode, down });
}
});
app.window().on_winit_window_event(move |_slint_window, event| {
if let winit::event::WindowEvent::KeyboardInput { event, .. } = event {
if let winit::keyboard::PhysicalKey::Code(keycode) = event.physical_key {
if let Some(keysym) = keymap::physical_key_to_keysym(keycode) {
if let winit::keyboard::PhysicalKey::Code(physical) = event.physical_key {
if let Some(keycode) = keymap::physical_key_to_hid(physical) {
let down = event.state == winit::event::ElementState::Pressed;
let _ = input_tx.send(InputEvent::Key { keysym, down });
let _ = input_tx.send(InputEvent::Key { keycode, down });
} else {
warn!("unmapped physical key: {keycode:?}");
warn!("unmapped physical key: {physical:?}");
}
}
return EventResult::PreventDefault;
@ -116,7 +118,7 @@ fn main() {
slint::invoke_from_event_loop(move || {
if let Some(app) = weak.upgrade() {
app.window().set_size(slint::LogicalSize::new(
width as f32,
width as f32 + TOOLBAR_WIDTH,
height as f32,
));
}
@ -129,7 +131,7 @@ fn main() {
slint::invoke_from_event_loop(move || {
if let Some(app) = weak.upgrade() {
app.window().set_size(slint::LogicalSize::new(
width as f32,
width as f32 + TOOLBAR_WIDTH,
height as f32,
));
}

View file

@ -7,7 +7,7 @@ use std::sync::mpsc;
#[derive(Debug, Clone, Copy)]
pub enum InputEvent {
Key { keysym: u32, down: bool },
Key { keycode: u32, down: bool },
}
#[derive(Debug)]

View file

@ -106,11 +106,11 @@ pub fn send_keepalive_ack(stream: &mut impl Write) -> Result<(), KvmError> {
Ok(())
}
pub fn build_key_event(keysym: u32, down: bool) -> [u8; 18] {
pub fn build_key_event(keycode: u32, down: bool) -> [u8; 18] {
let mut pkt = [0u8; 18];
pkt[0] = 0x04;
pkt[2] = if down { 1 } else { 0 };
pkt[5..9].copy_from_slice(&keysym.to_be_bytes());
pkt[5..9].copy_from_slice(&keycode.to_be_bytes());
pkt
}
@ -235,9 +235,9 @@ pub fn run_session(
debug!("writer thread started");
while let Ok(event) = input_rx.recv() {
match event {
InputEvent::Key { keysym, down } => {
debug!("sending key event: keysym=0x{keysym:04x} down={down}");
if write_stream.write_all(&build_key_event(keysym, down)).is_err() {
InputEvent::Key { keycode, down } => {
debug!("sending key event: keycode=0x{keycode:04x} down={down}");
if write_stream.write_all(&build_key_event(keycode, down)).is_err() {
debug!("writer thread: write failed, exiting");
break;
}
@ -577,21 +577,21 @@ mod tests {
#[test]
fn test_build_key_event_down() {
let pkt = build_key_event(0xFF0D, true); // Enter key down
let pkt = build_key_event(0x28, true); // Enter key down (HID 0x28)
assert_eq!(pkt.len(), 18);
assert_eq!(pkt[0], 0x04); // type
assert_eq!(pkt[1], 0x00); // no encryption
assert_eq!(pkt[2], 0x01); // down
assert_eq!(pkt[3], 0x00);
assert_eq!(pkt[4], 0x00);
assert_eq!(u32::from_be_bytes([pkt[5], pkt[6], pkt[7], pkt[8]]), 0xFF0D);
assert_eq!(u32::from_be_bytes([pkt[5], pkt[6], pkt[7], pkt[8]]), 0x28);
assert!(pkt[9..18].iter().all(|&b| b == 0));
}
#[test]
fn test_build_key_event_up() {
let pkt = build_key_event(0x61, false); // 'a' key up
let pkt = build_key_event(0x04, false); // 'A' key up (HID 0x04)
assert_eq!(pkt[2], 0x00); // up
assert_eq!(u32::from_be_bytes([pkt[5], pkt[6], pkt[7], pkt[8]]), 0x61);
assert_eq!(u32::from_be_bytes([pkt[5], pkt[6], pkt[7], pkt[8]]), 0x04);
}
}