Add VPN configuration tools and agent documentation
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>
This commit is contained in:
296
projects/msp-tools/guru-connect/agent/src/input/keyboard.rs
Normal file
296
projects/msp-tools/guru-connect/agent/src/input/keyboard.rs
Normal file
@@ -0,0 +1,296 @@
|
||||
//! Keyboard input simulation using Windows SendInput API
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
#[cfg(windows)]
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::{
|
||||
SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBD_EVENT_FLAGS, KEYEVENTF_EXTENDEDKEY,
|
||||
KEYEVENTF_KEYUP, KEYEVENTF_SCANCODE, KEYEVENTF_UNICODE, KEYBDINPUT,
|
||||
MapVirtualKeyW, MAPVK_VK_TO_VSC_EX,
|
||||
};
|
||||
|
||||
/// Keyboard input controller
|
||||
pub struct KeyboardController {
|
||||
// Track modifier states for proper handling
|
||||
#[allow(dead_code)]
|
||||
modifiers: ModifierState,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ModifierState {
|
||||
ctrl: bool,
|
||||
alt: bool,
|
||||
shift: bool,
|
||||
meta: bool,
|
||||
}
|
||||
|
||||
impl KeyboardController {
|
||||
/// Create a new keyboard controller
|
||||
pub fn new() -> Result<Self> {
|
||||
Ok(Self {
|
||||
modifiers: ModifierState::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Press a key down by virtual key code
|
||||
#[cfg(windows)]
|
||||
pub fn key_down(&mut self, vk_code: u16) -> Result<()> {
|
||||
self.send_key(vk_code, true)
|
||||
}
|
||||
|
||||
/// Release a key by virtual key code
|
||||
#[cfg(windows)]
|
||||
pub fn key_up(&mut self, vk_code: u16) -> Result<()> {
|
||||
self.send_key(vk_code, false)
|
||||
}
|
||||
|
||||
/// Send a key event
|
||||
#[cfg(windows)]
|
||||
fn send_key(&mut self, vk_code: u16, down: bool) -> Result<()> {
|
||||
// Get scan code from virtual key
|
||||
let scan_code = unsafe { MapVirtualKeyW(vk_code as u32, MAPVK_VK_TO_VSC_EX) as u16 };
|
||||
|
||||
let mut flags = KEYBD_EVENT_FLAGS::default();
|
||||
|
||||
// Add extended key flag for certain keys
|
||||
if Self::is_extended_key(vk_code) || (scan_code >> 8) == 0xE0 {
|
||||
flags |= KEYEVENTF_EXTENDEDKEY;
|
||||
}
|
||||
|
||||
if !down {
|
||||
flags |= KEYEVENTF_KEYUP;
|
||||
}
|
||||
|
||||
let input = INPUT {
|
||||
r#type: INPUT_KEYBOARD,
|
||||
Anonymous: INPUT_0 {
|
||||
ki: KEYBDINPUT {
|
||||
wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(vk_code),
|
||||
wScan: scan_code,
|
||||
dwFlags: flags,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
self.send_input(&[input])
|
||||
}
|
||||
|
||||
/// Type a unicode character
|
||||
#[cfg(windows)]
|
||||
pub fn type_char(&mut self, ch: char) -> Result<()> {
|
||||
let mut inputs = Vec::new();
|
||||
let mut buf = [0u16; 2];
|
||||
let encoded = ch.encode_utf16(&mut buf);
|
||||
|
||||
// For characters that fit in a single u16
|
||||
for &code_unit in encoded.iter() {
|
||||
// Key down
|
||||
inputs.push(INPUT {
|
||||
r#type: INPUT_KEYBOARD,
|
||||
Anonymous: INPUT_0 {
|
||||
ki: KEYBDINPUT {
|
||||
wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0),
|
||||
wScan: code_unit,
|
||||
dwFlags: KEYEVENTF_UNICODE,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Key up
|
||||
inputs.push(INPUT {
|
||||
r#type: INPUT_KEYBOARD,
|
||||
Anonymous: INPUT_0 {
|
||||
ki: KEYBDINPUT {
|
||||
wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0),
|
||||
wScan: code_unit,
|
||||
dwFlags: KEYEVENTF_UNICODE | KEYEVENTF_KEYUP,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
self.send_input(&inputs)
|
||||
}
|
||||
|
||||
/// Type a string of text
|
||||
#[cfg(windows)]
|
||||
pub fn type_string(&mut self, text: &str) -> Result<()> {
|
||||
for ch in text.chars() {
|
||||
self.type_char(ch)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send Secure Attention Sequence (Ctrl+Alt+Delete)
|
||||
///
|
||||
/// This uses a multi-tier approach:
|
||||
/// 1. Try the GuruConnect SAS Service (runs as SYSTEM, handles via named pipe)
|
||||
/// 2. Try the sas.dll directly (requires SYSTEM privileges)
|
||||
/// 3. Fallback to key simulation (won't work on secure desktop)
|
||||
#[cfg(windows)]
|
||||
pub fn send_sas(&mut self) -> Result<()> {
|
||||
// Tier 1: Try the SAS service (named pipe IPC to SYSTEM service)
|
||||
if let Ok(()) = crate::sas_client::request_sas() {
|
||||
tracing::info!("SAS sent via GuruConnect SAS Service");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!("SAS service not available, trying direct sas.dll...");
|
||||
|
||||
// Tier 2: Try using the sas.dll directly (requires SYSTEM privileges)
|
||||
use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW};
|
||||
use windows::core::PCWSTR;
|
||||
|
||||
unsafe {
|
||||
let dll_name: Vec<u16> = "sas.dll\0".encode_utf16().collect();
|
||||
let lib = LoadLibraryW(PCWSTR(dll_name.as_ptr()));
|
||||
|
||||
if let Ok(lib) = lib {
|
||||
let proc_name = b"SendSAS\0";
|
||||
if let Some(proc) = GetProcAddress(lib, windows::core::PCSTR(proc_name.as_ptr())) {
|
||||
// SendSAS takes a BOOL parameter: FALSE for Ctrl+Alt+Del
|
||||
let send_sas: extern "system" fn(i32) = std::mem::transmute(proc);
|
||||
send_sas(0); // FALSE = Ctrl+Alt+Del
|
||||
tracing::info!("SAS sent via direct sas.dll call");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tier 3: Fallback - try sending the keys (won't work on secure desktop)
|
||||
tracing::warn!("SAS service and sas.dll not available, Ctrl+Alt+Del may not work");
|
||||
|
||||
// VK codes
|
||||
const VK_CONTROL: u16 = 0x11;
|
||||
const VK_MENU: u16 = 0x12; // Alt
|
||||
const VK_DELETE: u16 = 0x2E;
|
||||
|
||||
// Press keys
|
||||
self.key_down(VK_CONTROL)?;
|
||||
self.key_down(VK_MENU)?;
|
||||
self.key_down(VK_DELETE)?;
|
||||
|
||||
// Release keys
|
||||
self.key_up(VK_DELETE)?;
|
||||
self.key_up(VK_MENU)?;
|
||||
self.key_up(VK_CONTROL)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if a virtual key code is an extended key
|
||||
#[cfg(windows)]
|
||||
fn is_extended_key(vk: u16) -> bool {
|
||||
matches!(
|
||||
vk,
|
||||
0x21..=0x28 | // Page Up, Page Down, End, Home, Arrow keys
|
||||
0x2D | 0x2E | // Insert, Delete
|
||||
0x5B | 0x5C | // Left/Right Windows keys
|
||||
0x5D | // Applications key
|
||||
0x6F | // Numpad Divide
|
||||
0x90 | // Num Lock
|
||||
0x91 // Scroll Lock
|
||||
)
|
||||
}
|
||||
|
||||
/// 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 key_down(&mut self, _vk_code: u16) -> Result<()> {
|
||||
anyhow::bail!("Keyboard input only supported on Windows")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn key_up(&mut self, _vk_code: u16) -> Result<()> {
|
||||
anyhow::bail!("Keyboard input only supported on Windows")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn type_char(&mut self, _ch: char) -> Result<()> {
|
||||
anyhow::bail!("Keyboard input only supported on Windows")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn send_sas(&mut self) -> Result<()> {
|
||||
anyhow::bail!("SAS only supported on Windows")
|
||||
}
|
||||
}
|
||||
|
||||
/// Common Windows virtual key codes
|
||||
#[allow(dead_code)]
|
||||
pub mod vk {
|
||||
pub const BACK: u16 = 0x08;
|
||||
pub const TAB: u16 = 0x09;
|
||||
pub const RETURN: u16 = 0x0D;
|
||||
pub const SHIFT: u16 = 0x10;
|
||||
pub const CONTROL: u16 = 0x11;
|
||||
pub const MENU: u16 = 0x12; // Alt
|
||||
pub const PAUSE: u16 = 0x13;
|
||||
pub const CAPITAL: u16 = 0x14; // Caps Lock
|
||||
pub const ESCAPE: u16 = 0x1B;
|
||||
pub const SPACE: u16 = 0x20;
|
||||
pub const PRIOR: u16 = 0x21; // Page Up
|
||||
pub const NEXT: u16 = 0x22; // Page Down
|
||||
pub const END: u16 = 0x23;
|
||||
pub const HOME: u16 = 0x24;
|
||||
pub const LEFT: u16 = 0x25;
|
||||
pub const UP: u16 = 0x26;
|
||||
pub const RIGHT: u16 = 0x27;
|
||||
pub const DOWN: u16 = 0x28;
|
||||
pub const INSERT: u16 = 0x2D;
|
||||
pub const DELETE: u16 = 0x2E;
|
||||
|
||||
// 0-9 keys
|
||||
pub const KEY_0: u16 = 0x30;
|
||||
pub const KEY_9: u16 = 0x39;
|
||||
|
||||
// A-Z keys
|
||||
pub const KEY_A: u16 = 0x41;
|
||||
pub const KEY_Z: u16 = 0x5A;
|
||||
|
||||
// Windows keys
|
||||
pub const LWIN: u16 = 0x5B;
|
||||
pub const RWIN: u16 = 0x5C;
|
||||
|
||||
// Function keys
|
||||
pub const F1: u16 = 0x70;
|
||||
pub const F2: u16 = 0x71;
|
||||
pub const F3: u16 = 0x72;
|
||||
pub const F4: u16 = 0x73;
|
||||
pub const F5: u16 = 0x74;
|
||||
pub const F6: u16 = 0x75;
|
||||
pub const F7: u16 = 0x76;
|
||||
pub const F8: u16 = 0x77;
|
||||
pub const F9: u16 = 0x78;
|
||||
pub const F10: u16 = 0x79;
|
||||
pub const F11: u16 = 0x7A;
|
||||
pub const F12: u16 = 0x7B;
|
||||
|
||||
// Modifier keys
|
||||
pub const LSHIFT: u16 = 0xA0;
|
||||
pub const RSHIFT: u16 = 0xA1;
|
||||
pub const LCONTROL: u16 = 0xA2;
|
||||
pub const RCONTROL: u16 = 0xA3;
|
||||
pub const LMENU: u16 = 0xA4; // Left Alt
|
||||
pub const RMENU: u16 = 0xA5; // Right Alt
|
||||
}
|
||||
91
projects/msp-tools/guru-connect/agent/src/input/mod.rs
Normal file
91
projects/msp-tools/guru-connect/agent/src/input/mod.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
//! Input injection module
|
||||
//!
|
||||
//! Handles mouse and keyboard input simulation using Windows SendInput API.
|
||||
|
||||
mod mouse;
|
||||
mod keyboard;
|
||||
|
||||
pub use mouse::MouseController;
|
||||
pub use keyboard::KeyboardController;
|
||||
|
||||
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
|
||||
pub fn mouse(&mut self) -> &mut MouseController {
|
||||
&mut self.mouse
|
||||
}
|
||||
|
||||
/// Get keyboard controller
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type a unicode character
|
||||
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,
|
||||
X1,
|
||||
X2,
|
||||
}
|
||||
|
||||
impl Default for InputController {
|
||||
fn default() -> Self {
|
||||
Self::new().expect("Failed to create input controller")
|
||||
}
|
||||
}
|
||||
223
projects/msp-tools/guru-connect/agent/src/input/mouse.rs
Normal file
223
projects/msp-tools/guru-connect/agent/src/input/mouse.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
//! 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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user