All checks were successful
SPEC-002 Phase 1 Task 6, code-reviewed APPROVED (2 rounds), locally verified (cargo fmt + clippy -D warnings exit 0 + cargo test --workspace 70 pass + build). - Viewer WH_KEYBOARD_LL hook diverts system combos (Win/Win+R, Alt+Tab, Alt+Esc, Ctrl+Esc) to the remote as a full KeyEvent (vk + scan + is_extended + modifiers) and suppresses local handling - GATED on the viewer window having focus AND a "send system keys" toggle (default on; Pause/Break host-key), so it never bricks the technician's local keyboard when unfocused. - Agent injection via SendInput KEYEVENTF_SCANCODE + correct KEYEVENTF_EXTENDEDKEY (right Ctrl/Alt, arrows, nav, Win, NumLock, numpad Divide) - layout-independent, extended-key-correct. - Ctrl+Alt+Del completes through the SAS helper (SYSTEM SendSAS); installer sets the SoftwareSASGeneration policy; 3-tier fail-loud (no false success). SAS named pipe DACL tightened from NULL/Everyone to Authenticated Users. - Modifier hygiene: viewer emits key-ups for held Ctrl/Alt/Shift/Win on focus loss / close so modifiers never stick on the remote. - proto: KeyEvent.is_extended = 7 (additive; older agents derive the flag). Closes Win+R / Ctrl+C-V / Ctrl+Alt+Del / arrows-vs-numpad fidelity. Live on-device testing is plan Task 8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
124 lines
3.4 KiB
Rust
124 lines
3.4 KiB
Rust
//! Input injection module
|
|
//!
|
|
//! Handles mouse and keyboard input simulation using Windows SendInput API.
|
|
|
|
mod keyboard;
|
|
mod mouse;
|
|
|
|
pub use keyboard::vk_is_extended;
|
|
pub use keyboard::KeyboardController;
|
|
pub use mouse::MouseController;
|
|
|
|
use anyhow::Result;
|
|
|
|
/// Combined input controller for mouse and keyboard
|
|
pub struct InputController {
|
|
mouse: MouseController,
|
|
keyboard: KeyboardController,
|
|
}
|
|
|
|
impl InputController {
|
|
/// Create a new input controller
|
|
pub fn new() -> Result<Self> {
|
|
Ok(Self {
|
|
mouse: MouseController::new()?,
|
|
keyboard: KeyboardController::new()?,
|
|
})
|
|
}
|
|
|
|
/// Get mouse controller
|
|
#[allow(dead_code)]
|
|
pub fn mouse(&mut self) -> &mut MouseController {
|
|
&mut self.mouse
|
|
}
|
|
|
|
/// Get keyboard controller
|
|
#[allow(dead_code)]
|
|
pub fn keyboard(&mut self) -> &mut KeyboardController {
|
|
&mut self.keyboard
|
|
}
|
|
|
|
/// Move mouse to absolute position
|
|
pub fn mouse_move(&mut self, x: i32, y: i32) -> Result<()> {
|
|
self.mouse.move_to(x, y)
|
|
}
|
|
|
|
/// Click mouse button
|
|
pub fn mouse_click(&mut self, button: MouseButton, down: bool) -> Result<()> {
|
|
if down {
|
|
self.mouse.button_down(button)
|
|
} else {
|
|
self.mouse.button_up(button)
|
|
}
|
|
}
|
|
|
|
/// Scroll mouse wheel
|
|
pub fn mouse_scroll(&mut self, delta_x: i32, delta_y: i32) -> Result<()> {
|
|
self.mouse.scroll(delta_x, delta_y)
|
|
}
|
|
|
|
/// Press or release a key by virtual-key code only (scan code derived from the VK).
|
|
#[allow(dead_code)]
|
|
pub fn key_event(&mut self, vk_code: u16, down: bool) -> Result<()> {
|
|
if down {
|
|
self.keyboard.key_down(vk_code)
|
|
} else {
|
|
self.keyboard.key_up(vk_code)
|
|
}
|
|
}
|
|
|
|
/// Inject a full-fidelity key event (VK + hardware scan code + extended-key flag).
|
|
///
|
|
/// This is the path used for relayed viewer keystrokes so that scan-code injection
|
|
/// (layout-independent) and the correct `KEYEVENTF_EXTENDEDKEY` flag are applied.
|
|
pub fn key_event_full(
|
|
&mut self,
|
|
vk_code: u16,
|
|
scan_code: u16,
|
|
is_extended: bool,
|
|
down: bool,
|
|
) -> Result<()> {
|
|
self.keyboard
|
|
.key_event_full(vk_code, scan_code, is_extended, down)
|
|
}
|
|
|
|
/// Release any modifier keys currently held down on the remote.
|
|
///
|
|
/// Invoked when the viewer loses focus or the session ends so a Ctrl/Alt/Shift/Win
|
|
/// whose key-up never arrived does not stay latched on the remote desktop.
|
|
#[allow(dead_code)]
|
|
pub fn release_all_modifiers(&mut self) -> Result<()> {
|
|
self.keyboard.release_all_modifiers()
|
|
}
|
|
|
|
/// Type a unicode character
|
|
#[allow(dead_code)]
|
|
pub fn type_unicode(&mut self, ch: char) -> Result<()> {
|
|
self.keyboard.type_char(ch)
|
|
}
|
|
|
|
/// Send Ctrl+Alt+Delete (requires special handling on Windows)
|
|
pub fn send_ctrl_alt_del(&mut self) -> Result<()> {
|
|
self.keyboard.send_sas()
|
|
}
|
|
}
|
|
|
|
/// Mouse button types
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum MouseButton {
|
|
Left,
|
|
Right,
|
|
Middle,
|
|
// Extra mouse buttons; not yet produced by the viewer input mapping.
|
|
#[allow(dead_code)]
|
|
X1,
|
|
#[allow(dead_code)]
|
|
X2,
|
|
}
|
|
|
|
impl Default for InputController {
|
|
fn default() -> Self {
|
|
Self::new().expect("Failed to create input controller")
|
|
}
|
|
}
|