Explore how video frames are encoded and sent in GuruConnect. I need to understand:

1. The agent's encoder module - how frames are encoded
2. What format the RawFrame uses (BGRA + zstd compression)
3. The exact protobuf structure for VideoFrame and RawFrame
4. How dirty rectangles work

Look in:
- agent/src/encoder/mod.rs and related files
- proto/guruconnect.proto

I need to understand the exact binary format so I can decode it in JavaScript.


I'll explore the GuruConnect video encoding system for you. Let me search through the agent encoder module and protobuf definitions.


C:\Users\MikeSwanson\Claude\guru-connect\agent\src\encoder\mod.rs
C:\Users\MikeSwanson\Claude\guru-connect\agent\src\encoder\raw.rs


Good! I found the encoder files. Now let me read the protobuf file and the encoder modules to understand the exact format.


     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 {
   121→    int32 x = 1;
   122→    int32 y = 2;
   123→    MouseButtons buttons = 3;
   124→    int32 wheel_delta_x = 4;
   125→    int32 wheel_delta_y = 5;
   126→    MouseEventType event_type = 6;
   127→}
   128→
   129→enum MouseEventType {
   130→    MOUSE_MOVE = 0;
   131→    MOUSE_DOWN = 1;
   132→    MOUSE_UP = 2;
   133→    MOUSE_WHEEL = 3;
   134→}
   135→
   136→message MouseButtons {
   137→    bool left = 1;
   138→    bool right = 2;
   139→    bool middle = 3;
   140→    bool x1 = 4;
   141→    bool x2 = 5;
   142→}
   143→
   144→message KeyEvent {
   145→    bool down = 1;              // true = key down, false = key up
   146→    KeyEventType key_type = 2;
   147→    uint32 vk_code = 3;         // Virtual key code (Windows VK_*)
   148→    uint32 scan_code = 4;       // Hardware scan code
   149→    string unicode = 5;         // Unicode character (for text input)
   150→    Modifiers modifiers = 6;
   151→}
   152→
   153→enum KeyEventType {
   154→    KEY_VK = 0;                 // Virtual key code
   155→    KEY_SCAN = 1;               // Scan code
   156→    KEY_UNICODE = 2;            // Unicode character
   157→}
   158→
   159→message Modifiers {
   160→    bool ctrl = 1;
   161→    bool alt = 2;
   162→    bool shift = 3;
   163→    bool meta = 4;              // Windows key
   164→    bool caps_lock = 5;
   165→    bool num_lock = 6;
   166→}
   167→
   168→message SpecialKeyEvent {
   169→    SpecialKey key = 1;
   170→}
   171→
   172→enum SpecialKey {
   173→    CTRL_ALT_DEL = 0;
   174→    LOCK_SCREEN = 1;
   175→    PRINT_SCREEN = 2;
   176→}
   177→
   178→// ============================================================================
   179→// Clipboard
   180→// ============================================================================
   181→
   182→message ClipboardData {
   183→    ClipboardFormat format = 1;
   184→    bytes data = 2;
   185→    string mime_type = 3;
   186→}
   187→
   188→enum ClipboardFormat {
   189→    CLIPBOARD_TEXT = 0;
   190→    CLIPBOARD_HTML = 1;
   191→    CLIPBOARD_RTF = 2;
   192→    CLIPBOARD_IMAGE = 3;
   193→    CLIPBOARD_FILES = 4;
   194→}
   195→
   196→message ClipboardRequest {
   197→    // Request current clipboard content
   198→}
   199→
   200→// ============================================================================
   201→// Quality Control
   202→// ============================================================================
   203→
   204→message QualitySettings {
   205→    QualityPreset preset = 1;
   206→    int32 custom_fps = 2;       // 1-60
   207→    int32 custom_bitrate = 3;   // kbps
   208→    CodecPreference codec = 4;
   209→}
   210→
   211→enum QualityPreset {
   212→    QUALITY_AUTO = 0;
   213→    QUALITY_LOW = 1;            // Low bandwidth
   214→    QUALITY_BALANCED = 2;
   215→    QUALITY_HIGH = 3;           // Best quality
   216→}
   217→
   218→enum CodecPreference {
   219→    CODEC_AUTO = 0;
   220→    CODEC_RAW = 1;              // Raw + Zstd (LAN)
   221→    CODEC_VP9 = 2;
   222→    CODEC_H264 = 3;
   223→    CODEC_H265 = 4;
   224→}
   225→
   226→message LatencyReport {
   227→    int64 rtt_ms = 1;
   228→    int32 fps = 2;
   229→    int32 bitrate_kbps = 3;
   230→}
   231→
   232→// ============================================================================
   233→// Chat Messages
   234→// ============================================================================
   235→
   236→message ChatMessage {
   237→    string id = 1;              // Unique message ID
   238→    string sender = 2;          // "technician" or "client"
   239→    string content = 3;         // Message text
   240→    int64 timestamp = 4;        // Unix timestamp
   241→}
   242→
   243→// ============================================================================
   244→// Control Messages
   245→// ============================================================================
   246→
   247→message Heartbeat {
   248→    int64 timestamp = 1;
   249→}
   250→
   251→message HeartbeatAck {
   252→    int64 client_timestamp = 1;
   253→    int64 server_timestamp = 2;
   254→}
   255→
   256→message Disconnect {
   257→    string reason = 1;
   258→}
   259→
   260→// Server commands agent to start streaming video
   261→message StartStream {
   262→    string viewer_id = 1;           // ID of viewer requesting stream
   263→    int32 display_id = 2;           // Which display to stream (0 = primary)
   264→}
   265→
   266→// Server commands agent to stop streaming
   267→message StopStream {
   268→    string viewer_id = 1;           // Which viewer disconnected
   269→}
   270→
   271→// Agent reports its status periodically when idle
   272→message AgentStatus {
   273→    string hostname = 1;
   274→    string os_version = 2;
   275→    bool is_elevated = 3;
   276→    int64 uptime_secs = 4;
   277→    int32 display_count = 5;
   278→    bool is_streaming = 6;
   279→}
   280→
   281→// ============================================================================
   282→// Top-Level Message Wrapper
   283→// ============================================================================
   284→
   285→message Message {
   286→    oneof payload {
   287→        // Session
   288→        SessionRequest session_request = 1;
   289→        SessionResponse session_response = 2;
   290→
   291→        // Video
   292→        VideoFrame video_frame = 10;
   293→        VideoAck video_ack = 11;
   294→        SwitchDisplay switch_display = 12;
   295→
   296→        // Cursor
   297→        CursorShape cursor_shape = 15;
   298→        CursorPosition cursor_position = 16;
   299→
   300→        // Input
   301→        MouseEvent mouse_event = 20;
   302→        KeyEvent key_event = 21;
   303→        SpecialKeyEvent special_key = 22;
   304→
   305→        // Clipboard
   306→        ClipboardData clipboard_data = 30;
   307→        ClipboardRequest clipboard_request = 31;
   308→
   309→        // Quality
   310→        QualitySettings quality_settings = 40;
   311→        LatencyReport latency_report = 41;
   312→
   313→        // Control
   314→        Heartbeat heartbeat = 50;
   315→        HeartbeatAck heartbeat_ack = 51;
   316→        Disconnect disconnect = 52;
   317→        StartStream start_stream = 53;
   318→        StopStream stop_stream = 54;
   319→        AgentStatus agent_status = 55;
   320→
   321→        // Chat
   322→        ChatMessage chat_message = 60;
   323→    }
   324→}
   325→

