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 mut output = vec![0u8; total_bytes];
|
||||
let before_out = decompressor.total_out();
|
||||
decompressor
|
||||
.decompress(&compressed, &mut output, flate2::FlushDecompress::Sync)
|
||||
.decompress(&compressed, &mut output, flate2::FlushDecompress::None)
|
||||
.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
|
||||
};
|
||||
|
||||
|
||||
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 framebuffer;
|
||||
pub mod handshake;
|
||||
pub mod input;
|
||||
pub mod msg;
|
||||
pub mod proto;
|
||||
pub mod session;
|
||||
|
||||
Reference in New Issue
Block a user