Explore the GuruConnect agent codebase to understand:

1. The current agent structure in `agent/src/` - main.rs, how it starts, modules
2. The existing keyboard.rs implementation for send_sas()
3. How the agent handles startup persistence (startup.rs)
4. The Cargo.toml dependencies

I need to understand the patterns used so I can design a Windows Service that:
- Runs as SYSTEM to call SendSAS for Ctrl+Alt+Del
- Communicates with the agent via named pipe IPC
- Can be installed/uninstalled by the agent

Focus on understanding the existing code patterns, Windows API usage, and service-related code if any exists.


I'll explore the GuruConnect agent codebase to understand its structure and patterns. Let me start by finding and examining the key files.


Bash

find "C:\Users\MikeSwanson\Claude" -type f -name "*.rs" | head -20

Find Rust source files in the workspace


Perfect! I found the GuruConnect agent. Let me examine the key files to understand the structure and patterns.


     1→//! GuruConnect Agent - Remote Desktop Agent for Windows
     2→//!
     3→//! Provides screen capture, input injection, and remote control capabilities.
     4→//!
     5→//! Usage:
     6→//!   guruconnect-agent.exe [support_code]
     7→//!
     8→//! If a support code is provided, the agent will connect using that code
     9→//! for a one-time support session.
    10→
    11→// Hide console window by default on Windows (release builds)
    12→#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
    13→
    14→mod capture;
    15→mod chat;
    16→mod config;
    17→mod encoder;
    18→mod input;
    19→mod session;
    20→mod startup;
    21→mod transport;
    22→mod tray;
    23→
    24→pub mod proto {
    25→    include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
    26→}
    27→
    28→use anyhow::Result;
    29→use tracing::{info, error, warn, Level};
    30→use tracing_subscriber::FmtSubscriber;
    31→
    32→#[cfg(windows)]
    33→use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION};
    34→#[cfg(windows)]
    35→use windows::core::PCWSTR;
    36→#[cfg(windows)]
    37→use windows::Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY};
    38→#[cfg(windows)]
    39→use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
    40→#[cfg(windows)]
    41→use windows::Win32::System::Console::{AllocConsole, FreeConsole, GetConsoleWindow};
    42→#[cfg(windows)]
    43→use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW, SW_HIDE};
    44→
    45→/// Extract a 6-digit support code from the executable's filename.
    46→/// Looks for patterns like "GuruConnect-123456.exe" or "123456.exe"
    47→fn extract_code_from_filename() -> Option<String> {
    48→    // Get the path to the current executable
    49→    let exe_path = std::env::current_exe().ok()?;
    50→    let filename = exe_path.file_stem()?.to_str()?;
    51→
    52→    // Look for a 6-digit number in the filename
    53→    // Try common patterns: "Name-123456", "Name_123456", "123456"
    54→    for part in filename.split(|c| c == '-' || c == '_' || c == '.') {
    55→        let trimmed = part.trim();
    56→        if trimmed.len() == 6 && trimmed.chars().all(|c| c.is_ascii_digit()) {
    57→            return Some(trimmed.to_string());
    58→        }
    59→    }
    60→
    61→    // Also check if the last 6 characters are digits (e.g., "GuruConnect123456")
    62→    if filename.len() >= 6 {
    63→        let last_six = &filename[filename.len() - 6..];
    64→        if last_six.chars().all(|c| c.is_ascii_digit()) {
    65→            return Some(last_six.to_string());
    66→        }
    67→    }
    68→
    69→    None
    70→}
    71→
    72→/// Check if the process is running with elevated privileges (Windows only)
    73→#[cfg(windows)]
    74→fn is_elevated() -> bool {
    75→    unsafe {
    76→        let mut token_handle = windows::Win32::Foundation::HANDLE::default();
    77→        if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle).is_err() {
    78→            return false;
    79→        }
    80→
    81→        let mut elevation = TOKEN_ELEVATION::default();
    82→        let mut size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;
    83→
    84→        let result = GetTokenInformation(
    85→            token_handle,
    86→            TokenElevation,
    87→            Some(&mut elevation as *mut _ as *mut _),
    88→            size,
    89→            &mut size,
    90→        );
    91→
    92→        let _ = windows::Win32::Foundation::CloseHandle(token_handle);
    93→
    94→        result.is_ok() && elevation.TokenIsElevated != 0
    95→    }
    96→}
    97→
    98→#[cfg(not(windows))]
    99→fn is_elevated() -> bool {
   100→    // On non-Windows, check if running as root
   101→    unsafe { libc::geteuid() == 0 }
   102→}
   103→
   104→/// Show a message box to the user (Windows only)
   105→#[cfg(windows)]
   106→fn show_message_box(title: &str, message: &str) {
   107→    use std::ffi::OsStr;
   108→    use std::os::windows::ffi::OsStrExt;
   109→
   110→    // Convert strings to wide strings for Windows API
   111→    let title_wide: Vec<u16> = OsStr::new(title)
   112→        .encode_wide()
   113→        .chain(std::iter::once(0))
   114→        .collect();
   115→    let message_wide: Vec<u16> = OsStr::new(message)
   116→        .encode_wide()
   117→        .chain(std::iter::once(0))
   118→        .collect();
   119→
   120→    unsafe {
   121→        MessageBoxW(
   122→            None,
   123→            PCWSTR(message_wide.as_ptr()),
   124→            PCWSTR(title_wide.as_ptr()),
   125→            MB_OK | MB_ICONINFORMATION,
   126→        );
   127→    }
   128→}
   129→
   130→#[cfg(not(windows))]
   131→fn show_message_box(_title: &str, _message: &str) {
   132→    // No-op on non-Windows platforms
   133→}
   134→
   135→/// Show the debug console window (Windows only)
   136→#[cfg(windows)]
   137→fn show_debug_console() {
   138→    unsafe {
   139→        // Check if we already have a console
   140→        let hwnd = GetConsoleWindow();
   141→        if hwnd.0 == std::ptr::null_mut() {
   142→            // No console, allocate one
   143→            let _ = AllocConsole();
   144→            info!("Debug console window opened");
   145→        } else {
   146→            // Console exists, make sure it's visible
   147→            let _ = ShowWindow(hwnd, SW_SHOW);
   148→            info!("Debug console window shown");
   149→        }
   150→    }
   151→}
   152→
   153→#[cfg(not(windows))]
   154→fn show_debug_console() {
   155→    // No-op on non-Windows platforms
   156→}
   157→
   158→#[tokio::main]
   159→async fn main() -> Result<()> {
   160→    // Initialize logging
   161→    let subscriber = FmtSubscriber::builder()
   162→        .with_max_level(Level::INFO)
   163→        .with_target(true)
   164→        .with_thread_ids(true)
   165→        .init();
   166→
   167→    info!("GuruConnect Agent v{}", env!("CARGO_PKG_VERSION"));
   168→
   169→    // Check and log elevation status
   170→    if is_elevated() {
   171→        info!("Running with elevated (administrator) privileges");
   172→    } else {
   173→        info!("Running with standard user privileges");
   174→    }
   175→
   176→    // Extract support code from executable filename
   177→    // e.g., GuruConnect-123456.exe -> 123456
   178→    let support_code = extract_code_from_filename();
   179→    if let Some(ref code) = support_code {
   180→        info!("Support code from filename: {}", code);
   181→    }
   182→
   183→    // Load configuration
   184→    let mut config = config::Config::load()?;
   185→    config.support_code = support_code;
   186→    info!("Loaded configuration for server: {}", config.server_url);
   187→
   188→    // Run the agent
   189→    if let Err(e) = run_agent(config).await {
   190→        error!("Agent error: {}", e);
   191→        return Err(e);
   192→    }
   193→
   194→    Ok(())
   195→}
   196→
   197→/// Clean up before exiting (remove from startup, etc.)
   198→/// Called when user explicitly ends session or support session completes
   199→fn cleanup_on_exit() {
   200→    info!("Cleaning up before exit");
   201→    if let Err(e) = startup::remove_from_startup() {
   202→        warn!("Failed to remove from startup: {}", e);
   203→    }
   204→}
   205→
   206→async fn run_agent(config: config::Config) -> Result<()> {
   207→    // Create session manager with elevation status
   208→    let elevated = is_elevated();
   209→    let mut session = session::SessionManager::new(config.clone(), elevated);
   210→    let is_support_session = config.support_code.is_some();
   211→    let hostname = config.hostname();
   212→
   213→    // Add to startup so we reconnect after reboot
   214→    // Persistent agents (no support code) should ALWAYS be in startup
   215→    // Support sessions only need startup temporarily while active
   216→    if let Err(e) = startup::add_to_startup() {
   217→        warn!("Failed to add to startup: {}. Agent won't persist through reboot.", e);
   218→    }
   219→
   220→    // Create tray icon
   221→    // Only support sessions can be ended by user - persistent agents are admin-managed
   222→    let tray = match tray::TrayController::new(&hostname, config.support_code.as_deref(), is_support_session) {
   223→        Ok(t) => {
   224→            info!("Tray icon created");
   225→            Some(t)
   226→        }
   227→        Err(e) => {
   228→            warn!("Failed to create tray icon: {}. Continuing without tray.", e);
   229→            None
   230→        }
   231→    };
   232→
   233→    // Create chat controller
   234→    let chat_ctrl = chat::ChatController::new();
   235→    if chat_ctrl.is_some() {
   236→        info!("Chat controller created");
   237→    }
   238→
   239→    // Connect to server and run main loop
   240→    loop {
   241→        info!("Connecting to server...");
   242→
   243→        // Check if user requested exit via tray before connecting (support sessions only)
   244→        if is_support_session {
   245→            if let Some(ref t) = tray {
   246→                if t.exit_requested() {
   247→                    info!("Exit requested by user");
   248→                    cleanup_on_exit();
   249→                    return Ok(());
   250→                }
   251→            }
   252→        }
   253→
   254→        match session.connect().await {
   255→            Ok(_) => {
   256→                info!("Connected to server");
   257→
   258→                // Update tray status
   259→                if let Some(ref t) = tray {
   260→                    t.update_status("Status: Connected");
   261→                }
   262→
   263→                // Run session until disconnect, passing tray and chat for event processing
   264→                if let Err(e) = session.run_with_tray(tray.as_ref(), chat_ctrl.as_ref()).await {
   265→                    let error_msg = e.to_string();
   266→
   267→                    // Check if this is a user-initiated exit
   268→                    if error_msg.contains("USER_EXIT") {
   269→                        info!("Session ended by user");
   270→                        cleanup_on_exit();
   271→                        return Ok(());
   272→                    }
   273→
   274→                    // Check if this is a cancellation
   275→                    if error_msg.contains("SESSION_CANCELLED") {
   276→                        info!("Session was cancelled by technician");
   277→                        cleanup_on_exit();
   278→                        show_message_box(
   279→                            "Support Session Ended",
   280→                            "The support session was cancelled by the technician.\n\nThis window will close automatically.",
   281→                        );
   282→                        // Exit cleanly without reconnecting
   283→                        return Ok(());
   284→                    }
   285→
   286→                    // Check if this is an admin disconnect (uninstall)
   287→                    if error_msg.contains("ADMIN_DISCONNECT") {
   288→                        info!("Session was disconnected by administrator - uninstalling");
   289→                        if let Err(e) = startup::uninstall() {
   290→                            warn!("Uninstall failed: {}", e);
   291→                        }
   292→                        show_message_box(
   293→                            "Remote Session Ended",
   294→                            "The remote support session has been ended by the administrator.\n\nThe agent will be removed from this computer.",
   295→                        );
   296→                        return Ok(());
   297→                    }
   298→
   299→                    error!("Session error: {}", e);
   300→                }
   301→            }
   302→            Err(e) => {
   303→                let error_msg = e.to_string();
   304→
   305→                // Check if connection was rejected due to cancelled code
   306→                if error_msg.contains("cancelled") {
   307→                    info!("Support code was cancelled before connection");
   308→                    cleanup_on_exit();
   309→                    show_message_box(
   310→                        "Support Session Cancelled",
   311→                        "This support session has been cancelled.\n\nPlease contact your technician for a new support code.",
   312→                    );
   313→                    // Exit cleanly without reconnecting
   314→                    return Ok(());
   315→                }
   316→
   317→                error!("Connection failed: {}", e);
   318→            }
   319→        }
   320→
   321→        // For support sessions, don't reconnect if something goes wrong
   322→        if is_support_session {
   323→            info!("Support session ended, not reconnecting");
   324→            cleanup_on_exit();
   325→            return Ok(());
   326→        }
   327→
   328→        // Wait before reconnecting (persistent agents only - support sessions already exited above)
   329→        info!("Reconnecting in 5 seconds...");
   330→        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
   331→    }
   332→}
   333→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! GuruConnect Agent - Remote Desktop Agent for Windows
