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>
174 lines
4.9 KiB
Rust
174 lines
4.9 KiB
Rust
//! Low-level keyboard hook for capturing all keys including Win key
|
|
|
|
use super::InputEvent;
|
|
#[cfg(windows)]
|
|
use crate::proto;
|
|
use anyhow::Result;
|
|
use tokio::sync::mpsc;
|
|
#[cfg(windows)]
|
|
use tracing::trace;
|
|
|
|
#[cfg(windows)]
|
|
use windows::{
|
|
Win32::Foundation::{LPARAM, LRESULT, WPARAM},
|
|
Win32::UI::WindowsAndMessaging::{
|
|
CallNextHookEx, DispatchMessageW, GetMessageW, PeekMessageW, SetWindowsHookExW,
|
|
TranslateMessage, UnhookWindowsHookEx, HHOOK, KBDLLHOOKSTRUCT, MSG, PM_REMOVE,
|
|
WH_KEYBOARD_LL, WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP,
|
|
},
|
|
};
|
|
|
|
#[cfg(windows)]
|
|
use std::sync::OnceLock;
|
|
|
|
#[cfg(windows)]
|
|
static INPUT_TX: OnceLock<mpsc::Sender<InputEvent>> = OnceLock::new();
|
|
|
|
#[cfg(windows)]
|
|
static mut HOOK_HANDLE: HHOOK = HHOOK(std::ptr::null_mut());
|
|
|
|
/// Virtual key codes for special keys
|
|
#[cfg(windows)]
|
|
mod vk {
|
|
pub const VK_LWIN: u32 = 0x5B;
|
|
pub const VK_RWIN: u32 = 0x5C;
|
|
pub const VK_APPS: u32 = 0x5D;
|
|
pub const VK_LSHIFT: u32 = 0xA0;
|
|
pub const VK_RSHIFT: u32 = 0xA1;
|
|
pub const VK_LCONTROL: u32 = 0xA2;
|
|
pub const VK_RCONTROL: u32 = 0xA3;
|
|
pub const VK_LMENU: u32 = 0xA4; // Left Alt
|
|
pub const VK_RMENU: u32 = 0xA5; // Right Alt
|
|
pub const VK_TAB: u32 = 0x09;
|
|
pub const VK_ESCAPE: u32 = 0x1B;
|
|
pub const VK_SNAPSHOT: u32 = 0x2C; // Print Screen
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
pub struct KeyboardHook {
|
|
_hook: HHOOK,
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
impl KeyboardHook {
|
|
pub fn new(input_tx: mpsc::Sender<InputEvent>) -> Result<Self> {
|
|
// Store the sender globally for the hook callback
|
|
INPUT_TX.set(input_tx).map_err(|_| anyhow::anyhow!("Input TX already set"))?;
|
|
|
|
unsafe {
|
|
let hook = SetWindowsHookExW(
|
|
WH_KEYBOARD_LL,
|
|
Some(keyboard_hook_proc),
|
|
None,
|
|
0,
|
|
)?;
|
|
|
|
HOOK_HANDLE = hook;
|
|
Ok(Self { _hook: hook })
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
impl Drop for KeyboardHook {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
if !HOOK_HANDLE.0.is_null() {
|
|
let _ = UnhookWindowsHookEx(HOOK_HANDLE);
|
|
HOOK_HANDLE = HHOOK(std::ptr::null_mut());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
unsafe extern "system" fn keyboard_hook_proc(
|
|
code: i32,
|
|
wparam: WPARAM,
|
|
lparam: LPARAM,
|
|
) -> LRESULT {
|
|
if code >= 0 {
|
|
let kb_struct = &*(lparam.0 as *const KBDLLHOOKSTRUCT);
|
|
let vk_code = kb_struct.vkCode;
|
|
let scan_code = kb_struct.scanCode;
|
|
|
|
let is_down = wparam.0 as u32 == WM_KEYDOWN || wparam.0 as u32 == WM_SYSKEYDOWN;
|
|
let is_up = wparam.0 as u32 == WM_KEYUP || wparam.0 as u32 == WM_SYSKEYUP;
|
|
|
|
if is_down || is_up {
|
|
// Check if this is a key we want to intercept (Win key, Alt+Tab, etc.)
|
|
let should_intercept = matches!(
|
|
vk_code,
|
|
vk::VK_LWIN | vk::VK_RWIN | vk::VK_APPS
|
|
);
|
|
|
|
// Send the key event to the remote
|
|
if let Some(tx) = INPUT_TX.get() {
|
|
let event = proto::KeyEvent {
|
|
down: is_down,
|
|
key_type: proto::KeyEventType::KeyVk as i32,
|
|
vk_code,
|
|
scan_code,
|
|
unicode: String::new(),
|
|
modifiers: Some(get_current_modifiers()),
|
|
};
|
|
|
|
let _ = tx.try_send(InputEvent::Key(event));
|
|
trace!("Key hook: vk={:#x} scan={} down={}", vk_code, scan_code, is_down);
|
|
}
|
|
|
|
// For Win key, consume the event so it doesn't open Start menu locally
|
|
if should_intercept {
|
|
return LRESULT(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
CallNextHookEx(HOOK_HANDLE, code, wparam, lparam)
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn get_current_modifiers() -> proto::Modifiers {
|
|
use windows::Win32::UI::Input::KeyboardAndMouse::GetAsyncKeyState;
|
|
|
|
unsafe {
|
|
proto::Modifiers {
|
|
ctrl: GetAsyncKeyState(0x11) < 0, // VK_CONTROL
|
|
alt: GetAsyncKeyState(0x12) < 0, // VK_MENU
|
|
shift: GetAsyncKeyState(0x10) < 0, // VK_SHIFT
|
|
meta: GetAsyncKeyState(0x5B) < 0 || GetAsyncKeyState(0x5C) < 0, // VK_LWIN/RWIN
|
|
caps_lock: GetAsyncKeyState(0x14) & 1 != 0, // VK_CAPITAL
|
|
num_lock: GetAsyncKeyState(0x90) & 1 != 0, // VK_NUMLOCK
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Pump Windows message queue (required for hooks to work)
|
|
#[cfg(windows)]
|
|
pub fn pump_messages() {
|
|
unsafe {
|
|
let mut msg = MSG::default();
|
|
while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
|
|
let _ = TranslateMessage(&msg);
|
|
DispatchMessageW(&msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Non-Windows stubs
|
|
#[cfg(not(windows))]
|
|
#[allow(dead_code)]
|
|
pub struct KeyboardHook;
|
|
|
|
#[cfg(not(windows))]
|
|
#[allow(dead_code)]
|
|
impl KeyboardHook {
|
|
pub fn new(_input_tx: mpsc::Sender<InputEvent>) -> Result<Self> {
|
|
Ok(Self)
|
|
}
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
#[allow(dead_code)]
|
|
pub fn pump_messages() {}
|