<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 {
    int32 x = 1;
    int32 y = 2;
    MouseButtons buttons = 3;
    int32 wheel_delta_x = 4;
    int32 wheel_delta_y = 5;
    MouseEventType event_type = 6;
}

enum MouseEventType {
    MOUSE_MOVE = 0;
    MOUSE_DOWN = 1;
    MOUSE_UP = 2;
    MOUSE_WHEEL = 3;
}

message MouseButtons {
    bool left = 1;
    bool right = 2;
    bool middle = 3;
    bool x1 = 4;
    bool x2 = 5;
}

message KeyEvent {
    bool down = 1;              // true = key down, false = key up
    KeyEventType key_type = 2;
    uint32 vk_code = 3;         // Virtual key code (Windows VK_*)
    uint32 scan_code = 4;       // Hardware scan code
    string unicode = 5;         // Unicode character (for text input)
    Modifiers modifiers = 6;
}

enum KeyEventType {
    KEY_VK = 0;                 // Virtual key code
    KEY_SCAN = 1;               // Scan code
    KEY_UNICODE = 2;            // Unicode character
}

message Modifiers {
    bool ctrl = 1;
    bool alt = 2;
    bool shift = 3;
    bool meta = 4;              // Windows key
    bool caps_lock = 5;
    bool num_lock = 6;
}

message SpecialKeyEvent {
    SpecialKey key = 1;
}

enum SpecialKey {
    CTRL_ALT_DEL = 0;
    LOCK_SCREEN = 1;
    PRINT_SCREEN = 2;
}

// ============================================================================
// Clipboard
// ============================================================================

message ClipboardData {
    ClipboardFormat format = 1;
    bytes data = 2;
    string mime_type = 3;
}

enum ClipboardFormat {
    CLIPBOARD_TEXT = 0;
    CLIPBOARD_HTML = 1;
    CLIPBOARD_RTF = 2;
    CLIPBOARD_IMAGE = 3;
    CLIPBOARD_FILES = 4;
}

message ClipboardRequest {
    // Request current clipboard content
}

// ============================================================================
// Quality Control
// ============================================================================

message QualitySettings {
    QualityPreset preset = 1;
    int32 custom_fps = 2;       // 1-60
    int32 custom_bitrate = 3;   // kbps
    CodecPreference codec = 4;
}

enum QualityPreset {
    QUALITY_AUTO = 0;
    QUALITY_LOW = 1;            // Low bandwidth
    QUALITY_BALANCED = 2;
    QUALITY_HIGH = 3;           // Best quality
}

enum CodecPreference {
    CODEC_AUTO = 0;
    CODEC_RAW = 1;              // Raw + Zstd (LAN)
    CODEC_VP9 = 2;
    CODEC_H264 = 3;
    CODEC_H265 = 4;
}

message LatencyReport {
    int64 rtt_ms = 1;
    int32 fps = 2;
    int32 bitrate_kbps = 3;
}

// ============================================================================
// Chat Messages
// ============================================================================

message ChatMessage {
    string id = 1;              // Unique message ID
    string sender = 2;          // "technician" or "client"
    string content = 3;         // Message text
    int64 timestamp = 4;        // Unix timestamp
}

// ============================================================================
// Control Messages
// ============================================================================

message Heartbeat {
    int64 timestamp = 1;
}

message HeartbeatAck {
    int64 client_timestamp = 1;
    int64 server_timestamp = 2;
}

message Disconnect {
    string reason = 1;
}

// Server commands agent to start streaming video
message StartStream {
    string viewer_id = 1;           // ID of viewer requesting stream
    int32 display_id = 2;           // Which display to stream (0 = primary)
}

// Server commands agent to stop streaming
message StopStream {
    string viewer_id = 1;           // Which viewer disconnected
}

// Agent reports its status periodically when idle
message AgentStatus {
    string hostname = 1;
    string os_version = 2;
    bool is_elevated = 3;
    int64 uptime_secs = 4;
    int32 display_count = 5;
    bool is_streaming = 6;
}

// ============================================================================
// Top-Level Message Wrapper
// ============================================================================