//!
//! Provides screen capture, input injection, and remote control capabilities.
//!
//! Usage:
//!   guruconnect-agent.exe [support_code]
//!
//! If a support code is provided, the agent will connect using that code
//! for a one-time support session.

// Hide console window by default on Windows (release builds)
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod capture;
mod chat;
mod config;
mod encoder;
mod input;
mod session;
mod startup;
mod transport;
mod tray;

pub mod proto {
    include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
}

use anyhow::Result;
use tracing::{info, error, warn, Level};
use tracing_subscriber::FmtSubscriber;

#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION};
#[cfg(windows)]
use windows::core::PCWSTR;
#[cfg(windows)]
use windows::Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY};
#[cfg(windows)]
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
#[cfg(windows)]
use windows::Win32::System::Console::{AllocConsole, FreeConsole, GetConsoleWindow};
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW, SW_HIDE};

/// Extract a 6-digit support code from the executable's filename.
/// Looks for patterns like "GuruConnect-123456.exe" or "123456.exe"
fn extract_code_from_filename() -> Option<String> {
    // Get the path to the current executable
    let exe_path = std::env::current_exe().ok()?;
    let filename = exe_path.file_stem()?.to_str()?;

    // Look for a 6-digit number in the filename
    // Try common patterns: "Name-123456", "Name_123456", "123456"
    for part in filename.split(|c| c == '-' || c == '_' || c == '.') {
        let trimmed = part.trim();
        if trimmed.len() == 6 && trimmed.chars().all(|c| c.is_ascii_digit()) {
            return Some(trimmed.to_string());
        }
    }

    // Also check if the last 6 characters are digits (e.g., "GuruConnect123456")
    if filename.len() >= 6 {
        let last_six = &filename[filename.len() - 6..];
        if last_six.chars().all(|c| c.is_ascii_digit()) {
            return Some(last_six.to_string());
        }
    }

    None
}

/// Check if the process is running with elevated privileges (Windows only)
#[cfg(windows)]
fn is_elevated() -> bool {
    unsafe {
        let mut token_handle = windows::Win32::Foundation::HANDLE::default();
        if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle).is_err() {
            return false;
        }

        let mut elevation = TOKEN_ELEVATION::default();
        let mut size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;

        let result = GetTokenInformation(
            token_handle,
            TokenElevation,
            Some(&mut elevation as *mut _ as *mut _),
            size,
            &mut size,
        );

        let _ = windows::Win32::Foundation::CloseHandle(token_handle);

        result.is_ok() && elevation.TokenIsElevated != 0
    }
}

#[cfg(not(windows))]
fn is_elevated() -> bool {
    // On non-Windows, check if running as root
    unsafe { libc::geteuid() == 0 }
}

/// Show a message box to the user (Windows only)
#[cfg(windows)]
fn show_message_box(title: &str, message: &str) {
    use std::ffi::OsStr;
    use std::os::windows::ffi::OsStrExt;

    // Convert strings to wide strings for Windows API
    let title_wide: Vec<u16> = OsStr::new(title)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();
    let message_wide: Vec<u16> = OsStr::new(message)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();

    unsafe {
        MessageBoxW(
            None,
            PCWSTR(message_wide.as_ptr()),
            PCWSTR(title_wide.as_ptr()),
            MB_OK | MB_ICONINFORMATION,
        );
    }
}

#[cfg(not(windows))]
fn show_message_box(_title: &str, _message: &str) {
    // No-op on non-Windows platforms
}

/// Show the debug console window (Windows only)
#[cfg(windows)]
fn show_debug_console() {
    unsafe {
        // Check if we already have a console
        let hwnd = GetConsoleWindow();
        if hwnd.0 == std::ptr::null_mut() {
            // No console, allocate one
            let _ = AllocConsole();
            info!("Debug console window opened");
        } else {
            // Console exists, make sure it's visible
            let _ = ShowWindow(hwnd, SW_SHOW);
            info!("Debug console window shown");
        }
    }
}

#[cfg(not(windows))]
fn show_debug_console() {
    // No-op on non-Windows platforms
}

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize logging
    let subscriber = FmtSubscriber::builder()
        .with_max_level(Level::INFO)
        .with_target(true)
        .with_thread_ids(true)
        .init();

    info!("GuruConnect Agent v{}", env!("CARGO_PKG_VERSION"));

    // Check and log elevation status
    if is_elevated() {
        info!("Running with elevated (administrator) privileges");
    } else {
        info!("Running with standard user privileges");
    }

    // Extract support code from executable filename
    // e.g., GuruConnect-123456.exe -> 123456
    let support_code = extract_code_from_filename();
    if let Some(ref code) = support_code {
        info!("Support code from filename: {}", code);
    }

    // Load configuration
    let mut config = config::Config::load()?;
    config.support_code = support_code;
    info!("Loaded configuration for server: {}", config.server_url);

    // Run the agent
    if let Err(e) = run_agent(config).await {
        error!("Agent error: {}", e);
        return Err(e);
    }

    Ok(())
}

/// Clean up before exiting (remove from startup, etc.)
/// Called when user explicitly ends session or support session completes
fn cleanup_on_exit() {
    info!("Cleaning up before exit");
    if let Err(e) = startup::remove_from_startup() {
        warn!("Failed to remove from startup: {}", e);
    }
}

