diff --git a/crates/ericrfb/src/codec/tight.rs b/crates/ericrfb/src/codec/tight.rs index 847377b..1d1764c 100644 --- a/crates/ericrfb/src/codec/tight.rs +++ b/crates/ericrfb/src/codec/tight.rs @@ -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 }; diff --git a/crates/ericrfb/src/input.rs b/crates/ericrfb/src/input.rs new file mode 100644 index 0000000..b27140e --- /dev/null +++ b/crates/ericrfb/src/input.rs @@ -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 { + 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); + } +} diff --git a/crates/ericrfb/src/lib.rs b/crates/ericrfb/src/lib.rs index 5feec30..577f1a9 100644 --- a/crates/ericrfb/src/lib.rs +++ b/crates/ericrfb/src/lib.rs @@ -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;