message Message {
    oneof payload {
        // Session
        SessionRequest session_request = 1;
        SessionResponse session_response = 2;

        // Video
        VideoFrame video_frame = 10;
        VideoAck video_ack = 11;
        SwitchDisplay switch_display = 12;

        // Cursor
        CursorShape cursor_shape = 15;
        CursorPosition cursor_position = 16;

        // Input
        MouseEvent mouse_event = 20;
        KeyEvent key_event = 21;
        SpecialKeyEvent special_key = 22;

        // Clipboard
        ClipboardData clipboard_data = 30;
        ClipboardRequest clipboard_request = 31;

        // Quality
        QualitySettings quality_settings = 40;
        LatencyReport latency_report = 41;

        // Control
        Heartbeat heartbeat = 50;
        HeartbeatAck heartbeat_ack = 51;
        Disconnect disconnect = 52;
        StartStream start_stream = 53;
        StopStream stop_stream = 54;
        AgentStatus agent_status = 55;

        // Chat
        ChatMessage chat_message = 60;
    }
}


     1→//! Frame encoding module
     2→//!
     3→//! Encodes captured frames for transmission. Supports:
     4→//! - Raw BGRA + Zstd compression (lowest latency, LAN mode)
     5→//! - VP9 software encoding (universal fallback)
     6→//! - H264 hardware encoding (when GPU available)
     7→
     8→mod raw;
     9→
    10→pub use raw::RawEncoder;
    11→
    12→use crate::capture::CapturedFrame;
    13→use crate::proto::{VideoFrame, RawFrame, DirtyRect as ProtoDirtyRect};
    14→use anyhow::Result;
    15→
    16→/// Encoded frame ready for transmission
    17→#[derive(Debug)]
    18→pub struct EncodedFrame {
    19→    /// Protobuf video frame message
    20→    pub frame: VideoFrame,
    21→
    22→    /// Size in bytes after encoding
    23→    pub size: usize,
    24→
    25→    /// Whether this is a keyframe (full frame)
    26→    pub is_keyframe: bool,
    27→}
    28→
    29→/// Frame encoder trait
    30→pub trait Encoder: Send {
    31→    /// Encode a captured frame
    32→    fn encode(&mut self, frame: &CapturedFrame) -> Result<EncodedFrame>;
    33→
    34→    /// Request a keyframe on next encode
    35→    fn request_keyframe(&mut self);
    36→
    37→    /// Get encoder name/type
    38→    fn name(&self) -> &str;
    39→}
    40→
    41→/// Create an encoder based on configuration
    42→pub fn create_encoder(codec: &str, quality: u32) -> Result<Box<dyn Encoder>> {
    43→    match codec.to_lowercase().as_str() {
    44→        "raw" | "zstd" => Ok(Box::new(RawEncoder::new(quality)?)),
    45→        // "vp9" => Ok(Box::new(Vp9Encoder::new(quality)?)),
    46→        // "h264" => Ok(Box::new(H264Encoder::new(quality)?)),
    47→        "auto" | _ => {
    48→            // Default to raw for now (best for LAN)
    49→            Ok(Box::new(RawEncoder::new(quality)?))
    50→        }
    51→    }
    52→}
    53→

<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>

//! Frame encoding module
//!
//! Encodes captured frames for transmission. Supports:
//! - Raw BGRA + Zstd compression (lowest latency, LAN mode)
//! - VP9 software encoding (universal fallback)
//! - H264 hardware encoding (when GPU available)

mod raw;

pub use raw::RawEncoder;

use crate::capture::CapturedFrame;
use crate::proto::{VideoFrame, RawFrame, DirtyRect as ProtoDirtyRect};
use anyhow::Result;

/// Encoded frame ready for transmission
#[derive(Debug)]
pub struct EncodedFrame {
    /// Protobuf video frame message
    pub frame: VideoFrame,

    /// Size in bytes after encoding
    pub size: usize,

    /// Whether this is a keyframe (full frame)
    pub is_keyframe: bool,
}

/// Frame encoder trait
pub trait Encoder: Send {
    /// Encode a captured frame
    fn encode(&mut self, frame: &CapturedFrame) -> Result<EncodedFrame>;

    /// Request a keyframe on next encode
    fn request_keyframe(&mut self);

    /// Get encoder name/type
    fn name(&self) -> &str;
}