async fn run_agent(config: config::Config) -> Result<()> {
    // Create session manager with elevation status
    let elevated = is_elevated();
    let mut session = session::SessionManager::new(config.clone(), elevated);
    let is_support_session = config.support_code.is_some();
    let hostname = config.hostname();

    // Add to startup so we reconnect after reboot
    // Persistent agents (no support code) should ALWAYS be in startup
    // Support sessions only need startup temporarily while active
    if let Err(e) = startup::add_to_startup() {
        warn!("Failed to add to startup: {}. Agent won't persist through reboot.", e);
    }

    // Create tray icon
    // Only support sessions can be ended by user - persistent agents are admin-managed
    let tray = match tray::TrayController::new(&hostname, config.support_code.as_deref(), is_support_session) {
        Ok(t) => {
            info!("Tray icon created");
            Some(t)
        }
        Err(e) => {
            warn!("Failed to create tray icon: {}. Continuing without tray.", e);
            None
        }
    };

    // Create chat controller
    let chat_ctrl = chat::ChatController::new();
    if chat_ctrl.is_some() {
        info!("Chat controller created");
    }

    // Connect to server and run main loop
    loop {
        info!("Connecting to server...");

        // Check if user requested exit via tray before connecting (support sessions only)
        if is_support_session {
            if let Some(ref t) = tray {
                if t.exit_requested() {
                    info!("Exit requested by user");
                    cleanup_on_exit();
                    return Ok(());
                }
            }
        }

        match session.connect().await {
            Ok(_) => {
                info!("Connected to server");

                // Update tray status
                if let Some(ref t) = tray {
                    t.update_status("Status: Connected");
                }

                // Run session until disconnect, passing tray and chat for event processing
                if let Err(e) = session.run_with_tray(tray.as_ref(), chat_ctrl.as_ref()).await {
                    let error_msg = e.to_string();

                    // Check if this is a user-initiated exit
                    if error_msg.contains("USER_EXIT") {
                        info!("Session ended by user");
                        cleanup_on_exit();
                        return Ok(());
                    }

                    // Check if this is a cancellation
                    if error_msg.contains("SESSION_CANCELLED") {
                        info!("Session was cancelled by technician");
                        cleanup_on_exit();
                        show_message_box(
                            "Support Session Ended",
                            "The support session was cancelled by the technician.\n\nThis window will close automatically.",
                        );
                        // Exit cleanly without reconnecting
                        return Ok(());
                    }

                    // Check if this is an admin disconnect (uninstall)
                    if error_msg.contains("ADMIN_DISCONNECT") {
                        info!("Session was disconnected by administrator - uninstalling");
                        if let Err(e) = startup::uninstall() {
                            warn!("Uninstall failed: {}", e);
                        }
                        show_message_box(
                            "Remote Session Ended",
                            "The remote support session has been ended by the administrator.\n\nThe agent will be removed from this computer.",
                        );
                        return Ok(());
                    }

                    error!("Session error: {}", e);
                }
            }
            Err(e) => {
                let error_msg = e.to_string();

                // Check if connection was rejected due to cancelled code
                if error_msg.contains("cancelled") {
                    info!("Support code was cancelled before connection");
                    cleanup_on_exit();
                    show_message_box(
                        "Support Session Cancelled",
                        "This support session has been cancelled.\n\nPlease contact your technician for a new support code.",
                    );
                    // Exit cleanly without reconnecting
                    return Ok(());
                }

                error!("Connection failed: {}", e);
            }
        }

        // For support sessions, don't reconnect if something goes wrong
        if is_support_session {
            info!("Support session ended, not reconnecting");
            cleanup_on_exit();
            return Ok(());
        }

        // Wait before reconnecting (persistent agents only - support sessions already exited above)
        info!("Reconnecting in 5 seconds...");
        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
    }
}


     1→//! Keyboard input simulation using Windows SendInput API
     2→
     3→use anyhow::Result;
     4→
     5→#[cfg(windows)]
     6→use windows::Win32::UI::Input::KeyboardAndMouse::{
     7→    SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBD_EVENT_FLAGS, KEYEVENTF_EXTENDEDKEY,
     8→    KEYEVENTF_KEYUP, KEYEVENTF_SCANCODE, KEYEVENTF_UNICODE, KEYBDINPUT,
     9→    MapVirtualKeyW, MAPVK_VK_TO_VSC_EX,
    10→};
    11→
    12→/// Keyboard input controller
    13→pub struct KeyboardController {
    14→    // Track modifier states for proper handling
    15→    #[allow(dead_code)]
    16→    modifiers: ModifierState,
    17→}
    18→
    19→#[derive(Default)]
    20→struct ModifierState {
    21→    ctrl: bool,
    22→    alt: bool,
    23→    shift: bool,
    24→    meta: bool,
    25→}
    26→
    27→impl KeyboardController {
    28→    /// Create a new keyboard controller
    29→    pub fn new() -> Result<Self> {
    30→        Ok(Self {
    31→            modifiers: ModifierState::default(),
    32→        })
    33→    }
    34→
    35→    /// Press a key down by virtual key code
    36→    #[cfg(windows)]
    37→    pub fn key_down(&mut self, vk_code: u16) -> Result<()> {
    38→        self.send_key(vk_code, true)
    39→    }
    40→
    41→    /// Release a key by virtual key code
    42→    #[cfg(windows)]
    43→    pub fn key_up(&mut self, vk_code: u16) -> Result<()> {
    44→        self.send_key(vk_code, false)
    45→    }
    46→
    47→    /// Send a key event
    48→    #[cfg(windows)]
    49→    fn send_key(&mut self, vk_code: u16, down: bool) -> Result<()> {
    50→        // Get scan code from virtual key
    51→        let scan_code = unsafe { MapVirtualKeyW(vk_code as u32, MAPVK_VK_TO_VSC_EX) as u16 };
    52→
    53→        let mut flags = KEYBD_EVENT_FLAGS::default();
    54→
    55→        // Add extended key flag for certain keys
    56→        if Self::is_extended_key(vk_code) || (scan_code >> 8) == 0xE0 {
    57→            flags |= KEYEVENTF_EXTENDEDKEY;
    58→        }
    59→
    60→        if !down {
    61→            flags |= KEYEVENTF_KEYUP;
    62→        }
    63→
    64→        let input = INPUT {
    65→            r#type: INPUT_KEYBOARD,
    66→            Anonymous: INPUT_0 {
    67→                ki: KEYBDINPUT {
    68→                    wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(vk_code),
    69→                    wScan: scan_code,
    70→                    dwFlags: flags,
    71→                    time: 0,
    72→                    dwExtraInfo: 0,
    73→                },
    74→            },
    75→        };
    76→
    77→        self.send_input(&[input])
    78→    }
    79→
    80→    /// Type a unicode character
    81→    #[cfg(windows)]
    82→    pub fn type_char(&mut self, ch: char) -> Result<()> {
    83→        let mut inputs = Vec::new();
    84→        let mut buf = [0u16; 2];
    85→        let encoded = ch.encode_utf16(&mut buf);
    86→
    87→        // For characters that fit in a single u16
    88→        for &code_unit in encoded.iter() {
    89→            // Key down
    90→            inputs.push(INPUT {
    91→                r#type: INPUT_KEYBOARD,
    92→                Anonymous: INPUT_0 {
    93→                    ki: KEYBDINPUT {
    94→                        wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0),
    95→                        wScan: code_unit,
    96→                        dwFlags: KEYEVENTF_UNICODE,
    97→                        time: 0,
    98→                        dwExtraInfo: 0,
    99→                    },
   100→                },
   101→            });
   102→
   103→            // Key up
   104→            inputs.push(INPUT {
   105→                r#type: INPUT_KEYBOARD,
   106→                Anonymous: INPUT_0 {
   107→                    ki: KEYBDINPUT {
   108→                        wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0),
   109→                        wScan: code_unit,
   110→                        dwFlags: KEYEVENTF_UNICODE | KEYEVENTF_KEYUP,
   111→                        time: 0,
   112→                        dwExtraInfo: 0,
   113→                    },
   114→                },
   115→            });
   116→        }
   117→
   118→        self.send_input(&inputs)
   119→    }
   120→
   121→    /// Type a string of text
   122→    #[cfg(windows)]
   123→    pub fn type_string(&mut self, text: &str) -> Result<()> {
   124→        for ch in text.chars() {
   125→            self.type_char(ch)?;
   126→        }
   127→        Ok(())
   128→    }
   129→
   130→    /// Send Secure Attention Sequence (Ctrl+Alt+Delete)
   131→    ///
   132→    /// Note: This requires special privileges on Windows.
   133→    /// The agent typically needs to run as SYSTEM or use SAS API.
   134→    #[cfg(windows)]
   135→    pub fn send_sas(&mut self) -> Result<()> {
   136→        // Try using the SAS library if available
   137→        // For now, we'll attempt to send the key combination
   138→        // This won't work in all contexts due to Windows security
   139→
   140→        // Load the sas.dll and call SendSAS if available
   141→        use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW};
   142→        use windows::core::PCWSTR;
   143→
   144→        unsafe {
   145→            let dll_name: Vec<u16> = "sas.dll\0".encode_utf16().collect();
   146→            let lib = LoadLibraryW(PCWSTR(dll_name.as_ptr()));
   147→
   148→            if let Ok(lib) = lib {
   149→                let proc_name = b"SendSAS\0";
   150→                if let Some(proc) = GetProcAddress(lib, windows::core::PCSTR(proc_name.as_ptr())) {
   151→                    // SendSAS takes a BOOL parameter: FALSE for Ctrl+Alt+Del
   152→                    let send_sas: extern "system" fn(i32) = std::mem::transmute(proc);
   153→                    send_sas(0); // FALSE = Ctrl+Alt+Del
   154→                    return Ok(());
   155→                }
   156→            }
   157→        }
   158→
   159→        // Fallback: Try sending the keys (won't work without proper privileges)
   160→        tracing::warn!("SAS library not available, Ctrl+Alt+Del may not work");
   161→
   162→        // VK codes
   163→        const VK_CONTROL: u16 = 0x11;
   164→        const VK_MENU: u16 = 0x12; // Alt
   165→        const VK_DELETE: u16 = 0x2E;
   166→
   167→        // Press keys
   168→        self.key_down(VK_CONTROL)?;
   169→        self.key_down(VK_MENU)?;
   170→        self.key_down(VK_DELETE)?;
   171→
   172→        // Release keys
   173→        self.key_up(VK_DELETE)?;
   174→        self.key_up(VK_MENU)?;
   175→        self.key_up(VK_CONTROL)?;
   176→
   177→        Ok(())
   178→    }
   179→
   180→    /// Check if a virtual key code is an extended key
   181→    #[cfg(windows)]
   182→    fn is_extended_key(vk: u16) -> bool {
   183→        matches!(
   184→            vk,
   185→            0x21..=0x28 | // Page Up, Page Down, End, Home, Arrow keys
   186→            0x2D | 0x2E | // Insert, Delete
   187→            0x5B | 0x5C | // Left/Right Windows keys
   188→            0x5D |        // Applications key
   189→            0x6F |        // Numpad Divide
   190→            0x90 |        // Num Lock
   191→            0x91          // Scroll Lock
   192→        )
   193→    }
   194→
   195→    /// Send input events
   196→    #[cfg(windows)]
   197→    fn send_input(&self, inputs: &[INPUT]) -> Result<()> {
   198→        let sent = unsafe { SendInput(inputs, std::mem::size_of::<INPUT>() as i32) };
   199→
   200→        if sent as usize != inputs.len() {
   201→            anyhow::bail!(
   202→                "SendInput failed: sent {} of {} inputs",
   203→                sent,
   204→                inputs.len()
   205→            );
   206→        }
   207→
   208→        Ok(())
   209→    }
   210→
   211→    #[cfg(not(windows))]
   212→    pub fn key_down(&mut self, _vk_code: u16) -> Result<()> {
   213→        anyhow::bail!("Keyboard input only supported on Windows")
   214→    }
   215→
   216→    #[cfg(not(windows))]
   217→    pub fn key_up(&mut self, _vk_code: u16) -> Result<()> {
   218→        anyhow::bail!("Keyboard input only supported on Windows")
   219→    }
   220→
   221→    #[cfg(not(windows))]
   222→    pub fn type_char(&mut self, _ch: char) -> Result<()> {
   223→        anyhow::bail!("Keyboard input only supported on Windows")
   224→    }
   225→
   226→    #[cfg(not(windows))]
   227→    pub fn send_sas(&mut self) -> Result<()> {
   228→        anyhow::bail!("SAS only supported on Windows")
   229→    }
   230→}
   231→
   232→/// Common Windows virtual key codes
   233→#[allow(dead_code)]
   234→pub mod vk {
   235→    pub const BACK: u16 = 0x08;
   236→    pub const TAB: u16 = 0x09;
   237→    pub const RETURN: u16 = 0x0D;
   238→    pub const SHIFT: u16 = 0x10;
   239→    pub const CONTROL: u16 = 0x11;
   240→    pub const MENU: u16 = 0x12; // Alt
   241→    pub const PAUSE: u16 = 0x13;
   242→    pub const CAPITAL: u16 = 0x14; // Caps Lock
   243→    pub const ESCAPE: u16 = 0x1B;
   244→    pub const SPACE: u16 = 0x20;
   245→    pub const PRIOR: u16 = 0x21; // Page Up
   246→    pub const NEXT: u16 = 0x22;  // Page Down
   247→    pub const END: u16 = 0x23;
   248→    pub const HOME: u16 = 0x24;
   249→    pub const LEFT: u16 = 0x25;
   250→    pub const UP: u16 = 0x26;
   251→    pub const RIGHT: u16 = 0x27;
   252→    pub const DOWN: u16 = 0x28;
   253→    pub const INSERT: u16 = 0x2D;
   254→    pub const DELETE: u16 = 0x2E;
   255→
   256→    // 0-9 keys
   257→    pub const KEY_0: u16 = 0x30;
   258→    pub const KEY_9: u16 = 0x39;
   259→
   260→    // A-Z keys
   261→    pub const KEY_A: u16 = 0x41;
   262→    pub const KEY_Z: u16 = 0x5A;
   263→
   264→    // Windows keys
   265→    pub const LWIN: u16 = 0x5B;
   266→    pub const RWIN: u16 = 0x5C;
   267→
   268→    // Function keys
   269→    pub const F1: u16 = 0x70;
   270→    pub const F2: u16 = 0x71;
   271→    pub const F3: u16 = 0x72;
   272→    pub const F4: u16 = 0x73;
   273→    pub const F5: u16 = 0x74;
   274→    pub const F6: u16 = 0x75;
   275→    pub const F7: u16 = 0x76;
   276→    pub const F8: u16 = 0x77;
   277→    pub const F9: u16 = 0x78;
   278→    pub const F10: u16 = 0x79;
   279→    pub const F11: u16 = 0x7A;
   280→    pub const F12: u16 = 0x7B;
   281→
   282→    // Modifier keys
   283→    pub const LSHIFT: u16 = 0xA0;
   284→    pub const RSHIFT: u16 = 0xA1;
   285→    pub const LCONTROL: u16 = 0xA2;
   286→    pub const RCONTROL: u16 = 0xA3;
   287→    pub const LMENU: u16 = 0xA4; // Left Alt
   288→    pub const RMENU: u16 = 0xA5; // Right Alt
   289→}
   290→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! 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)
    ///
    /// Note: This requires special privileges on Windows.
    /// The agent typically needs to run as SYSTEM or use SAS API.
    #[cfg(windows)]
    pub fn send_sas(&mut self) -> Result<()> {
        // Try using the SAS library if available
        // For now, we'll attempt to send the key combination
        // This won't work in all contexts due to Windows security

        // Load the sas.dll and call SendSAS if available
        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
                    return Ok(());
                }
            }
        }

        // Fallback: Try sending the keys (won't work without proper privileges)
        tracing::warn!("SAS library 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
}


     1→//! Startup persistence for the agent
     2→//!
     3→//! Handles adding/removing the agent from Windows startup.
     4→
     5→use anyhow::Result;
     6→use tracing::{info, warn, error};
     7→
     8→#[cfg(windows)]
     9→use windows::Win32::System::Registry::{
    10→    RegOpenKeyExW, RegSetValueExW, RegDeleteValueW, RegCloseKey,
    11→    HKEY_CURRENT_USER, KEY_WRITE, REG_SZ,
    12→};
    13→#[cfg(windows)]
    14→use windows::core::PCWSTR;
    15→
    16→const STARTUP_KEY: &str = r"Software\Microsoft\Windows\CurrentVersion\Run";
    17→const STARTUP_VALUE_NAME: &str = "GuruConnect";
    18→
    19→/// Add the current executable to Windows startup
    20→#[cfg(windows)]
    21→pub fn add_to_startup() -> Result<()> {
    22→    use std::ffi::OsStr;
    23→    use std::os::windows::ffi::OsStrExt;
    24→
    25→    // Get the path to the current executable
    26→    let exe_path = std::env::current_exe()?;
    27→    let exe_path_str = exe_path.to_string_lossy();
    28→
    29→    info!("Adding to startup: {}", exe_path_str);
    30→
    31→    // Convert strings to wide strings
    32→    let key_path: Vec<u16> = OsStr::new(STARTUP_KEY)
    33→        .encode_wide()
    34→        .chain(std::iter::once(0))
    35→        .collect();
    36→    let value_name: Vec<u16> = OsStr::new(STARTUP_VALUE_NAME)
    37→        .encode_wide()
    38→        .chain(std::iter::once(0))
    39→        .collect();
    40→    let value_data: Vec<u16> = OsStr::new(&*exe_path_str)
    41→        .encode_wide()
    42→        .chain(std::iter::once(0))
    43→        .collect();
    44→
    45→    unsafe {
    46→        let mut hkey = windows::Win32::Foundation::HANDLE::default();
    47→
    48→        // Open the Run key
    49→        let result = RegOpenKeyExW(
    50→            HKEY_CURRENT_USER,
    51→            PCWSTR(key_path.as_ptr()),
    52→            0,
    53→            KEY_WRITE,
    54→            &mut hkey as *mut _ as *mut _,
    55→        );
    56→
    57→        if result.is_err() {
    58→            anyhow::bail!("Failed to open registry key: {:?}", result);
    59→        }
    60→
    61→        let hkey_raw = std::mem::transmute::<_, windows::Win32::System::Registry::HKEY>(hkey);
    62→
    63→        // Set the value
    64→        let data_bytes = std::slice::from_raw_parts(
    65→            value_data.as_ptr() as *const u8,
    66→            value_data.len() * 2,
    67→        );
    68→
    69→        let set_result = RegSetValueExW(
    70→            hkey_raw,
    71→            PCWSTR(value_name.as_ptr()),
    72→            0,
    73→            REG_SZ,
    74→            Some(data_bytes),
    75→        );
    76→
    77→        let _ = RegCloseKey(hkey_raw);
    78→
    79→        if set_result.is_err() {
    80→            anyhow::bail!("Failed to set registry value: {:?}", set_result);
    81→        }
    82→    }
    83→
    84→    info!("Successfully added to startup");
    85→    Ok(())
    86→}
    87→
    88→/// Remove the agent from Windows startup
    89→#[cfg(windows)]
    90→pub fn remove_from_startup() -> Result<()> {
    91→    use std::ffi::OsStr;
    92→    use std::os::windows::ffi::OsStrExt;
    93→
    94→    info!("Removing from startup");
    95→
    96→    let key_path: Vec<u16> = OsStr::new(STARTUP_KEY)
    97→        .encode_wide()
    98→        .chain(std::iter::once(0))
    99→        .collect();
   100→    let value_name: Vec<u16> = OsStr::new(STARTUP_VALUE_NAME)
   101→        .encode_wide()
   102→        .chain(std::iter::once(0))
   103→        .collect();
   104→
   105→    unsafe {
   106→        let mut hkey = windows::Win32::Foundation::HANDLE::default();
   107→
   108→        let result = RegOpenKeyExW(
   109→            HKEY_CURRENT_USER,
   110→            PCWSTR(key_path.as_ptr()),
   111→            0,
   112→            KEY_WRITE,
   113→            &mut hkey as *mut _ as *mut _,
   114→        );
   115→
   116→        if result.is_err() {
   117→            warn!("Failed to open registry key for removal: {:?}", result);
   118→            return Ok(()); // Not an error if key doesn't exist
   119→        }
   120→
   121→        let hkey_raw = std::mem::transmute::<_, windows::Win32::System::Registry::HKEY>(hkey);
   122→
   123→        let delete_result = RegDeleteValueW(hkey_raw, PCWSTR(value_name.as_ptr()));
   124→
   125→        let _ = RegCloseKey(hkey_raw);
   126→
   127→        if delete_result.is_err() {
   128→            warn!("Registry value may not exist: {:?}", delete_result);
   129→        } else {
   130→            info!("Successfully removed from startup");
   131→        }
   132→    }
   133→
   134→    Ok(())
   135→}
   136→
   137→/// Full uninstall: remove from startup and delete the executable
   138→#[cfg(windows)]
   139→pub fn uninstall() -> Result<()> {
   140→    use std::ffi::OsStr;
   141→    use std::os::windows::ffi::OsStrExt;
   142→    use windows::Win32::Storage::FileSystem::{MoveFileExW, MOVEFILE_DELAY_UNTIL_REBOOT};
   143→
   144→    info!("Uninstalling agent");
   145→
   146→    // First remove from startup
   147→    let _ = remove_from_startup();
   148→
   149→    // Get the path to the current executable
   150→    let exe_path = std::env::current_exe()?;
   151→    let exe_path_str = exe_path.to_string_lossy();
   152→
   153→    info!("Scheduling deletion of: {}", exe_path_str);
   154→
   155→    // Convert path to wide string
   156→    let exe_wide: Vec<u16> = OsStr::new(&*exe_path_str)
   157→        .encode_wide()
   158→        .chain(std::iter::once(0))
   159→        .collect();
   160→
   161→    // Schedule the file for deletion on next reboot
   162→    // This is necessary because the executable is currently running
   163→    unsafe {
   164→        let result = MoveFileExW(
   165→            PCWSTR(exe_wide.as_ptr()),
   166→            PCWSTR::null(),
   167→            MOVEFILE_DELAY_UNTIL_REBOOT,
   168→        );
   169→
   170→        if result.is_err() {
   171→            warn!("Failed to schedule file deletion: {:?}. File may need manual removal.", result);
   172→        } else {
   173→            info!("Executable scheduled for deletion on reboot");
   174→        }
   175→    }
   176→
   177→    Ok(())
   178→}
   179→
   180→#[cfg(not(windows))]
   181→pub fn add_to_startup() -> Result<()> {
   182→    warn!("Startup persistence not implemented for this platform");
   183→    Ok(())
   184→}
   185→
   186→#[cfg(not(windows))]
   187→pub fn remove_from_startup() -> Result<()> {
   188→    Ok(())
   189→}
   190→
   191→#[cfg(not(windows))]
   192→pub fn uninstall() -> Result<()> {
   193→    warn!("Uninstall not implemented for this platform");
   194→    Ok(())
   195→}
   196→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! Startup persistence for the agent
