Created comprehensive VPN setup tooling for Peaceful Spirit L2TP/IPsec connection and enhanced agent documentation framework. VPN Configuration (PST-NW-VPN): - Setup-PST-L2TP-VPN.ps1: Automated L2TP/IPsec setup with split-tunnel and DNS - Connect-PST-VPN.ps1: Connection helper with PPP adapter detection, DNS (192.168.0.2), and route config (192.168.0.0/24) - Connect-PST-VPN-Standalone.ps1: Self-contained connection script for remote deployment - Fix-PST-VPN-Auth.ps1: Authentication troubleshooting for CHAP/MSChapv2 - Diagnose-VPN-Interface.ps1: Comprehensive VPN interface and routing diagnostic - Quick-Test-VPN.ps1: Fast connectivity verification (DNS/router/routes) - Add-PST-VPN-Route-Manual.ps1: Manual route configuration helper - vpn-connect.bat, vpn-disconnect.bat: Simple batch file shortcuts - OpenVPN config files (Windows-compatible, abandoned for L2TP) Key VPN Implementation Details: - L2TP creates PPP adapter with connection name as interface description - UniFi auto-configures DNS (192.168.0.2) but requires manual route to 192.168.0.0/24 - Split-tunnel enabled (only remote traffic through VPN) - All-user connection for pre-login auto-connect via scheduled task - Authentication: CHAP + MSChapv2 for UniFi compatibility Agent Documentation: - AGENT_QUICK_REFERENCE.md: Quick reference for all specialized agents - documentation-squire.md: Documentation and task management specialist agent - Updated all agent markdown files with standardized formatting Project Organization: - Moved conversation logs to dedicated directories (guru-connect-conversation-logs, guru-rmm-conversation-logs) - Cleaned up old session JSONL files from projects/msp-tools/ - Added guru-connect infrastructure (agent, dashboard, proto, scripts, .gitea workflows) - Added guru-rmm server components and deployment configs Technical Notes: - VPN IP pool: 192.168.4.x (client gets 192.168.4.6) - Remote network: 192.168.0.0/24 (router at 192.168.0.10) - PSK: rrClvnmUeXEFo90Ol+z7tfsAZHeSK6w7 - Credentials: pst-admin / 24Hearts$ Files: 15 VPN scripts, 2 agent docs, conversation log reorganization, guru-connect/guru-rmm infrastructure additions Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
224 lines
6.6 KiB
Rust
224 lines
6.6 KiB
Rust
//! 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,
|
|
};
|
|
|
|
// X button constants (not exported in windows crate 0.58+)
|
|
#[cfg(windows)]
|
|
const XBUTTON1: u32 = 0x0001;
|
|
#[cfg(windows)]
|
|
const XBUTTON2: u32 = 0x0002;
|
|
|
|
#[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),
|
|
MouseButton::X2 => (MOUSEEVENTF_XDOWN, XBUTTON2),
|
|
};
|
|
|
|
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),
|
|
MouseButton::X2 => (MOUSEEVENTF_XUP, XBUTTON2),
|
|
};
|
|
|
|
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")
|
|
}
|
|
}
|