/// Create an encoder based on configuration
pub fn create_encoder(codec: &str, quality: u32) -> Result<Box<dyn Encoder>> {
    match codec.to_lowercase().as_str() {
        "raw" | "zstd" => Ok(Box::new(RawEncoder::new(quality)?)),
        // "vp9" => Ok(Box::new(Vp9Encoder::new(quality)?)),
        // "h264" => Ok(Box::new(H264Encoder::new(quality)?)),
        "auto" | _ => {
            // Default to raw for now (best for LAN)
            Ok(Box::new(RawEncoder::new(quality)?))
        }
    }
}


     1→//! Raw frame encoder with Zstd compression
     2→//!
     3→//! Best for LAN connections where bandwidth is plentiful and latency is critical.
     4→//! Compresses BGRA pixel data using Zstd for fast compression/decompression.
     5→
     6→use super::{EncodedFrame, Encoder};
     7→use crate::capture::{CapturedFrame, DirtyRect};
     8→use crate::proto::{video_frame, DirtyRect as ProtoDirtyRect, RawFrame, VideoFrame};
     9→use anyhow::Result;
    10→
    11→/// Raw frame encoder with Zstd compression
    12→pub struct RawEncoder {
    13→    /// Compression level (1-22, default 3 for speed)
    14→    compression_level: i32,
    15→
    16→    /// Previous frame for delta detection
    17→    previous_frame: Option<Vec<u8>>,
    18→
    19→    /// Force keyframe on next encode
    20→    force_keyframe: bool,
    21→
    22→    /// Frame counter
    23→    sequence: u32,
    24→}
    25→
    26→impl RawEncoder {
    27→    /// Create a new raw encoder
    28→    ///
    29→    /// Quality 1-100 maps to Zstd compression level:
    30→    /// - Low quality (1-33): Level 1-3 (fastest)
    31→    /// - Medium quality (34-66): Level 4-9
    32→    /// - High quality (67-100): Level 10-15 (best compression)
    33→    pub fn new(quality: u32) -> Result<Self> {
    34→        let compression_level = Self::quality_to_level(quality);
    35→
    36→        Ok(Self {
    37→            compression_level,
    38→            previous_frame: None,
    39→            force_keyframe: true, // Start with keyframe
    40→            sequence: 0,
    41→        })
    42→    }
    43→
    44→    /// Convert quality (1-100) to Zstd compression level
    45→    fn quality_to_level(quality: u32) -> i32 {
    46→        // Lower quality = faster compression (level 1-3)
    47→        // Higher quality = better compression (level 10-15)
    48→        // We optimize for speed, so cap at 6
    49→        match quality {
    50→            0..=33 => 1,
    51→            34..=50 => 2,
    52→            51..=66 => 3,
    53→            67..=80 => 4,
    54→            81..=90 => 5,
    55→            _ => 6,
    56→        }
    57→    }
    58→
    59→    /// Compress data using Zstd
    60→    fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
    61→        let compressed = zstd::encode_all(data, self.compression_level)?;
    62→        Ok(compressed)
    63→    }
    64→
    65→    /// Detect dirty rectangles by comparing with previous frame
    66→    fn detect_dirty_rects(
    67→        &self,
    68→        current: &[u8],
    69→        previous: &[u8],
    70→        width: u32,
    71→        height: u32,
    72→    ) -> Vec<DirtyRect> {
    73→        // Simple block-based dirty detection
    74→        // Divide screen into 64x64 blocks and check which changed
    75→        const BLOCK_SIZE: u32 = 64;
    76→
    77→        let mut dirty_rects = Vec::new();
    78→        let stride = (width * 4) as usize;
    79→
    80→        let blocks_x = (width + BLOCK_SIZE - 1) / BLOCK_SIZE;
    81→        let blocks_y = (height + BLOCK_SIZE - 1) / BLOCK_SIZE;
    82→
    83→        for by in 0..blocks_y {
    84→            for bx in 0..blocks_x {
    85→                let x = bx * BLOCK_SIZE;
    86→                let y = by * BLOCK_SIZE;
    87→                let block_w = (BLOCK_SIZE).min(width - x);
    88→                let block_h = (BLOCK_SIZE).min(height - y);
    89→
    90→                // Check if this block changed
    91→                let mut changed = false;
    92→                'block_check: for row in 0..block_h {
    93→                    let row_start = ((y + row) as usize * stride) + (x as usize * 4);
    94→                    let row_end = row_start + (block_w as usize * 4);
    95→
    96→                    if row_end <= current.len() && row_end <= previous.len() {
    97→                        if current[row_start..row_end] != previous[row_start..row_end] {
    98→                            changed = true;
    99→                            break 'block_check;
   100→                        }
   101→                    } else {
   102→                        changed = true;
   103→                        break 'block_check;
   104→                    }
   105→                }
   106→
   107→                if changed {
   108→                    dirty_rects.push(DirtyRect {
   109→                        x,
   110→                        y,
   111→                        width: block_w,
   112→                        height: block_h,
   113→                    });
   114→                }
   115→            }
   116→        }
   117→
   118→        // Merge adjacent dirty rects (simple optimization)
   119→        // TODO: Implement proper rectangle merging
   120→
   121→        dirty_rects
   122→    }
   123→
   124→    /// Extract pixels for dirty rectangles only
   125→    fn extract_dirty_pixels(
   126→        &self,
   127→        data: &[u8],
   128→        width: u32,
   129→        dirty_rects: &[DirtyRect],
   130→    ) -> Vec<u8> {
   131→        let stride = (width * 4) as usize;
   132→        let mut pixels = Vec::new();
   133→
   134→        for rect in dirty_rects {
   135→            for row in 0..rect.height {
   136→                let row_start = ((rect.y + row) as usize * stride) + (rect.x as usize * 4);
   137→                let row_end = row_start + (rect.width as usize * 4);
   138→
   139→                if row_end <= data.len() {
   140→                    pixels.extend_from_slice(&data[row_start..row_end]);
   141→                }
   142→            }
   143→        }
   144→
   145→        pixels
   146→    }
   147→}
   148→
   149→impl Encoder for RawEncoder {
   150→    fn encode(&mut self, frame: &CapturedFrame) -> Result<EncodedFrame> {
   151→        self.sequence = self.sequence.wrapping_add(1);
   152→
   153→        let is_keyframe = self.force_keyframe || self.previous_frame.is_none();
   154→        self.force_keyframe = false;
   155→
   156→        let (data_to_compress, dirty_rects, full_frame) = if is_keyframe {
   157→            // Keyframe: send full frame
   158→            (frame.data.clone(), Vec::new(), true)
   159→        } else if let Some(ref previous) = self.previous_frame {
   160→            // Delta frame: detect and send only changed regions
   161→            let dirty_rects =
   162→                self.detect_dirty_rects(&frame.data, previous, frame.width, frame.height);
   163→
   164→            if dirty_rects.is_empty() {
   165→                // No changes, skip frame
   166→                return Ok(EncodedFrame {
   167→                    frame: VideoFrame::default(),
   168→                    size: 0,
   169→                    is_keyframe: false,
   170→                });
   171→            }
   172→
   173→            // If too many dirty rects, just send full frame
   174→            if dirty_rects.len() > 50 {
   175→                (frame.data.clone(), Vec::new(), true)
   176→            } else {
   177→                let dirty_pixels = self.extract_dirty_pixels(&frame.data, frame.width, &dirty_rects);
   178→                (dirty_pixels, dirty_rects, false)
   179→            }
   180→        } else {
   181→            (frame.data.clone(), Vec::new(), true)
   182→        };
   183→
   184→        // Compress the data
   185→        let compressed = self.compress(&data_to_compress)?;
   186→        let size = compressed.len();
   187→
   188→        // Build protobuf message
   189→        let proto_dirty_rects: Vec<ProtoDirtyRect> = dirty_rects
   190→            .iter()
   191→            .map(|r| ProtoDirtyRect {
   192→                x: r.x as i32,
   193→                y: r.y as i32,
   194→                width: r.width as i32,
   195→                height: r.height as i32,
   196→            })
   197→            .collect();
   198→
   199→        let raw_frame = RawFrame {
   200→            width: frame.width as i32,
   201→            height: frame.height as i32,
   202→            data: compressed,
   203→            compressed: true,
   204→            dirty_rects: proto_dirty_rects,
   205→            is_keyframe: full_frame,
   206→        };
   207→
   208→        let video_frame = VideoFrame {
   209→            timestamp: frame.timestamp.elapsed().as_millis() as i64,
   210→            display_id: frame.display_id as i32,
   211→            sequence: self.sequence as i32,
   212→            encoding: Some(video_frame::Encoding::Raw(raw_frame)),
   213→        };
   214→
   215→        // Save current frame for next comparison
   216→        self.previous_frame = Some(frame.data.clone());
   217→
   218→        Ok(EncodedFrame {
   219→            frame: video_frame,
   220→            size,
   221→            is_keyframe: full_frame,
   222→        })
   223→    }
   224→
   225→    fn request_keyframe(&mut self) {
   226→        self.force_keyframe = true;
   227→    }
   228→
   229→    fn name(&self) -> &str {
   230→        "raw+zstd"
   231→    }
   232→}
   233→