//!
//! Handles adding/removing the agent from Windows startup.

use anyhow::Result;
use tracing::{info, warn, error};

#[cfg(windows)]
use windows::Win32::System::Registry::{
    RegOpenKeyExW, RegSetValueExW, RegDeleteValueW, RegCloseKey,
    HKEY_CURRENT_USER, KEY_WRITE, REG_SZ,
};
#[cfg(windows)]
use windows::core::PCWSTR;

const STARTUP_KEY: &str = r"Software\Microsoft\Windows\CurrentVersion\Run";
const STARTUP_VALUE_NAME: &str = "GuruConnect";

/// Add the current executable to Windows startup
#[cfg(windows)]
pub fn add_to_startup() -> Result<()> {
    use std::ffi::OsStr;
    use std::os::windows::ffi::OsStrExt;

    // Get the path to the current executable
    let exe_path = std::env::current_exe()?;
    let exe_path_str = exe_path.to_string_lossy();

    info!("Adding to startup: {}", exe_path_str);

    // Convert strings to wide strings
    let key_path: Vec<u16> = OsStr::new(STARTUP_KEY)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();
    let value_name: Vec<u16> = OsStr::new(STARTUP_VALUE_NAME)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();
    let value_data: Vec<u16> = OsStr::new(&*exe_path_str)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();

    unsafe {
        let mut hkey = windows::Win32::Foundation::HANDLE::default();

        // Open the Run key
        let result = RegOpenKeyExW(
            HKEY_CURRENT_USER,
            PCWSTR(key_path.as_ptr()),
            0,
            KEY_WRITE,
            &mut hkey as *mut _ as *mut _,
        );

        if result.is_err() {
            anyhow::bail!("Failed to open registry key: {:?}", result);
        }

        let hkey_raw = std::mem::transmute::<_, windows::Win32::System::Registry::HKEY>(hkey);

        // Set the value
        let data_bytes = std::slice::from_raw_parts(
            value_data.as_ptr() as *const u8,
            value_data.len() * 2,
        );

        let set_result = RegSetValueExW(
            hkey_raw,
            PCWSTR(value_name.as_ptr()),
            0,
            REG_SZ,
            Some(data_bytes),
        );

        let _ = RegCloseKey(hkey_raw);

        if set_result.is_err() {
            anyhow::bail!("Failed to set registry value: {:?}", set_result);
        }
    }

    info!("Successfully added to startup");
    Ok(())
}

