Files
claudetools/projects/msp-tools/guru-connect/agent/src/capture/gdi.rs
Mike Swanson a602118f6d 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>
2026-01-18 11:51:47 -07:00

149 lines
4.4 KiB
Rust

//! GDI screen capture fallback
//!
//! Uses Windows GDI (Graphics Device Interface) for screen capture.
//! Slower than DXGI but works on older systems and edge cases.
use super::{CapturedFrame, Capturer, Display};
use anyhow::Result;
use std::time::Instant;
use windows::Win32::Graphics::Gdi::{
BitBlt, CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject,
GetDIBits, SelectObject, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS,
SRCCOPY, GetDC, ReleaseDC,
};
use windows::Win32::Foundation::HWND;
/// GDI-based screen capturer
pub struct GdiCapturer {
display: Display,
width: u32,
height: u32,
}
impl GdiCapturer {
/// Create a new GDI capturer for the specified display
pub fn new(display: Display) -> Result<Self> {
Ok(Self {
width: display.width,
height: display.height,
display,
})
}
/// Capture the screen using GDI
fn capture_gdi(&self) -> Result<Vec<u8>> {
unsafe {
// Get device context for the entire screen
let screen_dc = GetDC(HWND::default());
if screen_dc.is_invalid() {
anyhow::bail!("Failed to get screen DC");
}
// Create compatible DC and bitmap
let mem_dc = CreateCompatibleDC(screen_dc);
if mem_dc.is_invalid() {
ReleaseDC(HWND::default(), screen_dc);
anyhow::bail!("Failed to create compatible DC");
}
let bitmap = CreateCompatibleBitmap(screen_dc, self.width as i32, self.height as i32);
if bitmap.is_invalid() {
DeleteDC(mem_dc);
ReleaseDC(HWND::default(), screen_dc);
anyhow::bail!("Failed to create compatible bitmap");
}
// Select bitmap into memory DC
let old_bitmap = SelectObject(mem_dc, bitmap);
// Copy screen to memory DC
if let Err(e) = BitBlt(
mem_dc,
0,
0,
self.width as i32,
self.height as i32,
screen_dc,
self.display.x,
self.display.y,
SRCCOPY,
) {
SelectObject(mem_dc, old_bitmap);
DeleteObject(bitmap);
DeleteDC(mem_dc);
ReleaseDC(HWND::default(), screen_dc);
anyhow::bail!("BitBlt failed: {}", e);
}
// Prepare bitmap info for GetDIBits
let mut bmi = BITMAPINFO {
bmiHeader: BITMAPINFOHEADER {
biSize: std::mem::size_of::<BITMAPINFOHEADER>() as u32,
biWidth: self.width as i32,
biHeight: -(self.height as i32), // Negative for top-down
biPlanes: 1,
biBitCount: 32,
biCompression: BI_RGB.0,
biSizeImage: 0,
biXPelsPerMeter: 0,
biYPelsPerMeter: 0,
biClrUsed: 0,
biClrImportant: 0,
},
bmiColors: [Default::default()],
};
// Allocate buffer for pixel data
let buffer_size = (self.width * self.height * 4) as usize;
let mut data = vec![0u8; buffer_size];
// Get the bits
let lines = GetDIBits(
mem_dc,
bitmap,
0,
self.height,
Some(data.as_mut_ptr() as *mut _),
&mut bmi,
DIB_RGB_COLORS,
);
// Cleanup
SelectObject(mem_dc, old_bitmap);
DeleteObject(bitmap);
DeleteDC(mem_dc);
ReleaseDC(HWND::default(), screen_dc);
if lines == 0 {
anyhow::bail!("GetDIBits failed");
}
Ok(data)
}
}
}
impl Capturer for GdiCapturer {
fn capture(&mut self) -> Result<Option<CapturedFrame>> {
let data = self.capture_gdi()?;
Ok(Some(CapturedFrame {
width: self.width,
height: self.height,
data,
timestamp: Instant::now(),
display_id: self.display.id,
dirty_rects: None, // GDI doesn't provide dirty rects
}))
}
fn display(&self) -> &Display {
&self.display
}
fn is_valid(&self) -> bool {
true
}
}