<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>

//! Raw frame encoder with Zstd compression
//!
//! Best for LAN connections where bandwidth is plentiful and latency is critical.
//! Compresses BGRA pixel data using Zstd for fast compression/decompression.

use super::{EncodedFrame, Encoder};
use crate::capture::{CapturedFrame, DirtyRect};
use crate::proto::{video_frame, DirtyRect as ProtoDirtyRect, RawFrame, VideoFrame};
use anyhow::Result;

/// Raw frame encoder with Zstd compression
pub struct RawEncoder {
    /// Compression level (1-22, default 3 for speed)
    compression_level: i32,

    /// Previous frame for delta detection
    previous_frame: Option<Vec<u8>>,

    /// Force keyframe on next encode
    force_keyframe: bool,

    /// Frame counter
    sequence: u32,
}

impl RawEncoder {
    /// Create a new raw encoder
    ///
    /// Quality 1-100 maps to Zstd compression level:
    /// - Low quality (1-33): Level 1-3 (fastest)
    /// - Medium quality (34-66): Level 4-9
    /// - High quality (67-100): Level 10-15 (best compression)
    pub fn new(quality: u32) -> Result<Self> {
        let compression_level = Self::quality_to_level(quality);

        Ok(Self {
            compression_level,
            previous_frame: None,
            force_keyframe: true, // Start with keyframe
            sequence: 0,
        })
    }

    /// Convert quality (1-100) to Zstd compression level
    fn quality_to_level(quality: u32) -> i32 {
        // Lower quality = faster compression (level 1-3)
        // Higher quality = better compression (level 10-15)
        // We optimize for speed, so cap at 6
        match quality {
            0..=33 => 1,
            34..=50 => 2,
            51..=66 => 3,
            67..=80 => 4,
            81..=90 => 5,
            _ => 6,
        }
    }

    /// Compress data using Zstd
    fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
        let compressed = zstd::encode_all(data, self.compression_level)?;
        Ok(compressed)
    }

    /// Detect dirty rectangles by comparing with previous frame
    fn detect_dirty_rects(
        &self,
        current: &[u8],
        previous: &[u8],
        width: u32,
        height: u32,
    ) -> Vec<DirtyRect> {
        // Simple block-based dirty detection
        // Divide screen into 64x64 blocks and check which changed
        const BLOCK_SIZE: u32 = 64;

        let mut dirty_rects = Vec::new();
        let stride = (width * 4) as usize;

        let blocks_x = (width + BLOCK_SIZE - 1) / BLOCK_SIZE;
        let blocks_y = (height + BLOCK_SIZE - 1) / BLOCK_SIZE;

        for by in 0..blocks_y {
            for bx in 0..blocks_x {
                let x = bx * BLOCK_SIZE;
                let y = by * BLOCK_SIZE;
                let block_w = (BLOCK_SIZE).min(width - x);
                let block_h = (BLOCK_SIZE).min(height - y);

                // Check if this block changed
                let mut changed = false;
                'block_check: for row in 0..block_h {
                    let row_start = ((y + row) as usize * stride) + (x as usize * 4);
                    let row_end = row_start + (block_w as usize * 4);

                    if row_end <= current.len() && row_end <= previous.len() {
                        if current[row_start..row_end] != previous[row_start..row_end] {
                            changed = true;
                            break 'block_check;
                        }
                    } else {
                        changed = true;
                        break 'block_check;
                    }
                }

                if changed {
                    dirty_rects.push(DirtyRect {
                        x,
                        y,
                        width: block_w,
                        height: block_h,
                    });
                }
            }
        }

        // Merge adjacent dirty rects (simple optimization)
        // TODO: Implement proper rectangle merging

        dirty_rects
    }

    /// Extract pixels for dirty rectangles only
    fn extract_dirty_pixels(
        &self,
        data: &[u8],
        width: u32,
        dirty_rects: &[DirtyRect],
    ) -> Vec<u8> {
        let stride = (width * 4) as usize;
        let mut pixels = Vec::new();

        for rect in dirty_rects {
            for row in 0..rect.height {
                let row_start = ((rect.y + row) as usize * stride) + (rect.x as usize * 4);
                let row_end = row_start + (rect.width as usize * 4);

                if row_end <= data.len() {
                    pixels.extend_from_slice(&data[row_start..row_end]);
                }
            }
        }

        pixels
    }
}