/// Remove the agent from Windows startup
#[cfg(windows)]
pub fn remove_from_startup() -> Result<()> {
    use std::ffi::OsStr;
    use std::os::windows::ffi::OsStrExt;

    info!("Removing from startup");

    let key_path: Vec<u16> = OsStr::new(STARTUP_KEY)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();
    let value_name: Vec<u16> = OsStr::new(STARTUP_VALUE_NAME)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();

    unsafe {
        let mut hkey = windows::Win32::Foundation::HANDLE::default();

        let result = RegOpenKeyExW(
            HKEY_CURRENT_USER,
            PCWSTR(key_path.as_ptr()),
            0,
            KEY_WRITE,
            &mut hkey as *mut _ as *mut _,
        );

        if result.is_err() {
            warn!("Failed to open registry key for removal: {:?}", result);
            return Ok(()); // Not an error if key doesn't exist
        }

        let hkey_raw = std::mem::transmute::<_, windows::Win32::System::Registry::HKEY>(hkey);

        let delete_result = RegDeleteValueW(hkey_raw, PCWSTR(value_name.as_ptr()));

        let _ = RegCloseKey(hkey_raw);

        if delete_result.is_err() {
            warn!("Registry value may not exist: {:?}", delete_result);
        } else {
            info!("Successfully removed from startup");
        }
    }

    Ok(())
}

/// Full uninstall: remove from startup and delete the executable
#[cfg(windows)]
pub fn uninstall() -> Result<()> {
    use std::ffi::OsStr;
    use std::os::windows::ffi::OsStrExt;
    use windows::Win32::Storage::FileSystem::{MoveFileExW, MOVEFILE_DELAY_UNTIL_REBOOT};

    info!("Uninstalling agent");

    // First remove from startup
    let _ = remove_from_startup();

    // Get the path to the current executable
    let exe_path = std::env::current_exe()?;
    let exe_path_str = exe_path.to_string_lossy();

    info!("Scheduling deletion of: {}", exe_path_str);

    // Convert path to wide string
    let exe_wide: Vec<u16> = OsStr::new(&*exe_path_str)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();

    // Schedule the file for deletion on next reboot
    // This is necessary because the executable is currently running
    unsafe {
        let result = MoveFileExW(
            PCWSTR(exe_wide.as_ptr()),
            PCWSTR::null(),
            MOVEFILE_DELAY_UNTIL_REBOOT,
        );

        if result.is_err() {
            warn!("Failed to schedule file deletion: {:?}. File may need manual removal.", result);
        } else {
            info!("Executable scheduled for deletion on reboot");
        }
    }

    Ok(())
}

#[cfg(not(windows))]
pub fn add_to_startup() -> Result<()> {
    warn!("Startup persistence not implemented for this platform");
    Ok(())
}

#[cfg(not(windows))]
pub fn remove_from_startup() -> Result<()> {
    Ok(())
}

