feat: phase 6 — keyboard/mouse input + fix Tight zlib init
input.rs: - write_key_press/release/tap: scancode | 0x80 = press, bare = release - JavaScript KeyboardEvent.code → e-RIC scancode mapping (104pc layout) - Hotkey sequence sender (raw hex byte strings from applet params) - write_ctrl_alt_del() using HOTKEYCODE_0 "36 f0 37 f0 4e" - PointerEvent writer already in msg.rs (8 bytes, absolute mode) - 5 unit tests Tight zlib fix: - Changed Decompress::new(true) for zlib-wrapped format (matching Java's Inflater() which expects zlib header 78 9C) - Added output length verification after decompression - Tested: Tight-encoded frames now decode correctly from real device 37 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -228,11 +228,24 @@ pub fn decode_tight(
|
|||||||
let decompressor = zlib.get_or_init(stream_idx);
|
let decompressor = zlib.get_or_init(stream_idx);
|
||||||
|
|
||||||
let mut output = vec![0u8; total_bytes];
|
let mut output = vec![0u8; total_bytes];
|
||||||
|
let before_out = decompressor.total_out();
|
||||||
decompressor
|
decompressor
|
||||||
.decompress(&compressed, &mut output, flate2::FlushDecompress::Sync)
|
.decompress(&compressed, &mut output, flate2::FlushDecompress::None)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
proto::ProtoError::Io(std::io::Error::new(std::io::ErrorKind::InvalidData, e))
|
proto::ProtoError::Io(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidData,
|
||||||
|
format!(
|
||||||
|
"zlib: {e} (stream {stream_idx}, in={comp_len}, expected_out={total_bytes})"
|
||||||
|
),
|
||||||
|
))
|
||||||
})?;
|
})?;
|
||||||
|
let produced = (decompressor.total_out() - before_out) as usize;
|
||||||
|
if produced != total_bytes {
|
||||||
|
return Err(proto::ProtoError::Io(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidData,
|
||||||
|
format!("zlib: produced {produced} bytes, expected {total_bytes}"),
|
||||||
|
)));
|
||||||
|
}
|
||||||
output
|
output
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
223
crates/ericrfb/src/input.rs
Normal file
223
crates/ericrfb/src/input.rs
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crate::proto;
|
||||||
|
|
||||||
|
/// e-RIC key scancode from KbdLayout_104pc.java.
|
||||||
|
/// Press = scancode | 0x80, release = scancode.
|
||||||
|
/// Sent via msg type 4: `[4, code]`.
|
||||||
|
pub fn write_key_press(w: &mut impl Write, scancode: u8) -> proto::Result<()> {
|
||||||
|
w.write_all(&[4, scancode | 0x80])?;
|
||||||
|
w.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_key_release(w: &mut impl Write, scancode: u8) -> proto::Result<()> {
|
||||||
|
w.write_all(&[4, scancode])?;
|
||||||
|
w.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a complete key tap (press + release).
|
||||||
|
pub fn write_key_tap(w: &mut impl Write, scancode: u8) -> proto::Result<()> {
|
||||||
|
write_key_press(w, scancode)?;
|
||||||
|
write_key_release(w, scancode)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a hotkey sequence from the applet's HOTKEYCODE params.
|
||||||
|
/// Bytes are space-separated hex values, sent raw via msg type 4.
|
||||||
|
pub fn write_hotkey_sequence(w: &mut impl Write, hex_str: &str) -> proto::Result<()> {
|
||||||
|
for token in hex_str.split_whitespace() {
|
||||||
|
if let Ok(byte) = u8::from_str_radix(token, 16) {
|
||||||
|
w.write_all(&[4, byte])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ctrl+Alt+Delete hotkey sequence from the OmniView's HOTKEYCODE_0 param.
|
||||||
|
pub const HOTKEY_CTRL_ALT_DEL: &str = "36 f0 37 f0 4e";
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// JavaScript KeyboardEvent.code → e-RIC scancode mapping
|
||||||
|
//
|
||||||
|
// Maps browser key codes to KbdLayout_104pc keycodes.
|
||||||
|
// keynr == keycode for almost all keys in this layout.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Map a JavaScript `KeyboardEvent.code` string to an e-RIC scancode.
|
||||||
|
/// Returns `None` for unmapped keys.
|
||||||
|
pub fn js_code_to_scancode(code: &str) -> Option<u8> {
|
||||||
|
Some(match code {
|
||||||
|
// Function row
|
||||||
|
"Escape" => 0,
|
||||||
|
"F1" => 59,
|
||||||
|
"F2" => 60,
|
||||||
|
"F3" => 61,
|
||||||
|
"F4" => 62,
|
||||||
|
"F5" => 63,
|
||||||
|
"F6" => 64,
|
||||||
|
"F7" => 65,
|
||||||
|
"F8" => 66,
|
||||||
|
"F9" => 67,
|
||||||
|
"F10" => 68,
|
||||||
|
"F11" => 69,
|
||||||
|
"F12" => 70,
|
||||||
|
|
||||||
|
// Number row
|
||||||
|
"Backquote" => 1,
|
||||||
|
"Digit1" => 2,
|
||||||
|
"Digit2" => 3,
|
||||||
|
"Digit3" => 4,
|
||||||
|
"Digit4" => 5,
|
||||||
|
"Digit5" => 6,
|
||||||
|
"Digit6" => 7,
|
||||||
|
"Digit7" => 8,
|
||||||
|
"Digit8" => 9,
|
||||||
|
"Digit9" => 10,
|
||||||
|
"Digit0" => 11,
|
||||||
|
"Minus" => 12,
|
||||||
|
"Equal" => 13,
|
||||||
|
"Backspace" => 14,
|
||||||
|
|
||||||
|
// QWERTY row
|
||||||
|
"Tab" => 15,
|
||||||
|
"KeyQ" => 16,
|
||||||
|
"KeyW" => 17,
|
||||||
|
"KeyE" => 18,
|
||||||
|
"KeyR" => 19,
|
||||||
|
"KeyT" => 20,
|
||||||
|
"KeyY" => 21,
|
||||||
|
"KeyU" => 22,
|
||||||
|
"KeyI" => 23,
|
||||||
|
"KeyO" => 24,
|
||||||
|
"KeyP" => 25,
|
||||||
|
"BracketLeft" => 26,
|
||||||
|
"BracketRight" => 27,
|
||||||
|
|
||||||
|
// Home row
|
||||||
|
"CapsLock" => 28,
|
||||||
|
"KeyA" => 29,
|
||||||
|
"KeyS" => 30,
|
||||||
|
"KeyD" => 31,
|
||||||
|
"KeyF" => 32,
|
||||||
|
"KeyG" => 33,
|
||||||
|
"KeyH" => 34,
|
||||||
|
"KeyJ" => 35,
|
||||||
|
"KeyK" => 36,
|
||||||
|
"KeyL" => 37,
|
||||||
|
"Semicolon" => 38,
|
||||||
|
"Quote" => 39,
|
||||||
|
"Enter" => 40,
|
||||||
|
|
||||||
|
// Bottom row
|
||||||
|
"ShiftLeft" => 41,
|
||||||
|
"Backslash" => 42,
|
||||||
|
"KeyZ" => 43,
|
||||||
|
"KeyX" => 44,
|
||||||
|
"KeyC" => 45,
|
||||||
|
"KeyV" => 46,
|
||||||
|
"KeyB" => 47,
|
||||||
|
"KeyN" => 48,
|
||||||
|
"KeyM" => 49,
|
||||||
|
"Comma" => 50,
|
||||||
|
"Period" => 51,
|
||||||
|
"Slash" => 52,
|
||||||
|
"ShiftRight" => 53,
|
||||||
|
|
||||||
|
// Modifier / bottom row
|
||||||
|
"ControlLeft" => 54,
|
||||||
|
"MetaLeft" => 105,
|
||||||
|
"AltLeft" => 55,
|
||||||
|
"Space" => 56,
|
||||||
|
"AltRight" => 57,
|
||||||
|
"MetaRight" => 106,
|
||||||
|
"ControlRight" => 58,
|
||||||
|
|
||||||
|
// Navigation cluster
|
||||||
|
"PrintScreen" => 71,
|
||||||
|
"ScrollLock" => 72,
|
||||||
|
"Pause" => 73,
|
||||||
|
"Insert" => 75,
|
||||||
|
"Home" => 76,
|
||||||
|
"PageUp" => 77,
|
||||||
|
"Delete" => 78,
|
||||||
|
"End" => 79,
|
||||||
|
"PageDown" => 80,
|
||||||
|
|
||||||
|
// Arrow keys
|
||||||
|
"ArrowUp" => 81,
|
||||||
|
"ArrowLeft" => 82,
|
||||||
|
"ArrowDown" => 83,
|
||||||
|
"ArrowRight" => 84,
|
||||||
|
|
||||||
|
// Numpad
|
||||||
|
"NumLock" => 85,
|
||||||
|
"NumpadDivide" => 86,
|
||||||
|
"NumpadMultiply" => 87,
|
||||||
|
"NumpadSubtract" => 88,
|
||||||
|
"NumpadAdd" => 89,
|
||||||
|
"NumpadEnter" => 98,
|
||||||
|
"Numpad7" => 90,
|
||||||
|
"Numpad8" => 94,
|
||||||
|
"Numpad9" => 99,
|
||||||
|
"Numpad4" => 91,
|
||||||
|
"Numpad5" => 92,
|
||||||
|
"Numpad6" => 93,
|
||||||
|
"Numpad1" => 95,
|
||||||
|
"Numpad2" => 96,
|
||||||
|
"Numpad3" => 97,
|
||||||
|
"Numpad0" => 100,
|
||||||
|
"NumpadDecimal" => 101,
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send Ctrl+Alt+Delete via the applet's hotkey sequence.
|
||||||
|
pub fn write_ctrl_alt_del(w: &mut impl Write) -> proto::Result<()> {
|
||||||
|
write_hotkey_sequence(w, HOTKEY_CTRL_ALT_DEL)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_key_press_sets_bit7() {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
write_key_press(&mut buf, 29).unwrap(); // 'A' scancode
|
||||||
|
assert_eq!(buf, [4, 29 | 0x80]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_key_release_no_bit7() {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
write_key_release(&mut buf, 29).unwrap();
|
||||||
|
assert_eq!(buf, [4, 29]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_key_tap() {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
write_key_tap(&mut buf, 29).unwrap();
|
||||||
|
assert_eq!(buf, [4, 29 | 0x80, 4, 29]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hotkey_sequence() {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
write_hotkey_sequence(&mut buf, "36 f0 37").unwrap();
|
||||||
|
assert_eq!(buf, [4, 0x36, 4, 0xF0, 4, 0x37]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_js_code_mapping() {
|
||||||
|
assert_eq!(js_code_to_scancode("KeyA"), Some(29));
|
||||||
|
assert_eq!(js_code_to_scancode("Escape"), Some(0));
|
||||||
|
assert_eq!(js_code_to_scancode("ControlLeft"), Some(54));
|
||||||
|
assert_eq!(js_code_to_scancode("Delete"), Some(78));
|
||||||
|
assert_eq!(js_code_to_scancode("Unknown"), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod codec;
|
pub mod codec;
|
||||||
pub mod framebuffer;
|
pub mod framebuffer;
|
||||||
pub mod handshake;
|
pub mod handshake;
|
||||||
|
pub mod input;
|
||||||
pub mod msg;
|
pub mod msg;
|
||||||
pub mod proto;
|
pub mod proto;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
|
|||||||
Reference in New Issue
Block a user