impl Encoder for RawEncoder {
    fn encode(&mut self, frame: &CapturedFrame) -> Result<EncodedFrame> {
        self.sequence = self.sequence.wrapping_add(1);

        let is_keyframe = self.force_keyframe || self.previous_frame.is_none();
        self.force_keyframe = false;

        let (data_to_compress, dirty_rects, full_frame) = if is_keyframe {
            // Keyframe: send full frame
            (frame.data.clone(), Vec::new(), true)
        } else if let Some(ref previous) = self.previous_frame {
            // Delta frame: detect and send only changed regions
            let dirty_rects =
                self.detect_dirty_rects(&frame.data, previous, frame.width, frame.height);

            if dirty_rects.is_empty() {
                // No changes, skip frame
                return Ok(EncodedFrame {
                    frame: VideoFrame::default(),
                    size: 0,
                    is_keyframe: false,
                });
            }

            // If too many dirty rects, just send full frame
            if dirty_rects.len() > 50 {
                (frame.data.clone(), Vec::new(), true)
            } else {
                let dirty_pixels = self.extract_dirty_pixels(&frame.data, frame.width, &dirty_rects);
                (dirty_pixels, dirty_rects, false)
            }
        } else {
            (frame.data.clone(), Vec::new(), true)
        };

        // Compress the data
        let compressed = self.compress(&data_to_compress)?;
        let size = compressed.len();

        // Build protobuf message
        let proto_dirty_rects: Vec<ProtoDirtyRect> = dirty_rects
            .iter()
            .map(|r| ProtoDirtyRect {
                x: r.x as i32,
                y: r.y as i32,
                width: r.width as i32,
                height: r.height as i32,
            })
            .collect();

        let raw_frame = RawFrame {
            width: frame.width as i32,
            height: frame.height as i32,
            data: compressed,
            compressed: true,
            dirty_rects: proto_dirty_rects,
            is_keyframe: full_frame,
        };

        let video_frame = VideoFrame {
            timestamp: frame.timestamp.elapsed().as_millis() as i64,
            display_id: frame.display_id as i32,
            sequence: self.sequence as i32,
            encoding: Some(video_frame::Encoding::Raw(raw_frame)),
        };

        // Save current frame for next comparison
        self.previous_frame = Some(frame.data.clone());

        Ok(EncodedFrame {
            frame: video_frame,
            size,
            is_keyframe: full_frame,
        })
    }

    fn request_keyframe(&mut self) {
        self.force_keyframe = true;
    }

    fn name(&self) -> &str {
        "raw+zstd"
    }
}


Perfect! Now let me check if there are any capture module files that define the CapturedFrame structure to get the complete picture:


     1→//! Screen capture module
     2→//!
     3→//! Provides DXGI Desktop Duplication for high-performance screen capture on Windows 8+,
     4→//! with GDI fallback for legacy systems or edge cases.
     5→
     6→#[cfg(windows)]
     7→mod dxgi;
     8→#[cfg(windows)]
     9→mod gdi;
    10→mod display;
    11→
    12→pub use display::{Display, DisplayInfo};
    13→
    14→use anyhow::Result;
    15→use std::time::Instant;
    16→
    17→/// Captured frame data
    18→#[derive(Debug)]
    19→pub struct CapturedFrame {
    20→    /// Frame width in pixels
    21→    pub width: u32,
    22→
    23→    /// Frame height in pixels
    24→    pub height: u32,
    25→
    26→    /// Raw BGRA pixel data (4 bytes per pixel)
    27→    pub data: Vec<u8>,
    28→
    29→    /// Timestamp when frame was captured
    30→    pub timestamp: Instant,
    31→
    32→    /// Display ID this frame is from
    33→    pub display_id: u32,
    34→
    35→    /// Regions that changed since last frame (if available)
    36→    pub dirty_rects: Option<Vec<DirtyRect>>,
    37→}
    38→
    39→/// Rectangular region that changed
    40→#[derive(Debug, Clone, Copy)]
    41→pub struct DirtyRect {
    42→    pub x: u32,
    43→    pub y: u32,
    44→    pub width: u32,
    45→    pub height: u32,
    46→}
    47→
    48→/// Screen capturer trait
    49→pub trait Capturer: Send {
    50→    /// Capture the next frame
    51→    ///
    52→    /// Returns None if no new frame is available (screen unchanged)
    53→    fn capture(&mut self) -> Result<Option<CapturedFrame>>;
    54→
    55→    /// Get the current display info
    56→    fn display(&self) -> &Display;
    57→
    58→    /// Check if capturer is still valid (display may have changed)
    59→    fn is_valid(&self) -> bool;
    60→}
    61→
    62→/// Create a capturer for the specified display
    63→#[cfg(windows)]
    64→pub fn create_capturer(display: Display, use_dxgi: bool, gdi_fallback: bool) -> Result<Box<dyn Capturer>> {
    65→    if use_dxgi {
    66→        match dxgi::DxgiCapturer::new(display.clone()) {
    67→            Ok(capturer) => {
    68→                tracing::info!("Using DXGI Desktop Duplication for capture");
    69→                return Ok(Box::new(capturer));
    70→            }
    71→            Err(e) => {
    72→                tracing::warn!("DXGI capture failed: {}, trying fallback", e);
    73→                if !gdi_fallback {
    74→                    return Err(e);
    75→                }
    76→            }
    77→        }
    78→    }
    79→
    80→    // GDI fallback
    81→    tracing::info!("Using GDI for capture");
    82→    Ok(Box::new(gdi::GdiCapturer::new(display)?))
    83→}
    84→
    85→#[cfg(not(windows))]
    86→pub fn create_capturer(_display: Display, _use_dxgi: bool, _gdi_fallback: bool) -> Result<Box<dyn Capturer>> {
    87→    anyhow::bail!("Screen capture only supported on Windows")
    88→}
    89→
    90→/// Get all available displays
    91→pub fn enumerate_displays() -> Result<Vec<Display>> {
    92→    display::enumerate_displays()
    93→}
    94→
    95→/// Get the primary display
    96→pub fn primary_display() -> Result<Display> {
    97→    let displays = enumerate_displays()?;
    98→    displays
    99→        .into_iter()
   100→        .find(|d| d.is_primary)
   101→        .ok_or_else(|| anyhow::anyhow!("No primary display found"))
   102→}
   103→

<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>

//! Screen capture module
//!
//! Provides DXGI Desktop Duplication for high-performance screen capture on Windows 8+,
//! with GDI fallback for legacy systems or edge cases.

#[cfg(windows)]
mod dxgi;
#[cfg(windows)]
mod gdi;
mod display;

pub use display::{Display, DisplayInfo};

use anyhow::Result;
use std::time::Instant;

/// Captured frame data
#[derive(Debug)]
pub struct CapturedFrame {
    /// Frame width in pixels
    pub width: u32,

    /// Frame height in pixels
    pub height: u32,

    /// Raw BGRA pixel data (4 bytes per pixel)
    pub data: Vec<u8>,

    /// Timestamp when frame was captured
    pub timestamp: Instant,

    /// Display ID this frame is from
    pub display_id: u32,

    /// Regions that changed since last frame (if available)
    pub dirty_rects: Option<Vec<DirtyRect>>,
}

/// Rectangular region that changed
#[derive(Debug, Clone, Copy)]
pub struct DirtyRect {
    pub x: u32,
    pub y: u32,
    pub width: u32,
    pub height: u32,
}

/// Screen capturer trait
pub trait Capturer: Send {
    /// Capture the next frame
    ///
    /// Returns None if no new frame is available (screen unchanged)
    fn capture(&mut self) -> Result<Option<CapturedFrame>>;

    /// Get the current display info
    fn display(&self) -> &Display;

    /// Check if capturer is still valid (display may have changed)
    fn is_valid(&self) -> bool;
}

/// Create a capturer for the specified display
#[cfg(windows)]
pub fn create_capturer(display: Display, use_dxgi: bool, gdi_fallback: bool) -> Result<Box<dyn Capturer>> {
    if use_dxgi {
        match dxgi::DxgiCapturer::new(display.clone()) {
            Ok(capturer) => {
                tracing::info!("Using DXGI Desktop Duplication for capture");
                return Ok(Box::new(capturer));
            }
            Err(e) => {
                tracing::warn!("DXGI capture failed: {}, trying fallback", e);
                if !gdi_fallback {
                    return Err(e);
                }
            }
        }
    }

    // GDI fallback
    tracing::info!("Using GDI for capture");
    Ok(Box::new(gdi::GdiCapturer::new(display)?))
}

#[cfg(not(windows))]
pub fn create_capturer(_display: Display, _use_dxgi: bool, _gdi_fallback: bool) -> Result<Box<dyn Capturer>> {
    anyhow::bail!("Screen capture only supported on Windows")
}

/// Get all available displays
pub fn enumerate_displays() -> Result<Vec<Display>> {
    display::enumerate_displays()
}

/// Get the primary display
pub fn primary_display() -> Result<Display> {
    let displays = enumerate_displays()?;
    displays
        .into_iter()
        .find(|d| d.is_primary)
        .ok_or_else(|| anyhow::anyhow!("No primary display found"))
}


Excellent! Now let me check if there are any generated protobuf Rust files to see how the messages are structured:


Bash

find "C:\Users\MikeSwanson\Claude\guru-connect" -name "*.rs" -type f | grep -i proto | head -20

Find all proto-related Rust files


No files found


C:\Users\MikeSwanson\Claude\gururmm\agent\src\main.rs
C:\Users\MikeSwanson\Claude\guru-connect\agent\src\main.rs


     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;
    }
}


Perfect! Now let me check the Cargo.toml to understand how protobuf compilation is set up:


find "C:\Users\MikeSwanson\Claude\guru-connect" -name "build.rs" -type f

Find build.rs files for protobuf compilation


     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"


C:\Users\MikeSwanson\Claude\guru-connect/agent/build.rs
C:\Users\MikeSwanson\Claude\guru-connect/server/build.rs


     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 create a comprehensive analysis document. Let me gather all the information I've found and provide you with a clear breakdown:

## GuruConnect Video Encoding Analysis

I've thoroughly explored the GuruConnect codebase to understand how video frames are encoded and transmitted. Here's the complete breakdown:

### 1. **Agent Encoder Module Structure**

The encoder module (`agent/src/encoder/`) consists of:
- **mod.rs** - Main encoder trait and factory
- **raw.rs** - RawEncoder implementation (primary encoder for LAN)

**Encoder Trait** (from mod.rs):
```rust
pub trait Encoder: Send {
    fn encode(&mut self, frame: &CapturedFrame) -> Result<EncodedFrame>;
    fn request_keyframe(&mut self);
    fn name(&self) -> &str;
}
```

Returns an `EncodedFrame` which wraps:
- `frame: VideoFrame` (protobuf message)
- `size: usize` (compressed size)
- `is_keyframe: bool` (indicates full vs incremental frame)

### 2. **RawFrame Format (BGRA + Zstd Compression)**

**Source Data** - From `capture/mod.rs`:
```rust
pub struct CapturedFrame {
    pub width: u32,                    // Frame width in pixels
    pub height: u32,                   // Frame height in pixels
    pub data: Vec<u8>,                 // Raw BGRA pixel data (4 bytes per pixel)
    pub timestamp: Instant,
    pub display_id: u32,
    pub dirty_rects: Option<Vec<DirtyRect>>,
}

pub struct DirtyRect {
    pub x: u32,
    pub y: u32,
    pub width: u32,
    pub height: u32,
}
```

**Encoding Process** (from raw.rs lines 150-223):

1. **Keyframe Check**: Determines if full frame or delta frame
2. **Compression Level Mapping** (lines 45-57):
   - Quality 1-33 → Zstd Level 1 (fastest)
   - Quality 34-50 → Zstd Level 2
   - Quality 51-66 → Zstd Level 3
   - Quality 67-80 → Zstd Level 4
   - Quality 81-90 → Zstd Level 5
   - Quality 91-100 → Zstd Level 6 (capped for speed)

3. **Delta Detection** (for non-keyframes):
   - Compares current frame with previous frame
   - Divides screen into 64x64 blocks (line 75)
   - Detects which blocks changed
   - If >50 dirty rects, sends full frame instead

4. **Pixel Extraction**: If delta frame, extracts only changed regions

5. **Zstd Compression**: Uses `zstd::encode_all()` with the computed level