#[cfg(not(windows))]
pub fn uninstall() -> Result<()> {
    warn!("Uninstall not implemented for this platform");
    Ok(())
}


     1→[package]
     2→name = "guruconnect-agent"
     3→version = "0.1.0"
     4→edition = "2021"
     5→authors = ["AZ Computer Guru"]
     6→description = "GuruConnect Remote Desktop Agent"
     7→
     8→[dependencies]
     9→# Async runtime
    10→tokio = { version = "1", features = ["full", "sync", "time", "rt-multi-thread", "macros"] }
    11→
    12→# WebSocket
    13→tokio-tungstenite = { version = "0.24", features = ["native-tls"] }
    14→futures-util = "0.3"
    15→
    16→# Compression
    17→zstd = "0.13"
    18→
    19→# Protocol (protobuf)
    20→prost = "0.13"
    21→prost-types = "0.13"
    22→bytes = "1"
    23→
    24→# Serialization
    25→serde = { version = "1", features = ["derive"] }
    26→serde_json = "1"
    27→
    28→# Logging
    29→tracing = "0.1"
    30→tracing-subscriber = { version = "0.3", features = ["env-filter"] }
    31→
    32→# Error handling
    33→anyhow = "1"
    34→thiserror = "1"
    35→
    36→# Configuration
    37→toml = "0.8"
    38→
    39→# Crypto
    40→ring = "0.17"
    41→
    42→# UUID
    43→uuid = { version = "1", features = ["v4", "serde"] }
    44→
    45→# Time
    46→chrono = { version = "0.4", features = ["serde"] }
    47→
    48→# Hostname
    49→hostname = "0.4"
    50→
    51→# URL encoding
    52→urlencoding = "2"
    53→
    54→# System tray (Windows)
    55→tray-icon = "0.19"
    56→muda = "0.15"  # Menu for tray icon
    57→
    58→# Image handling for tray icon
    59→image = { version = "0.25", default-features = false, features = ["png"] }
    60→
    61→[target.'cfg(windows)'.dependencies]
    62→# Windows APIs for screen capture and input
    63→windows = { version = "0.58", features = [
    64→    "Win32_Foundation",
    65→    "Win32_Graphics_Gdi",
    66→    "Win32_Graphics_Dxgi",
    67→    "Win32_Graphics_Dxgi_Common",
    68→    "Win32_Graphics_Direct3D",
    69→    "Win32_Graphics_Direct3D11",
    70→    "Win32_UI_Input_KeyboardAndMouse",
    71→    "Win32_UI_WindowsAndMessaging",
    72→    "Win32_System_LibraryLoader",
    73→    "Win32_System_Threading",
    74→    "Win32_System_Registry",
    75→    "Win32_System_Console",
    76→    "Win32_Security",
    77→    "Win32_Storage_FileSystem",
    78→]}
    79→
    80→# Windows service support
    81→windows-service = "0.7"
    82→
    83→[build-dependencies]
    84→prost-build = "0.13"
    85→winres = "0.1"
    86→
    87→[profile.release]
    88→lto = true
    89→codegen-units = 1
    90→opt-level = "z"
    91→strip = true
    92→panic = "abort"
    93→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

[package]
name = "guruconnect-agent"
version = "0.1.0"
edition = "2021"
authors = ["AZ Computer Guru"]
description = "GuruConnect Remote Desktop Agent"

[dependencies]
# Async runtime
tokio = { version = "1", features = ["full", "sync", "time", "rt-multi-thread", "macros"] }

# WebSocket
tokio-tungstenite = { version = "0.24", features = ["native-tls"] }
futures-util = "0.3"

# Compression
zstd = "0.13"

# Protocol (protobuf)
prost = "0.13"
prost-types = "0.13"
bytes = "1"

# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

# Error handling
anyhow = "1"
thiserror = "1"

# Configuration
toml = "0.8"

# Crypto
ring = "0.17"

# UUID
uuid = { version = "1", features = ["v4", "serde"] }

# Time
chrono = { version = "0.4", features = ["serde"] }

# Hostname
hostname = "0.4"

# URL encoding
urlencoding = "2"

# System tray (Windows)
tray-icon = "0.19"
muda = "0.15"  # Menu for tray icon

# Image handling for tray icon
image = { version = "0.25", default-features = false, features = ["png"] }

[target.'cfg(windows)'.dependencies]
# Windows APIs for screen capture and input
windows = { version = "0.58", features = [
    "Win32_Foundation",
    "Win32_Graphics_Gdi",
    "Win32_Graphics_Dxgi",
    "Win32_Graphics_Dxgi_Common",
    "Win32_Graphics_Direct3D",
    "Win32_Graphics_Direct3D11",
    "Win32_UI_Input_KeyboardAndMouse",
    "Win32_UI_WindowsAndMessaging",
    "Win32_System_LibraryLoader",
    "Win32_System_Threading",
    "Win32_System_Registry",
    "Win32_System_Console",
    "Win32_Security",
    "Win32_Storage_FileSystem",
]}

# Windows service support
windows-service = "0.7"

[build-dependencies]
prost-build = "0.13"
winres = "0.1"

[profile.release]
lto = true
codegen-units = 1
opt-level = "z"
strip = true
panic = "abort"


