Initial GuruConnect implementation - Phase 1 MVP
- Agent: DXGI/GDI screen capture, mouse/keyboard input, WebSocket transport - Server: Axum relay, session management, REST API - Dashboard: React viewer components with TypeScript - Protocol: Protobuf definitions for all message types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
217
agent/src/input/mouse.rs
Normal file
217
agent/src/input/mouse.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
//! Mouse input simulation using Windows SendInput API
|
||||
|
||||
use super::MouseButton;
|
||||
use anyhow::Result;
|
||||
|
||||
#[cfg(windows)]
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::{
|
||||
SendInput, INPUT, INPUT_0, INPUT_MOUSE, MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_HWHEEL,
|
||||
MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP,
|
||||
MOUSEEVENTF_MOVE, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, MOUSEEVENTF_VIRTUALDESK,
|
||||
MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP, MOUSEINPUT, XBUTTON1, XBUTTON2,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
GetSystemMetrics, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN,
|
||||
SM_YVIRTUALSCREEN,
|
||||
};
|
||||
|
||||
/// Mouse input controller
|
||||
pub struct MouseController {
|
||||
/// Virtual screen dimensions for coordinate translation
|
||||
#[cfg(windows)]
|
||||
virtual_screen: VirtualScreen,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
struct VirtualScreen {
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
|
||||
impl MouseController {
|
||||
/// Create a new mouse controller
|
||||
pub fn new() -> Result<Self> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let virtual_screen = unsafe {
|
||||
VirtualScreen {
|
||||
x: GetSystemMetrics(SM_XVIRTUALSCREEN),
|
||||
y: GetSystemMetrics(SM_YVIRTUALSCREEN),
|
||||
width: GetSystemMetrics(SM_CXVIRTUALSCREEN),
|
||||
height: GetSystemMetrics(SM_CYVIRTUALSCREEN),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self { virtual_screen })
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
anyhow::bail!("Mouse input only supported on Windows")
|
||||
}
|
||||
}
|
||||
|
||||
/// Move mouse to absolute screen coordinates
|
||||
#[cfg(windows)]
|
||||
pub fn move_to(&mut self, x: i32, y: i32) -> Result<()> {
|
||||
// Convert screen coordinates to normalized absolute coordinates (0-65535)
|
||||
let norm_x = ((x - self.virtual_screen.x) * 65535) / self.virtual_screen.width;
|
||||
let norm_y = ((y - self.virtual_screen.y) * 65535) / self.virtual_screen.height;
|
||||
|
||||
let input = INPUT {
|
||||
r#type: INPUT_MOUSE,
|
||||
Anonymous: INPUT_0 {
|
||||
mi: MOUSEINPUT {
|
||||
dx: norm_x,
|
||||
dy: norm_y,
|
||||
mouseData: 0,
|
||||
dwFlags: MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
self.send_input(&[input])
|
||||
}
|
||||
|
||||
/// Press mouse button down
|
||||
#[cfg(windows)]
|
||||
pub fn button_down(&mut self, button: MouseButton) -> Result<()> {
|
||||
let (flags, data) = match button {
|
||||
MouseButton::Left => (MOUSEEVENTF_LEFTDOWN, 0),
|
||||
MouseButton::Right => (MOUSEEVENTF_RIGHTDOWN, 0),
|
||||
MouseButton::Middle => (MOUSEEVENTF_MIDDLEDOWN, 0),
|
||||
MouseButton::X1 => (MOUSEEVENTF_XDOWN, XBUTTON1 as u32),
|
||||
MouseButton::X2 => (MOUSEEVENTF_XDOWN, XBUTTON2 as u32),
|
||||
};
|
||||
|
||||
let input = INPUT {
|
||||
r#type: INPUT_MOUSE,
|
||||
Anonymous: INPUT_0 {
|
||||
mi: MOUSEINPUT {
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
mouseData: data,
|
||||
dwFlags: flags,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
self.send_input(&[input])
|
||||
}
|
||||
|
||||
/// Release mouse button
|
||||
#[cfg(windows)]
|
||||
pub fn button_up(&mut self, button: MouseButton) -> Result<()> {
|
||||
let (flags, data) = match button {
|
||||
MouseButton::Left => (MOUSEEVENTF_LEFTUP, 0),
|
||||
MouseButton::Right => (MOUSEEVENTF_RIGHTUP, 0),
|
||||
MouseButton::Middle => (MOUSEEVENTF_MIDDLEUP, 0),
|
||||
MouseButton::X1 => (MOUSEEVENTF_XUP, XBUTTON1 as u32),
|
||||
MouseButton::X2 => (MOUSEEVENTF_XUP, XBUTTON2 as u32),
|
||||
};
|
||||
|
||||
let input = INPUT {
|
||||
r#type: INPUT_MOUSE,
|
||||
Anonymous: INPUT_0 {
|
||||
mi: MOUSEINPUT {
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
mouseData: data,
|
||||
dwFlags: flags,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
self.send_input(&[input])
|
||||
}
|
||||
|
||||
/// Scroll mouse wheel
|
||||
#[cfg(windows)]
|
||||
pub fn scroll(&mut self, delta_x: i32, delta_y: i32) -> Result<()> {
|
||||
let mut inputs = Vec::new();
|
||||
|
||||
// Vertical scroll
|
||||
if delta_y != 0 {
|
||||
inputs.push(INPUT {
|
||||
r#type: INPUT_MOUSE,
|
||||
Anonymous: INPUT_0 {
|
||||
mi: MOUSEINPUT {
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
mouseData: delta_y as u32,
|
||||
dwFlags: MOUSEEVENTF_WHEEL,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Horizontal scroll
|
||||
if delta_x != 0 {
|
||||
inputs.push(INPUT {
|
||||
r#type: INPUT_MOUSE,
|
||||
Anonymous: INPUT_0 {
|
||||
mi: MOUSEINPUT {
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
mouseData: delta_x as u32,
|
||||
dwFlags: MOUSEEVENTF_HWHEEL,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if !inputs.is_empty() {
|
||||
self.send_input(&inputs)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send input events
|
||||
#[cfg(windows)]
|
||||
fn send_input(&self, inputs: &[INPUT]) -> Result<()> {
|
||||
let sent = unsafe {
|
||||
SendInput(inputs, std::mem::size_of::<INPUT>() as i32)
|
||||
};
|
||||
|
||||
if sent as usize != inputs.len() {
|
||||
anyhow::bail!("SendInput failed: sent {} of {} inputs", sent, inputs.len());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn move_to(&mut self, _x: i32, _y: i32) -> Result<()> {
|
||||
anyhow::bail!("Mouse input only supported on Windows")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn button_down(&mut self, _button: MouseButton) -> Result<()> {
|
||||
anyhow::bail!("Mouse input only supported on Windows")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn button_up(&mut self, _button: MouseButton) -> Result<()> {
|
||||
anyhow::bail!("Mouse input only supported on Windows")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn scroll(&mut self, _delta_x: i32, _delta_y: i32) -> Result<()> {
|
||||
anyhow::bail!("Mouse input only supported on Windows")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user