### 3. **Protobuf Structure (From guruconnect.proto)**

**VideoFrame Message** (lines 56-67):
```protobuf
message VideoFrame {
    int64 timestamp = 1;              // Milliseconds elapsed
    int32 display_id = 2;             // Display number
    int32 sequence = 3;               // Frame sequence number

    oneof encoding {
        RawFrame raw = 10;            // Raw BGRA + zstd
        EncodedFrame vp9 = 11;        // VP9 codec
        EncodedFrame h264 = 12;       // H264 codec
        EncodedFrame h265 = 13;       // H265 codec
    }
}
```

**RawFrame Message** (lines 69-76):
```protobuf
message RawFrame {
    int32 width = 1;                  // Frame width in pixels
    int32 height = 2;                 // Frame height in pixels
    bytes data = 3;                   // Zstd compressed BGRA
    bool compressed = 4;              // Always true for raw frames
    repeated DirtyRect dirty_rects = 5; // Changed regions (empty for keyframes)
    bool is_keyframe = 6;             // true = full frame, false = delta
}
```

**DirtyRect Message** (lines 78-83):
```protobuf
message DirtyRect {
    int32 x = 1;                      // X coordinate
    int32 y = 2;                      // Y coordinate
    int32 width = 3;                  // Rectangle width
    int32 height = 4;                 // Rectangle height
}
```

### 4. **Dirty Rectangle Implementation**

**Block-Based Detection** (raw.rs lines 73-122):
- Screen divided into 64x64 pixel blocks
- Each block compared row-by-row with previous frame
- Comparison is byte-for-byte (BGRA format)
- Stride calculation: `width * 4` bytes (4 bytes per pixel)

**Row Comparison Logic**:
```rust
let stride = (width * 4) as usize;
for row in 0..block_h {
    let row_start = ((y + row) as usize * stride) + (x as usize * 4);
    let row_end = row_start + (block_w as usize * 4);
    if current[row_start..row_end] != previous[row_start..row_end] {
        // Block changed, add to dirty_rects
    }
}
```

**Optimization**: If dirty_rects.len() > 50, the encoder sends a full keyframe instead (line 174)

**Delta Pixel Extraction** (lines 125-146):
- For each dirty rect, extracts pixels row by row
- Only sends changed pixels, not metadata positioning
- Decoder must use DirtyRect coords to place pixels in final frame

### 5. **Binary Format for JavaScript Decoding**

**Protobuf Encoding**:
- Uses Prost (Rust protobuf library)
- Standard protobuf3 wire format
- Messages are length-prefixed in streams

**Typical Message Flow**:
1. VideoFrame protobuf message is serialized
2. RawFrame (as oneof) includes:
   - `width` (varint encoded)
   - `height` (varint encoded)
   - `data` (length-delimited bytes - zstd compressed)
   - `compressed` (boolean)
   - `dirty_rects` (repeated DirtyRect messages)
   - `is_keyframe` (boolean)

3. Each DirtyRect contains 4 varints (x, y, width, height)

**Decompression Steps**:
1. Parse VideoFrame protobuf
2. Extract RawFrame from oneof
3. Get width, height, compressed data
4. Decompress using zstd decoder
5. If `is_keyframe == true`: Use full decompressed BGRA data
6. If `is_keyframe == false`:
   - Decompressed data contains only pixels from dirty_rects
   - Iterate through dirty_rects
   - For each rect, copy pixels from extracted data into position
   - Calculate source position: sum of (width * height * 4) of previous rects

**Color Format**: BGRA (32-bit)
- Byte 0: Blue
- Byte 1: Green
- Byte 2: Red
- Byte 3: Alpha

### 6. **Key Implementation Details from raw.rs**

**Compression Settings**:
```rust
compression_level: i32  // 1-6 (capped, even though zstd supports up to 22)
```

**Sequence Numbering**:
- Self-incrementing u32
- Wraps around (`wrapping_add(1)`)
- Used for frame ordering and acknowledgments

**State Management**:
```rust
pub struct RawEncoder {
    compression_level: i32,
    previous_frame: Option<Vec<u8>>,  // For delta detection
    force_keyframe: bool,              // Allows forcing full frames
    sequence: u32,                     // Frame counter
}
```

**Special Cases**:
- First frame is always keyframe (line 39)
- If no changes detected: returns empty VideoFrame with size=0 (line 166-170)
- Force keyframe requests honored on next encode

### 7. **Protocol Integration**

**Top-Level Message Wrapper** (guruconnect.proto line 285):
```protobuf
message Message {
    oneof payload {
        VideoFrame video_frame = 10;
        VideoAck video_ack = 11;
        // ... other message types
    }
}
```

**Video Acknowledgment**:
```protobuf
message VideoAck {
    int32 sequence = 1;    // Match VideoFrame sequence
    int64 timestamp = 2;
}
```

**Quality Settings** (for future use):
```protobuf
message QualitySettings {
    QualityPreset preset = 1;
    int32 custom_fps = 2;
    int32 custom_bitrate = 3;
    CodecPreference codec = 4;  // CODEC_RAW, CODEC_VP9, etc.
}
```

### Summary for JavaScript Implementation

To decode GuruConnect video frames in JavaScript:

1. **Parse Message**: Use protobuf decoder for Message wrapper
2. **Extract VideoFrame**: Read the video_frame field
3. **Check Encoding**: Verify it's RawFrame type (field 10)
4. **Get RawFrame**: Extract width, height, compressed data, is_keyframe, dirty_rects
5. **Decompress**: Use zstd-wasm or similar to decompress data
6. **Reconstruct**:
   - If keyframe: Use full decompressed data
   - If delta: Use DirtyRects to place pixels correctly
7. **Convert BGRA**: Ensure your canvas renderer handles BGRA format (or convert to RGBA)
8. **Render**: Display on canvas at 4 bytes/pixel

The architecture is optimized for LAN connections where bandwidth is abundant but latency matters - raw BGRA with light compression (Zstd level 1-6) and delta encoding gives very low latency while maintaining reasonable bandwidth usage.