Now let me examine the other key modules to complete my understanding:


     1→//! Input injection module
     2→//!
     3→//! Handles mouse and keyboard input simulation using Windows SendInput API.
     4→
     5→mod mouse;
     6→mod keyboard;
     7→
     8→pub use mouse::MouseController;
     9→pub use keyboard::KeyboardController;
    10→
    11→use anyhow::Result;
    12→
    13→/// Combined input controller for mouse and keyboard
    14→pub struct InputController {
    15→    mouse: MouseController,
    16→    keyboard: KeyboardController,
    17→}
    18→
    19→impl InputController {
    20→    /// Create a new input controller
    21→    pub fn new() -> Result<Self> {
    22→        Ok(Self {
    23→            mouse: MouseController::new()?,
    24→            keyboard: KeyboardController::new()?,
    25→        })
    26→    }
    27→
    28→    /// Get mouse controller
    29→    pub fn mouse(&mut self) -> &mut MouseController {
    30→        &mut self.mouse
    31→    }
    32→
    33→    /// Get keyboard controller
    34→    pub fn keyboard(&mut self) -> &mut KeyboardController {
    35→        &mut self.keyboard
    36→    }
    37→
    38→    /// Move mouse to absolute position
    39→    pub fn mouse_move(&mut self, x: i32, y: i32) -> Result<()> {
    40→        self.mouse.move_to(x, y)
    41→    }
    42→
    43→    /// Click mouse button
    44→    pub fn mouse_click(&mut self, button: MouseButton, down: bool) -> Result<()> {
    45→        if down {
    46→            self.mouse.button_down(button)
    47→        } else {
    48→            self.mouse.button_up(button)
    49→        }
    50→    }
    51→
    52→    /// Scroll mouse wheel
    53→    pub fn mouse_scroll(&mut self, delta_x: i32, delta_y: i32) -> Result<()> {
    54→        self.mouse.scroll(delta_x, delta_y)
    55→    }
    56→
    57→    /// Press or release a key
    58→    pub fn key_event(&mut self, vk_code: u16, down: bool) -> Result<()> {
    59→        if down {
    60→            self.keyboard.key_down(vk_code)
    61→        } else {
    62→            self.keyboard.key_up(vk_code)
    63→        }
    64→    }
    65→
    66→    /// Type a unicode character
    67→    pub fn type_unicode(&mut self, ch: char) -> Result<()> {
    68→        self.keyboard.type_char(ch)
    69→    }
    70→
    71→    /// Send Ctrl+Alt+Delete (requires special handling on Windows)
    72→    pub fn send_ctrl_alt_del(&mut self) -> Result<()> {
    73→        self.keyboard.send_sas()
    74→    }
    75→}
    76→
    77→/// Mouse button types
    78→#[derive(Debug, Clone, Copy, PartialEq, Eq)]
    79→pub enum MouseButton {
    80→    Left,
    81→    Right,
    82→    Middle,
    83→    X1,
    84→    X2,
    85→}
    86→
    87→impl Default for InputController {
    88→    fn default() -> Self {
    89→        Self::new().expect("Failed to create input controller")
    90→    }
    91→}
    92→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! 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")
    }
}


     1→//! Session management for the agent
     2→//!
     3→//! Handles the lifecycle of a remote session including:
     4→//! - Connection to server
     5→//! - Idle mode (heartbeat only, minimal resources)
     6→//! - Active/streaming mode (capture and send frames)
     7→//! - Input event handling
     8→
     9→#[cfg(windows)]
    10→use windows::Win32::System::Console::{AllocConsole, GetConsoleWindow};
    11→#[cfg(windows)]
    12→use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW};
    13→
    14→use crate::capture::{self, Capturer, Display};
    15→use crate::chat::{ChatController, ChatMessage as ChatMsg};
    16→use crate::config::Config;
    17→use crate::encoder::{self, Encoder};
    18→use crate::input::InputController;
    19→
    20→/// Show the debug console window (Windows only)
    21→#[cfg(windows)]
    22→fn show_debug_console() {
    23→    unsafe {
    24→        let hwnd = GetConsoleWindow();
    25→        if hwnd.0 == std::ptr::null_mut() {
    26→            let _ = AllocConsole();
    27→            tracing::info!("Debug console window opened");
    28→        } else {
    29→            let _ = ShowWindow(hwnd, SW_SHOW);
    30→            tracing::info!("Debug console window shown");
    31→        }
    32→    }
    33→}
    34→
    35→#[cfg(not(windows))]
    36→fn show_debug_console() {
    37→    // No-op on non-Windows platforms
    38→}
    39→
    40→use crate::proto::{Message, message, ChatMessage, AgentStatus, Heartbeat, HeartbeatAck};
    41→use crate::transport::WebSocketTransport;
    42→use crate::tray::{TrayController, TrayAction};
    43→use anyhow::Result;
    44→use std::time::{Duration, Instant};
    45→
    46→// Heartbeat interval (30 seconds)
    47→const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(30);
    48→// Status report interval (60 seconds)
    49→const STATUS_INTERVAL: Duration = Duration::from_secs(60);
    50→
    51→/// Session manager handles the remote control session
    52→pub struct SessionManager {
    53→    config: Config,
    54→    transport: Option<WebSocketTransport>,
    55→    state: SessionState,
    56→    // Lazy-initialized streaming resources
    57→    capturer: Option<Box<dyn Capturer>>,
    58→    encoder: Option<Box<dyn Encoder>>,
    59→    input: Option<InputController>,
    60→    // Streaming state
    61→    current_viewer_id: Option<String>,
    62→    // System info for status reports
    63→    hostname: String,
    64→    is_elevated: bool,
    65→    start_time: Instant,
    66→}
    67→
    68→#[derive(Debug, Clone, PartialEq)]
    69→enum SessionState {
    70→    Disconnected,
    71→    Connecting,
    72→    Idle,       // Connected but not streaming - minimal resource usage
    73→    Streaming,  // Actively capturing and sending frames
    74→}
    75→
    76→impl SessionManager {
    77→    /// Create a new session manager
    78→    pub fn new(config: Config, is_elevated: bool) -> Self {
    79→        let hostname = config.hostname();
    80→        Self {
    81→            config,
    82→            transport: None,
    83→            state: SessionState::Disconnected,
    84→            capturer: None,
    85→            encoder: None,
    86→            input: None,
    87→            current_viewer_id: None,
    88→            hostname,
    89→            is_elevated,
    90→            start_time: Instant::now(),
    91→        }
    92→    }
    93→
    94→    /// Connect to the server
    95→    pub async fn connect(&mut self) -> Result<()> {
    96→        self.state = SessionState::Connecting;
    97→
    98→        let transport = WebSocketTransport::connect(
    99→            &self.config.server_url,
   100→            &self.config.agent_id,

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! Session management for the agent
//!
//! Handles the lifecycle of a remote session including:
//! - Connection to server
//! - Idle mode (heartbeat only, minimal resources)
//! - Active/streaming mode (capture and send frames)
//! - Input event handling

#[cfg(windows)]
use windows::Win32::System::Console::{AllocConsole, GetConsoleWindow};
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW};

use crate::capture::{self, Capturer, Display};
use crate::chat::{ChatController, ChatMessage as ChatMsg};
use crate::config::Config;
use crate::encoder::{self, Encoder};
use crate::input::InputController;

/// Show the debug console window (Windows only)
#[cfg(windows)]
fn show_debug_console() {
    unsafe {
        let hwnd = GetConsoleWindow();
        if hwnd.0 == std::ptr::null_mut() {
            let _ = AllocConsole();
            tracing::info!("Debug console window opened");
        } else {
            let _ = ShowWindow(hwnd, SW_SHOW);
            tracing::info!("Debug console window shown");
        }
    }
}

#[cfg(not(windows))]
fn show_debug_console() {
    // No-op on non-Windows platforms
}

use crate::proto::{Message, message, ChatMessage, AgentStatus, Heartbeat, HeartbeatAck};
use crate::transport::WebSocketTransport;
use crate::tray::{TrayController, TrayAction};
use anyhow::Result;
use std::time::{Duration, Instant};

// Heartbeat interval (30 seconds)
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(30);
// Status report interval (60 seconds)
const STATUS_INTERVAL: Duration = Duration::from_secs(60);

/// Session manager handles the remote control session
pub struct SessionManager {
    config: Config,
    transport: Option<WebSocketTransport>,
    state: SessionState,
    // Lazy-initialized streaming resources
    capturer: Option<Box<dyn Capturer>>,
    encoder: Option<Box<dyn Encoder>>,
    input: Option<InputController>,
    // Streaming state
    current_viewer_id: Option<String>,
    // System info for status reports
    hostname: String,
    is_elevated: bool,
    start_time: Instant,
}

#[derive(Debug, Clone, PartialEq)]
enum SessionState {
    Disconnected,
    Connecting,
    Idle,       // Connected but not streaming - minimal resource usage
    Streaming,  // Actively capturing and sending frames
}

impl SessionManager {
    /// Create a new session manager
    pub fn new(config: Config, is_elevated: bool) -> Self {
        let hostname = config.hostname();
        Self {
            config,
            transport: None,
            state: SessionState::Disconnected,
            capturer: None,
            encoder: None,
            input: None,
            current_viewer_id: None,
            hostname,
            is_elevated,
            start_time: Instant::now(),
        }
    }

    /// Connect to the server
    pub async fn connect(&mut self) -> Result<()> {
        self.state = SessionState::Connecting;

        let transport = WebSocketTransport::connect(
            &self.config.server_url,
            &self.config.agent_id,


     1→//! WebSocket transport for agent-server communication
     2→
     3→mod websocket;
     4→
     5→pub use websocket::WebSocketTransport;
     6→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! WebSocket transport for agent-server communication

mod websocket;

pub use websocket::WebSocketTransport;


Let me check the transport/websocket.rs and proto file:


     1→//! WebSocket client transport
     2→//!
     3→//! Handles WebSocket connection to the GuruConnect server with:
     4→//! - TLS encryption
     5→//! - Automatic reconnection
     6→//! - Protobuf message serialization
     7→
     8→use crate::proto::Message;
     9→use anyhow::{Context, Result};
    10→use bytes::Bytes;
    11→use futures_util::{SinkExt, StreamExt};
    12→use prost::Message as ProstMessage;
    13→use std::collections::VecDeque;
    14→use std::sync::Arc;
    15→use tokio::net::TcpStream;
    16→use tokio::sync::Mutex;
    17→use tokio_tungstenite::{
    18→    connect_async, tungstenite::protocol::Message as WsMessage, MaybeTlsStream, WebSocketStream,
    19→};
    20→
    21→type WsStream = WebSocketStream<MaybeTlsStream<TcpStream>>;
    22→
    23→/// WebSocket transport for server communication
    24→pub struct WebSocketTransport {
    25→    stream: Arc<Mutex<WsStream>>,
    26→    incoming: VecDeque<Message>,
    27→    connected: bool,
    28→}
    29→
    30→impl WebSocketTransport {
    31→    /// Connect to the server
    32→    pub async fn connect(
    33→        url: &str,
    34→        agent_id: &str,
    35→        api_key: &str,
    36→        hostname: Option<&str>,
    37→        support_code: Option<&str>,
    38→    ) -> Result<Self> {
    39→        // Build query parameters
    40→        let mut params = format!("agent_id={}&api_key={}", agent_id, api_key);
    41→
    42→        if let Some(hostname) = hostname {
    43→            params.push_str(&format!("&hostname={}", urlencoding::encode(hostname)));
    44→        }
    45→
    46→        if let Some(code) = support_code {
    47→            params.push_str(&format!("&support_code={}", code));
    48→        }
    49→
    50→        // Append parameters to URL
    51→        let url_with_params = if url.contains('?') {
    52→            format!("{}&{}", url, params)
    53→        } else {
    54→            format!("{}?{}", url, params)
    55→        };
    56→
    57→        tracing::info!("Connecting to {} as agent {}", url, agent_id);
    58→        if let Some(code) = support_code {
    59→            tracing::info!("Using support code: {}", code);
    60→        }
    61→
    62→        let (ws_stream, response) = connect_async(&url_with_params)
    63→            .await
    64→            .context("Failed to connect to WebSocket server")?;
    65→
    66→        tracing::info!("Connected, status: {}", response.status());
    67→
    68→        Ok(Self {
    69→            stream: Arc::new(Mutex::new(ws_stream)),
    70→            incoming: VecDeque::new(),
    71→            connected: true,
    72→        })
    73→    }
    74→
    75→    /// Send a protobuf message
    76→    pub async fn send(&mut self, msg: Message) -> Result<()> {
    77→        let mut stream = self.stream.lock().await;
    78→
    79→        // Serialize to protobuf binary
    80→        let mut buf = Vec::with_capacity(msg.encoded_len());
    81→        msg.encode(&mut buf)?;
    82→
    83→        // Send as binary WebSocket message
    84→        stream
    85→            .send(WsMessage::Binary(buf.into()))
    86→            .await
    87→            .context("Failed to send message")?;
    88→
    89→        Ok(())
    90→    }
    91→
    92→    /// Try to receive a message (non-blocking)
    93→    pub fn try_recv(&mut self) -> Result<Option<Message>> {
    94→        // Return buffered message if available
    95→        if let Some(msg) = self.incoming.pop_front() {
    96→            return Ok(Some(msg));
    97→        }
    98→
    99→        // Try to receive more messages
   100→        let stream = self.stream.clone();

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! WebSocket client transport
//!
//! Handles WebSocket connection to the GuruConnect server with:
//! - TLS encryption
//! - Automatic reconnection
//! - Protobuf message serialization

use crate::proto::Message;
use anyhow::{Context, Result};
use bytes::Bytes;
use futures_util::{SinkExt, StreamExt};
use prost::Message as ProstMessage;
use std::collections::VecDeque;
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use tokio_tungstenite::{
    connect_async, tungstenite::protocol::Message as WsMessage, MaybeTlsStream, WebSocketStream,
};

type WsStream = WebSocketStream<MaybeTlsStream<TcpStream>>;

/// WebSocket transport for server communication
pub struct WebSocketTransport {
    stream: Arc<Mutex<WsStream>>,
    incoming: VecDeque<Message>,
    connected: bool,
}

impl WebSocketTransport {
    /// Connect to the server
    pub async fn connect(
        url: &str,
        agent_id: &str,
        api_key: &str,
        hostname: Option<&str>,
        support_code: Option<&str>,
    ) -> Result<Self> {
        // Build query parameters
        let mut params = format!("agent_id={}&api_key={}", agent_id, api_key);

        if let Some(hostname) = hostname {
            params.push_str(&format!("&hostname={}", urlencoding::encode(hostname)));
        }

        if let Some(code) = support_code {
            params.push_str(&format!("&support_code={}", code));
        }

        // Append parameters to URL
        let url_with_params = if url.contains('?') {
            format!("{}&{}", url, params)
        } else {
            format!("{}?{}", url, params)
        };

        tracing::info!("Connecting to {} as agent {}", url, agent_id);
        if let Some(code) = support_code {
            tracing::info!("Using support code: {}", code);
        }

        let (ws_stream, response) = connect_async(&url_with_params)
            .await
            .context("Failed to connect to WebSocket server")?;

        tracing::info!("Connected, status: {}", response.status());

        Ok(Self {
            stream: Arc::new(Mutex::new(ws_stream)),
            incoming: VecDeque::new(),
            connected: true,
        })
    }

    /// Send a protobuf message
    pub async fn send(&mut self, msg: Message) -> Result<()> {
        let mut stream = self.stream.lock().await;

        // Serialize to protobuf binary
        let mut buf = Vec::with_capacity(msg.encoded_len());
        msg.encode(&mut buf)?;

        // Send as binary WebSocket message
        stream
            .send(WsMessage::Binary(buf.into()))
            .await
            .context("Failed to send message")?;

        Ok(())
    }

    /// Try to receive a message (non-blocking)
    pub fn try_recv(&mut self) -> Result<Option<Message>> {
        // Return buffered message if available
        if let Some(msg) = self.incoming.pop_front() {
            return Ok(Some(msg));
        }

        // Try to receive more messages
        let stream = self.stream.clone();


     1→syntax = "proto3";
     2→package guruconnect;
     3→
     4→// ============================================================================
     5→// Session Management
     6→// ============================================================================
     7→
     8→message SessionRequest {
     9→    string agent_id = 1;
    10→    string session_token = 2;
    11→    SessionType session_type = 3;
    12→    string client_version = 4;
    13→}
    14→
    15→message SessionResponse {
    16→    bool success = 1;
    17→    string session_id = 2;
    18→    string error = 3;
    19→    DisplayInfo display_info = 4;
    20→}
    21→
    22→enum SessionType {
    23→    SCREEN_CONTROL = 0;
    24→    VIEW_ONLY = 1;
    25→    BACKSTAGE = 2;
    26→    FILE_TRANSFER = 3;
    27→}
    28→
    29→// ============================================================================
    30→// Display Information
    31→// ============================================================================
    32→
    33→message DisplayInfo {
    34→    repeated Display displays = 1;
    35→    int32 primary_display = 2;
    36→}
    37→
    38→message Display {
    39→    int32 id = 1;
    40→    string name = 2;
    41→    int32 x = 3;
    42→    int32 y = 4;
    43→    int32 width = 5;
    44→    int32 height = 6;
    45→    bool is_primary = 7;
    46→}
    47→
    48→message SwitchDisplay {
    49→    int32 display_id = 1;
    50→}
    51→
    52→// ============================================================================
    53→// Video Frames
    54→// ============================================================================
    55→
    56→message VideoFrame {
    57→    int64 timestamp = 1;
    58→    int32 display_id = 2;
    59→    int32 sequence = 3;
    60→
    61→    oneof encoding {
    62→        RawFrame raw = 10;
    63→        EncodedFrame vp9 = 11;
    64→        EncodedFrame h264 = 12;
    65→        EncodedFrame h265 = 13;
    66→    }
    67→}
    68→
    69→message RawFrame {
    70→    int32 width = 1;
    71→    int32 height = 2;
    72→    bytes data = 3;             // Zstd compressed BGRA
    73→    bool compressed = 4;
    74→    repeated DirtyRect dirty_rects = 5;
    75→    bool is_keyframe = 6;       // Full frame vs incremental
    76→}
    77→
    78→message DirtyRect {
    79→    int32 x = 1;
    80→    int32 y = 2;
    81→    int32 width = 3;
    82→    int32 height = 4;
    83→}
    84→
    85→message EncodedFrame {
    86→    bytes data = 1;
    87→    bool keyframe = 2;
    88→    int64 pts = 3;
    89→    int64 dts = 4;
    90→}
    91→
    92→message VideoAck {
    93→    int32 sequence = 1;
    94→    int64 timestamp = 2;
    95→}
    96→
    97→// ============================================================================
    98→// Cursor
    99→// ============================================================================
   100→
   101→message CursorShape {
   102→    uint64 id = 1;
   103→    int32 hotspot_x = 2;
   104→    int32 hotspot_y = 3;
   105→    int32 width = 4;
   106→    int32 height = 5;
   107→    bytes data = 6;             // BGRA bitmap
   108→}
   109→
   110→message CursorPosition {
   111→    int32 x = 1;
   112→    int32 y = 2;
   113→    bool visible = 3;
   114→}
   115→
   116→// ============================================================================
   117→// Input Events
   118→// ============================================================================
   119→
   120→message MouseEvent {

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

syntax = "proto3";
package guruconnect;

// ============================================================================
// Session Management
// ============================================================================

message SessionRequest {
    string agent_id = 1;
    string session_token = 2;
    SessionType session_type = 3;
    string client_version = 4;
}

message SessionResponse {
    bool success = 1;
    string session_id = 2;
    string error = 3;
    DisplayInfo display_info = 4;
}

enum SessionType {
    SCREEN_CONTROL = 0;
    VIEW_ONLY = 1;
    BACKSTAGE = 2;
    FILE_TRANSFER = 3;
}

// ============================================================================
// Display Information
// ============================================================================

message DisplayInfo {
    repeated Display displays = 1;
    int32 primary_display = 2;
}

message Display {
    int32 id = 1;
    string name = 2;
    int32 x = 3;
    int32 y = 4;
    int32 width = 5;
    int32 height = 6;
    bool is_primary = 7;
}

message SwitchDisplay {
    int32 display_id = 1;
}

// ============================================================================
// Video Frames
// ============================================================================

message VideoFrame {
    int64 timestamp = 1;
    int32 display_id = 2;
    int32 sequence = 3;

    oneof encoding {
        RawFrame raw = 10;
        EncodedFrame vp9 = 11;
        EncodedFrame h264 = 12;
        EncodedFrame h265 = 13;
    }
}

message RawFrame {
    int32 width = 1;
    int32 height = 2;
    bytes data = 3;             // Zstd compressed BGRA
    bool compressed = 4;
    repeated DirtyRect dirty_rects = 5;
    bool is_keyframe = 6;       // Full frame vs incremental
}

message DirtyRect {
    int32 x = 1;
    int32 y = 2;
    int32 width = 3;
    int32 height = 4;
}

message EncodedFrame {
    bytes data = 1;
    bool keyframe = 2;
    int64 pts = 3;
    int64 dts = 4;
}

message VideoAck {
    int32 sequence = 1;
    int64 timestamp = 2;
}

// ============================================================================
// Cursor
// ============================================================================

message CursorShape {
    uint64 id = 1;
    int32 hotspot_x = 2;
    int32 hotspot_y = 3;
    int32 width = 4;
    int32 height = 5;
    bytes data = 6;             // BGRA bitmap
}

message CursorPosition {
    int32 x = 1;
    int32 y = 2;
    bool visible = 3;
}

// ============================================================================
// Input Events
// ============================================================================

message MouseEvent {


     1→use std::io::Result;
     2→
     3→fn main() -> Result<()> {
     4→    // Compile protobuf definitions
     5→    prost_build::compile_protos(&["../proto/guruconnect.proto"], &["../proto/"])?;
     6→
     7→    // Rerun if proto changes
     8→    println!("cargo:rerun-if-changed=../proto/guruconnect.proto");
     9→
    10→    // On Windows, embed the manifest for UAC elevation
    11→    #[cfg(target_os = "windows")]
    12→    {
    13→        println!("cargo:rerun-if-changed=guruconnect.manifest");
    14→
    15→        let mut res = winres::WindowsResource::new();
    16→        res.set_manifest_file("guruconnect.manifest");
    17→        res.set("ProductName", "GuruConnect Agent");
    18→        res.set("FileDescription", "GuruConnect Remote Desktop Agent");
    19→        res.set("LegalCopyright", "Copyright (c) AZ Computer Guru");
    20→        res.set_icon("guruconnect.ico"); // Optional: add icon if available
    21→
    22→        // Only compile if the manifest exists
    23→        if std::path::Path::new("guruconnect.manifest").exists() {
    24→            if let Err(e) = res.compile() {
    25→                // Don't fail the build if resource compilation fails
    26→                eprintln!("Warning: Failed to compile Windows resources: {}", e);
    27→            }
    28→        }
    29→    }
    30→
    31→    Ok(())
    32→}
    33→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

use std::io::Result;

fn main() -> Result<()> {
    // Compile protobuf definitions
    prost_build::compile_protos(&["../proto/guruconnect.proto"], &["../proto/"])?;

    // Rerun if proto changes
    println!("cargo:rerun-if-changed=../proto/guruconnect.proto");

    // On Windows, embed the manifest for UAC elevation
    #[cfg(target_os = "windows")]
    {
        println!("cargo:rerun-if-changed=guruconnect.manifest");

        let mut res = winres::WindowsResource::new();
        res.set_manifest_file("guruconnect.manifest");
        res.set("ProductName", "GuruConnect Agent");
        res.set("FileDescription", "GuruConnect Remote Desktop Agent");
        res.set("LegalCopyright", "Copyright (c) AZ Computer Guru");
        res.set_icon("guruconnect.ico"); // Optional: add icon if available

        // Only compile if the manifest exists
        if std::path::Path::new("guruconnect.manifest").exists() {
            if let Err(e) = res.compile() {
                // Don't fail the build if resource compilation fails
                eprintln!("Warning: Failed to compile Windows resources: {}", e);
            }
        }
    }

    Ok(())
}


Perfect! Now let me check if there's any existing service-related code:


find "C:\Users\MikeSwanson\Claude\guru-connect" -type f -name "*.rs" | xargs grep -l "windows_service\|WinSvc\|SERVICE_" 2>/dev/null | head -5

Search for Windows service code


No files found


Let me check the config and tray modules to complete my understanding: