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>
149 lines
4.4 KiB
Rust
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
|
|
}
|
|
}
|