# Code Quality & Refactoring Ideation Agent

You are a senior software architect and code quality expert. Your task is to analyze a codebase and identify refactoring opportunities, code smells, best practice violations, and areas that could benefit from improved code quality.

## Context

You have access to:
- Project index with file structure and file sizes
- Source code across the project
- Package manifest (package.json, requirements.txt, etc.)
- Configuration files (ESLint, Prettier, tsconfig, etc.)
- Git history (if available)
- Memory context from previous sessions (if available)
- Graph hints from Graphiti knowledge graph (if available)

### Graph Hints Integration

If `graph_hints.json` exists and contains hints for your ideation type (`code_quality`), use them to:
1. **Avoid duplicates**: Don't suggest refactorings that have already been completed
2. **Build on success**: Prioritize refactoring patterns that worked well in the past
3. **Learn from failures**: Avoid refactorings that previously caused regressions
4. **Leverage context**: Use historical code quality knowledge to identify high-impact areas

## Your Mission

Identify code quality issues across these categories:

### 1. Large Files
- Files exceeding 500-800 lines that should be split
- Component files over 400 lines
- Monolithic components/modules
- "God objects" with too many responsibilities
- Single files handling multiple concerns

### 2. Code Smells
- Duplicated code blocks
- Long methods/functions (>50 lines)
- Deep nesting (>3 levels)
- Too many parameters (>4)
- Primitive obsession
- Feature envy
- Inappropriate intimacy between modules

### 3. High Complexity
- Cyclomatic complexity issues
- Complex conditionals that need simplification
- Overly clever code that's hard to understand
- Functions doing too many things

### 4. Code Duplication
- Copy-pasted code blocks
- Similar logic that could be abstracted
- Repeated patterns that should be utilities
- Near-duplicate components

### 5. Naming Conventions
- Inconsistent naming styles
- Unclear/cryptic variable names
- Abbreviations that hurt readability
- Names that don't reflect purpose

### 6. File Structure
- Poor folder organization
- Inconsistent module boundaries
- Circular dependencies
- Misplaced files
- Missing index/barrel files

### 7. Linting Issues
- Missing ESLint/Prettier configuration
- Inconsistent code formatting
- Unused variables/imports
- Missing or inconsistent rules

### 8. Test Coverage
- Missing unit tests for critical logic
- Components without test files
- Untested edge cases
- Missing integration tests

### 9. Type Safety
- Missing TypeScript types
- Excessive `any` usage
- Incomplete type definitions
- Runtime type mismatches

### 10. Dependency Issues
- Unused dependencies
- Duplicate dependencies
- Outdated dev tooling
- Missing peer dependencies

### 11. Dead Code
- Unused functions/components
- Commented-out code blocks
- Unreachable code paths
- Deprecated features not removed

### 12. Git Hygiene
- Large commits that should be split
- Missing commit message standards
- Lack of branch naming conventions
- Missing pre-commit hooks

## Analysis Process

1. **File Size Analysis**
   - Identify files over 500-800 lines (context-dependent)
   - Find components with too many exports
   - Check for monolithic modules

2. **Pattern Detection**
   - Search for duplicated code blocks
   - Find similar function signatures
   - Identify repeated error handling patterns

3. **Complexity Metrics**
   - Estimate cyclomatic complexity
   - Count nesting levels
   - Measure function lengths

4. **Config Review**
   - Check for linting configuration
   - Review TypeScript strictness
   - Assess test setup

5. **Structure Analysis**
   - Map module dependencies
   - Check for circular imports
   - Review folder organization

## Output Format

Write your findings to `{output_dir}/code_quality_ideas.json`:

```json
{
  "code_quality": [
    {
      "id": "cq-001",
      "type": "code_quality",
      "title": "Split large API handler file into domain modules",
      "description": "The file src/api/handlers.ts has grown to 1200 lines and handles multiple unrelated domains (users, products, orders). This violates single responsibility and makes the code hard to navigate and maintain.",
      "rationale": "Very large files increase cognitive load, make code reviews harder, and often lead to merge conflicts. Smaller, focused modules are easier to test, maintain, and reason about.",
      "category": "large_files",
      "severity": "major",
      "affectedFiles": ["src/api/handlers.ts"],
      "currentState": "Single 1200-line file handling users, products, and orders API logic",
      "proposedChange": "Split into src/api/users/handlers.ts, src/api/products/handlers.ts, src/api/orders/handlers.ts with shared utilities in src/api/utils/",
      "codeExample": "// Current:\nexport function handleUserCreate() { ... }\nexport function handleProductList() { ... }\nexport function handleOrderSubmit() { ... }\n\n// Proposed:\n// users/handlers.ts\nexport function handleCreate() { ... }",
      "bestPractice": "Single Responsibility Principle - each module should have one reason to change",
      "metrics": {
        "lineCount": 1200,
        "complexity": null,
        "duplicateLines": null,
        "testCoverage": null
      },
      "estimatedEffort": "medium",
      "breakingChange": false,
      "prerequisites": ["Ensure test coverage before refactoring"]
    },
    {
      "id": "cq-002",
      "type": "code_quality",
      "title": "Extract duplicated form validation logic",
      "description": "Similar validation logic is duplicated across 5 form components. Each validates email, phone, and required fields with slightly different implementations.",
      "rationale": "Code duplication leads to bugs when fixes are applied inconsistently and increases maintenance burden.",
      "category": "duplication",
      "severity": "minor",
      "affectedFiles": [
        "src/components/UserForm.tsx",
        "src/components/ContactForm.tsx",
        "src/components/SignupForm.tsx",
        "src/components/ProfileForm.tsx",
        "src/components/CheckoutForm.tsx"
      ],
      "currentState": "5 forms each implementing their own validation with 15-20 lines of similar code",
      "proposedChange": "Create src/lib/validation.ts with reusable validators (validateEmail, validatePhone, validateRequired) and a useFormValidation hook",
      "codeExample": "// Current (repeated in 5 files):\nconst validateEmail = (v) => /^[^@]+@[^@]+\\.[^@]+$/.test(v);\n\n// Proposed:\nimport { validators, useFormValidation } from '@/lib/validation';\nconst { errors, validate } = useFormValidation({\n  email: validators.email,\n  phone: validators.phone\n});",
      "bestPractice": "DRY (Don't Repeat Yourself) - extract common logic into reusable utilities",
      "metrics": {
        "lineCount": null,
        "complexity": null,
        "duplicateLines": 85,
        "testCoverage": null
      },
      "estimatedEffort": "small",
      "breakingChange": false,
      "prerequisites": null
    }
  ],
  "metadata": {
    "filesAnalyzed": 156,
    "largeFilesFound": 8,
    "duplicateBlocksFound": 12,
    "lintingConfigured": true,
    "testsPresent": true,
    "generatedAt": "2024-12-11T10:00:00Z"
  }
}
```

## Severity Classification

| Severity | Description | Examples |
|----------|-------------|----------|
| critical | Blocks development, causes bugs | Circular deps, type errors |
| major | Significant maintainability impact | Large files, high complexity |
| minor | Should be addressed but not urgent | Duplication, naming issues |
| suggestion | Nice to have improvements | Style consistency, docs |

## Guidelines

- **Prioritize Impact**: Focus on issues that most affect maintainability and developer experience
- **Provide Clear Refactoring Steps**: Each finding should include how to fix it
- **Consider Breaking Changes**: Flag refactorings that might break existing code or tests
- **Identify Prerequisites**: Note if something else should be done first
- **Be Realistic About Effort**: Accurately estimate the work required
- **Include Code Examples**: Show before/after when helpful
- **Consider Trade-offs**: Sometimes "imperfect" code is acceptable for good reasons

## Categories Explained

| Category | Focus | Common Issues |
|----------|-------|---------------|
| large_files | File size & scope | >300 line files, monoliths |
| code_smells | Design problems | Long methods, deep nesting |
| complexity | Cognitive load | Complex conditionals, many branches |
| duplication | Repeated code | Copy-paste, similar patterns |
| naming | Readability | Unclear names, inconsistency |
| structure | Organization | Folder structure, circular deps |
| linting | Code style | Missing config, inconsistent format |
| testing | Test coverage | Missing tests, uncovered paths |
| types | Type safety | Missing types, excessive `any` |
| dependencies | Package management | Unused, outdated, duplicates |
| dead_code | Unused code | Commented code, unreachable paths |
| git_hygiene | Version control | Commit practices, hooks |

## Common Patterns to Flag

### Large File Indicators
```
# Files to investigate (use judgment - context matters)
- Component files > 400-500 lines
- Utility/service files > 600-800 lines
- Test files > 800 lines (often acceptable if well-organized)
- Single-purpose modules > 1000 lines (definite split candidate)
```

### Code Smell Patterns
```javascript
// Long parameter list (>4 params)
function createUser(name, email, phone, address, city, state, zip, country) { }

// Deep nesting (>3 levels)
if (a) { if (b) { if (c) { if (d) { ... } } } }

// Feature envy - method uses more from another class
class Order {
  getCustomerDiscount() {
    return this.customer.level * this.customer.years * this.customer.purchases;
  }
}
```

### Duplication Signals
```javascript
// Near-identical functions
function validateUserEmail(email) { return /regex/.test(email); }
function validateContactEmail(email) { return /regex/.test(email); }
function validateOrderEmail(email) { return /regex/.test(email); }
```

### Type Safety Issues
```typescript
// Excessive any usage
const data: any = fetchData();
const result: any = process(data as any);

// Missing return types
function calculate(a, b) { return a + b; }  // Should have : number
```

Remember: Code quality improvements should make code easier to understand, test, and maintain. Focus on changes that provide real value to the development team, not arbitrary rules.

---

**Output Directory**: C:\Users\MikeSwanson\claude-projects\guru-connect\.auto-claude\ideation
**Project Directory**: C:\Users\MikeSwanson\claude-projects\guru-connect
**Max Ideas**: 5

**Ideation Context**: C:\Users\MikeSwanson\claude-projects\guru-connect\.auto-claude\ideation\ideation_context.json
**Project Index**: C:\Users\MikeSwanson\claude-projects\guru-connect\.auto-claude\ideation\project_index.json
**Output File**: C:\Users\MikeSwanson\claude-projects\guru-connect\.auto-claude\ideation\code_quality_ideas.json
**Max Ideas**: 5

Generate up to 5 Code Quality & Refactoring ideas.
Avoid duplicating features that are already planned (see ideation_context.json).
Output your ideas to code_quality_ideas.json.


The user wants me to analyze the GuruConnect codebase for code quality issues and refactoring opportunities. I need to:

1. First, read the ideation context and project index to understand what's already there
2. Analyze the codebase for code quality issues
3. Generate up to 5 code quality improvement ideas
4. Write them to the specified output file

Let me start by reading the context files and exploring the codebase structure.


     1→{
     2→  "existing_features": [],
     3→  "tech_stack": [],
     4→  "target_audience": null,
     5→  "planned_features": [],
     6→  "graph_hints": {},
     7→  "config": {
     8→    "enabled_types": [
     9→      "code_improvements",
    10→      "ui_ux_improvements",
    11→      "security_hardening",
    12→      "documentation_gaps",
    13→      "performance_optimizations",
    14→      "code_quality"
    15→    ],
    16→    "include_roadmap_context": true,
    17→    "include_kanban_context": true,
    18→    "max_ideas_per_type": 5
    19→  },
    20→  "created_at": "2026-01-11T15:32:38.355636"
    21→}

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


     1→{
     2→  "project_root": "C:\\Users\\MikeSwanson\\claude-projects\\guru-connect",
     3→  "project_type": "monorepo",
     4→  "services": {
     5→    "agent": {
     6→      "name": "agent",
     7→      "path": "C:\\Users\\MikeSwanson\\claude-projects\\guru-connect\\agent",
     8→      "language": "Rust",
     9→      "framework": null,
    10→      "type": "unknown",
    11→      "package_manager": "cargo",
    12→      "key_directories": {
    13→        "src": {
    14→          "path": "src",
    15→          "purpose": "Source code"
    16→        }
    17→      },
    18→      "entry_point": "src/main.rs"
    19→    },
    20→    "dashboard": {
    21→      "name": "dashboard",
    22→      "path": "C:\\Users\\MikeSwanson\\claude-projects\\guru-connect\\dashboard",
    23→      "language": "TypeScript",
    24→      "framework": "React",
    25→      "type": "frontend",
    26→      "package_manager": "npm",
    27→      "default_port": 3000,
    28→      "build_tool": "Vite",
    29→      "styling": "Tailwind CSS",
    30→      "dev_command": "npm run dev",
    31→      "key_directories": {
    32→        "src": {
    33→          "path": "src",
    34→          "purpose": "Source code"
    35→        }
    36→      },
    37→      "entry_point": "src/App.tsx",
    38→      "dependencies": [
    39→        "@tanstack/react-query",
    40→        "axios",
    41→        "clsx",
    42→        "fzstd",
    43→        "lucide-react",
    44→        "react",
    45→        "react-dom",
    46→        "react-router-dom",
    47→        "tailwind-merge"
    48→      ],
    49→      "dev_dependencies": [
    50→        "@eslint/js",
    51→        "@tailwindcss/vite",
    52→        "@types/node",
    53→        "@types/react",
    54→        "@types/react-dom",
    55→        "@vitejs/plugin-react",
    56→        "autoprefixer",
    57→        "eslint",
    58→        "eslint-plugin-react-hooks",
    59→        "eslint-plugin-react-refresh"
    60→      ],
    61→      "consumes": [
    62→        "server.api"
    63→      ]
    64→    },
    65→    "server": {
    66→      "name": "server",
    67→      "path": "C:\\Users\\MikeSwanson\\claude-projects\\guru-connect\\server",
    68→      "language": "Rust",
    69→      "framework": "Axum",
    70→      "type": "backend",
    71→      "package_manager": "cargo",
    72→      "default_port": 3000,
    73→      "key_directories": {
    74→        "src": {
    75→          "path": "src",
    76→          "purpose": "Source code"
    77→        }
    78→      },
    79→      "entry_point": "src/main.rs",
    80→      "api": {
    81→        "routes": [
    82→          {
    83→            "path": "/health",
    84→            "methods": [
    85→              "GET"
    86→            ],
    87→            "file": "src\\main.rs",
    88→            "framework": "Rust",
    89→            "requires_auth": false
    90→          },
    91→          {
    92→            "path": "/ws/agent",
    93→            "methods": [
    94→              "GET"
    95→            ],
    96→            "file": "src\\main.rs",
    97→            "framework": "Rust",
    98→            "requires_auth": false
    99→          },
   100→          {
   101→            "path": "/ws/viewer",
   102→            "methods": [
   103→              "GET"
   104→            ],
   105→            "file": "src\\main.rs",
   106→            "framework": "Rust",
   107→            "requires_auth": false
   108→          },
   109→          {
   110→            "path": "/api/sessions",
   111→            "methods": [
   112→              "GET"
   113→            ],
   114→            "file": "src\\main.rs",
   115→            "framework": "Rust",
   116→            "requires_auth": false
   117→          },
   118→          {
   119→            "path": "/api/sessions/:id",
   120→            "methods": [
   121→              "GET"
   122→            ],
   123→            "file": "src\\main.rs",
   124→            "framework": "Rust",
   125→            "requires_auth": false
   126→          }
   127→        ],
   128→        "total_routes": 5,
   129→        "methods": [
   130→          "GET"
   131→        ],
   132→        "protected_routes": []
   133→      },
   134→      "monitoring": {
   135→        "health_checks": [
   136→          "/health"
   137→        ]
   138→      }
   139→    }
   140→  },
   141→  "infrastructure": {},
   142→  "conventions": {}
   143→}

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

dashboard

@tanstack/react-query axios clsx fzstd lucide-react react react-dom react-router-dom tailwind-merge


Good, I have the context. This is a monorepo with:
- agent (Rust) - Windows remote desktop agent
- server (Rust/Axum) - Relay server
- dashboard (React/TypeScript/Vite) - Web viewer

Let me explore the codebase to understand file sizes and structure. I'll analyze:
1. Large files
2. Code complexity
3. Duplication
4. Test coverage
5. Type safety
6. Linting configuration


Let me explore the codebase structure and analyze the source files for code quality issues.


Let me now read the key source files to analyze them for code quality issues. I'll focus on the actual project source files (not node_modules or target directories).


Let me read the key source files to analyze their size and complexity:


     1→//! GuruConnect Agent - Remote Desktop Agent for Windows
     2→//!
     3→//! Provides screen capture, input injection, and remote control capabilities.
     4→
     5→mod capture;
     6→mod config;
     7→mod encoder;
     8→mod input;
     9→mod session;
    10→mod transport;
    11→
    12→pub mod proto {
    13→    include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
    14→}
    15→
    16→use anyhow::Result;
    17→use tracing::{info, error, Level};
    18→use tracing_subscriber::FmtSubscriber;
    19→
    20→#[tokio::main]
    21→async fn main() -> Result<()> {
    22→    // Initialize logging
    23→    let subscriber = FmtSubscriber::builder()
    24→        .with_max_level(Level::INFO)
    25→        .with_target(true)
    26→        .with_thread_ids(true)
    27→        .init();
    28→
    29→    info!("GuruConnect Agent v{}", env!("CARGO_PKG_VERSION"));
    30→
    31→    // Load configuration
    32→    let config = config::Config::load()?;
    33→    info!("Loaded configuration for server: {}", config.server_url);
    34→
    35→    // Run the agent
    36→    if let Err(e) = run_agent(config).await {
    37→        error!("Agent error: {}", e);
    38→        return Err(e);
    39→    }
    40→
    41→    Ok(())
    42→}
    43→
    44→async fn run_agent(config: config::Config) -> Result<()> {
    45→    // Create session manager
    46→    let mut session = session::SessionManager::new(config.clone());
    47→
    48→    // Connect to server and run main loop
    49→    loop {
    50→        info!("Connecting to server...");
    51→
    52→        match session.connect().await {
    53→            Ok(_) => {
    54→                info!("Connected to server");
    55→
    56→                // Run session until disconnect
    57→                if let Err(e) = session.run().await {
    58→                    error!("Session error: {}", e);
    59→                }
    60→            }
    61→            Err(e) => {
    62→                error!("Connection failed: {}", e);
    63→            }
    64→        }
    65→
    66→        // Wait before reconnecting
    67→        info!("Reconnecting in 5 seconds...");
    68→        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
    69→    }
    70→}
    71→

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

mod capture;
mod config;
mod encoder;
mod input;
mod session;
mod transport;

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

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

#[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"));

    // Load configuration
    let config = config::Config::load()?;
    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(())
}

async fn run_agent(config: config::Config) -> Result<()> {
    // Create session manager
    let mut session = session::SessionManager::new(config.clone());

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

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

                // Run session until disconnect
                if let Err(e) = session.run().await {
                    error!("Session error: {}", e);
                }
            }
            Err(e) => {
                error!("Connection failed: {}", e);
            }
        }

        // Wait before reconnecting
        info!("Reconnecting in 5 seconds...");
        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
    }
}


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


     1→//! DXGI Desktop Duplication screen capture
     2→//!
     3→//! Uses the Windows Desktop Duplication API (available on Windows 8+) for
     4→//! high-performance, low-latency screen capture with hardware acceleration.
     5→//!
     6→//! Reference: RustDesk's scrap library implementation
     7→
     8→use super::{CapturedFrame, Capturer, DirtyRect, Display};
     9→use anyhow::{Context, Result};
    10→use std::ptr;
    11→use std::time::Instant;
    12→
    13→use windows::Win32::Graphics::Direct3D::D3D_DRIVER_TYPE_UNKNOWN;
    14→use windows::Win32::Graphics::Direct3D11::{
    15→    D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D,
    16→    D3D11_CPU_ACCESS_READ, D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC,
    17→    D3D11_USAGE_STAGING, D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ,
    18→};
    19→use windows::Win32::Graphics::Dxgi::{
    20→    CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory1, IDXGIOutput, IDXGIOutput1,
    21→    IDXGIOutputDuplication, IDXGIResource, DXGI_ERROR_ACCESS_LOST,
    22→    DXGI_ERROR_WAIT_TIMEOUT, DXGI_OUTDUPL_DESC, DXGI_OUTDUPL_FRAME_INFO,
    23→    DXGI_RESOURCE_PRIORITY_MAXIMUM,
    24→};
    25→use windows::core::Interface;
    26→
    27→/// DXGI Desktop Duplication capturer
    28→pub struct DxgiCapturer {
    29→    display: Display,
    30→    device: ID3D11Device,
    31→    context: ID3D11DeviceContext,
    32→    duplication: IDXGIOutputDuplication,
    33→    staging_texture: Option<ID3D11Texture2D>,
    34→    width: u32,
    35→    height: u32,
    36→    last_frame: Option<Vec<u8>>,
    37→}
    38→
    39→impl DxgiCapturer {
    40→    /// Create a new DXGI capturer for the specified display
    41→    pub fn new(display: Display) -> Result<Self> {
    42→        let (device, context, duplication, desc) = Self::create_duplication(&display)?;
    43→
    44→        Ok(Self {
    45→            display,
    46→            device,
    47→            context,
    48→            duplication,
    49→            staging_texture: None,
    50→            width: desc.ModeDesc.Width,
    51→            height: desc.ModeDesc.Height,
    52→            last_frame: None,
    53→        })
    54→    }
    55→
    56→    /// Create D3D device and output duplication
    57→    fn create_duplication(
    58→        display_info: &Display,
    59→    ) -> Result<(ID3D11Device, ID3D11DeviceContext, IDXGIOutputDuplication, DXGI_OUTDUPL_DESC)> {
    60→        unsafe {
    61→            // Create DXGI factory
    62→            let factory: IDXGIFactory1 = CreateDXGIFactory1()
    63→                .context("Failed to create DXGI factory")?;
    64→
    65→            // Find the adapter and output for this display
    66→            let (adapter, output) = Self::find_adapter_output(&factory, display_info)?;
    67→
    68→            // Create D3D11 device
    69→            let mut device: Option<ID3D11Device> = None;
    70→            let mut context: Option<ID3D11DeviceContext> = None;
    71→
    72→            D3D11CreateDevice(
    73→                &adapter,
    74→                D3D_DRIVER_TYPE_UNKNOWN,
    75→                None,
    76→                Default::default(),
    77→                None,
    78→                D3D11_SDK_VERSION,
    79→                Some(&mut device),
    80→                None,
    81→                Some(&mut context),
    82→            )
    83→            .context("Failed to create D3D11 device")?;
    84→
    85→            let device = device.context("D3D11 device is None")?;
    86→            let context = context.context("D3D11 context is None")?;
    87→
    88→            // Get IDXGIOutput1 interface
    89→            let output1: IDXGIOutput1 = output.cast()
    90→                .context("Failed to get IDXGIOutput1 interface")?;
    91→
    92→            // Create output duplication
    93→            let duplication = output1.DuplicateOutput(&device)
    94→                .context("Failed to create output duplication")?;
    95→
    96→            // Get duplication description
    97→            let desc = duplication.GetDesc();
    98→
    99→            tracing::info!(
   100→                "Created DXGI duplication: {}x{}, display: {}",
   101→                desc.ModeDesc.Width,
   102→                desc.ModeDesc.Height,
   103→                display_info.name
   104→            );
   105→
   106→            Ok((device, context, duplication, desc))
   107→        }
   108→    }
   109→
   110→    /// Find the adapter and output for the specified display
   111→    fn find_adapter_output(
   112→        factory: &IDXGIFactory1,
   113→        display: &Display,
   114→    ) -> Result<(IDXGIAdapter1, IDXGIOutput)> {
   115→        unsafe {
   116→            let mut adapter_idx = 0u32;
   117→
   118→            loop {
   119→                // Enumerate adapters
   120→                let adapter: IDXGIAdapter1 = match factory.EnumAdapters1(adapter_idx) {
   121→                    Ok(a) => a,
   122→                    Err(_) => break,
   123→                };
   124→
   125→                let mut output_idx = 0u32;
   126→
   127→                loop {
   128→                    // Enumerate outputs for this adapter
   129→                    let output: IDXGIOutput = match adapter.EnumOutputs(output_idx) {
   130→                        Ok(o) => o,
   131→                        Err(_) => break,
   132→                    };
   133→
   134→                    // Check if this is the display we want
   135→                    let desc = output.GetDesc()?;
   136→
   137→                    let name = String::from_utf16_lossy(
   138→                        &desc.DeviceName[..desc.DeviceName.iter().position(|&c| c == 0).unwrap_or(desc.DeviceName.len())]
   139→                    );
   140→
   141→                    if name == display.name || desc.Monitor.0 as isize == display.handle {
   142→                        return Ok((adapter, output));
   143→                    }
   144→
   145→                    output_idx += 1;
   146→                }
   147→
   148→                adapter_idx += 1;
   149→            }
   150→
   151→            // If we didn't find the specific display, use the first one
   152→            let adapter: IDXGIAdapter1 = factory.EnumAdapters1(0)
   153→                .context("No adapters found")?;
   154→            let output: IDXGIOutput = adapter.EnumOutputs(0)
   155→                .context("No outputs found")?;
   156→
   157→            Ok((adapter, output))
   158→        }
   159→    }
   160→
   161→    /// Create or get the staging texture for CPU access
   162→    fn get_staging_texture(&mut self, src_texture: &ID3D11Texture2D) -> Result<&ID3D11Texture2D> {
   163→        if self.staging_texture.is_none() {
   164→            unsafe {
   165→                let mut desc = D3D11_TEXTURE2D_DESC::default();
   166→                src_texture.GetDesc(&mut desc);
   167→
   168→                desc.Usage = D3D11_USAGE_STAGING;
   169→                desc.BindFlags = Default::default();
   170→                desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ.0 as u32;
   171→                desc.MiscFlags = Default::default();
   172→
   173→                let mut staging: Option<ID3D11Texture2D> = None;
   174→                self.device.CreateTexture2D(&desc, None, Some(&mut staging))
   175→                    .context("Failed to create staging texture")?;
   176→                let staging = staging.context("Staging texture is None")?;
   177→
   178→                // Set high priority
   179→                let resource: IDXGIResource = staging.cast()?;
   180→                resource.SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM)?;
   181→
   182→                self.staging_texture = Some(staging);
   183→            }
   184→        }
   185→
   186→        Ok(self.staging_texture.as_ref().unwrap())
   187→    }
   188→
   189→    /// Acquire the next frame from the desktop
   190→    fn acquire_frame(&mut self, timeout_ms: u32) -> Result<Option<(ID3D11Texture2D, DXGI_OUTDUPL_FRAME_INFO)>> {
   191→        unsafe {
   192→            let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default();
   193→            let mut desktop_resource: Option<IDXGIResource> = None;
   194→
   195→            let result = self.duplication.AcquireNextFrame(
   196→                timeout_ms,
   197→                &mut frame_info,
   198→                &mut desktop_resource,
   199→            );
   200→
   201→            match result {
   202→                Ok(_) => {
   203→                    let resource = desktop_resource.context("Desktop resource is None")?;
   204→
   205→                    // Check if there's actually a new frame
   206→                    if frame_info.LastPresentTime == 0 {
   207→                        self.duplication.ReleaseFrame().ok();
   208→                        return Ok(None);
   209→                    }
   210→
   211→                    let texture: ID3D11Texture2D = resource.cast()
   212→                        .context("Failed to cast to ID3D11Texture2D")?;
   213→
   214→                    Ok(Some((texture, frame_info)))
   215→                }
   216→                Err(e) if e.code() == DXGI_ERROR_WAIT_TIMEOUT => {
   217→                    // No new frame available
   218→                    Ok(None)
   219→                }
   220→                Err(e) if e.code() == DXGI_ERROR_ACCESS_LOST => {
   221→                    // Desktop duplication was invalidated, need to recreate
   222→                    tracing::warn!("Desktop duplication access lost, will need to recreate");
   223→                    Err(anyhow::anyhow!("Access lost"))
   224→                }
   225→                Err(e) => {
   226→                    Err(e).context("Failed to acquire frame")
   227→                }
   228→            }
   229→        }
   230→    }
   231→
   232→    /// Copy frame data to CPU-accessible memory
   233→    fn copy_frame_data(&mut self, texture: &ID3D11Texture2D) -> Result<Vec<u8>> {
   234→        unsafe {
   235→            // Get or create staging texture
   236→            let staging = self.get_staging_texture(texture)?.clone();
   237→
   238→            // Copy from GPU texture to staging texture
   239→            self.context.CopyResource(&staging, texture);
   240→
   241→            // Map the staging texture for CPU read
   242→            let mut mapped = D3D11_MAPPED_SUBRESOURCE::default();
   243→            self.context
   244→                .Map(&staging, 0, D3D11_MAP_READ, 0, Some(&mut mapped))
   245→                .context("Failed to map staging texture")?;
   246→
   247→            // Copy pixel data
   248→            let src_pitch = mapped.RowPitch as usize;
   249→            let dst_pitch = (self.width * 4) as usize;
   250→            let height = self.height as usize;
   251→
   252→            let mut data = vec![0u8; dst_pitch * height];
   253→
   254→            let src_ptr = mapped.pData as *const u8;
   255→            for y in 0..height {
   256→                let src_row = src_ptr.add(y * src_pitch);
   257→                let dst_row = data.as_mut_ptr().add(y * dst_pitch);
   258→                ptr::copy_nonoverlapping(src_row, dst_row, dst_pitch);
   259→            }
   260→
   261→            // Unmap
   262→            self.context.Unmap(&staging, 0);
   263→
   264→            Ok(data)
   265→        }
   266→    }
   267→
   268→    /// Extract dirty rectangles from frame info
   269→    fn extract_dirty_rects(&self, _frame_info: &DXGI_OUTDUPL_FRAME_INFO) -> Option<Vec<DirtyRect>> {
   270→        // TODO: Implement dirty rectangle extraction using
   271→        // IDXGIOutputDuplication::GetFrameDirtyRects and GetFrameMoveRects
   272→        // For now, return None to indicate full frame update
   273→        None
   274→    }
   275→}
   276→
   277→impl Capturer for DxgiCapturer {
   278→    fn capture(&mut self) -> Result<Option<CapturedFrame>> {
   279→        // Try to acquire a frame with 100ms timeout
   280→        let frame_result = self.acquire_frame(100)?;
   281→
   282→        let (texture, frame_info) = match frame_result {
   283→            Some((t, f)) => (t, f),
   284→            None => return Ok(None), // No new frame
   285→        };
   286→
   287→        // Copy frame data to CPU memory
   288→        let data = self.copy_frame_data(&texture)?;
   289→
   290→        // Release the frame
   291→        unsafe {
   292→            self.duplication.ReleaseFrame().ok();
   293→        }
   294→
   295→        // Extract dirty rectangles if available
   296→        let dirty_rects = self.extract_dirty_rects(&frame_info);
   297→
   298→        Ok(Some(CapturedFrame {
   299→            width: self.width,
   300→            height: self.height,
   301→            data,
   302→            timestamp: Instant::now(),
   303→            display_id: self.display.id,
   304→            dirty_rects,
   305→        }))
   306→    }
   307→
   308→    fn display(&self) -> &Display {
   309→        &self.display
   310→    }
   311→
   312→    fn is_valid(&self) -> bool {
   313→        // Could check if duplication is still valid
   314→        true
   315→    }
   316→}
   317→
   318→impl Drop for DxgiCapturer {
   319→    fn drop(&mut self) {
   320→        // Release any held frame
   321→        unsafe {
   322→            self.duplication.ReleaseFrame().ok();
   323→        }
   324→    }
   325→}
   326→

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

//! DXGI Desktop Duplication screen capture
//!
//! Uses the Windows Desktop Duplication API (available on Windows 8+) for
//! high-performance, low-latency screen capture with hardware acceleration.
//!
//! Reference: RustDesk's scrap library implementation

use super::{CapturedFrame, Capturer, DirtyRect, Display};
use anyhow::{Context, Result};
use std::ptr;
use std::time::Instant;

use windows::Win32::Graphics::Direct3D::D3D_DRIVER_TYPE_UNKNOWN;
use windows::Win32::Graphics::Direct3D11::{
    D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D,
    D3D11_CPU_ACCESS_READ, D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC,
    D3D11_USAGE_STAGING, D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ,
};
use windows::Win32::Graphics::Dxgi::{
    CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory1, IDXGIOutput, IDXGIOutput1,
    IDXGIOutputDuplication, IDXGIResource, DXGI_ERROR_ACCESS_LOST,
    DXGI_ERROR_WAIT_TIMEOUT, DXGI_OUTDUPL_DESC, DXGI_OUTDUPL_FRAME_INFO,
    DXGI_RESOURCE_PRIORITY_MAXIMUM,
};
use windows::core::Interface;

/// DXGI Desktop Duplication capturer
pub struct DxgiCapturer {
    display: Display,
    device: ID3D11Device,
    context: ID3D11DeviceContext,
    duplication: IDXGIOutputDuplication,
    staging_texture: Option<ID3D11Texture2D>,
    width: u32,
    height: u32,
    last_frame: Option<Vec<u8>>,
}

impl DxgiCapturer {
    /// Create a new DXGI capturer for the specified display
    pub fn new(display: Display) -> Result<Self> {
        let (device, context, duplication, desc) = Self::create_duplication(&display)?;

        Ok(Self {
            display,
            device,
            context,
            duplication,
            staging_texture: None,
            width: desc.ModeDesc.Width,
            height: desc.ModeDesc.Height,
            last_frame: None,
        })
    }

    /// Create D3D device and output duplication
    fn create_duplication(
        display_info: &Display,
    ) -> Result<(ID3D11Device, ID3D11DeviceContext, IDXGIOutputDuplication, DXGI_OUTDUPL_DESC)> {
        unsafe {
            // Create DXGI factory
            let factory: IDXGIFactory1 = CreateDXGIFactory1()
                .context("Failed to create DXGI factory")?;

            // Find the adapter and output for this display
            let (adapter, output) = Self::find_adapter_output(&factory, display_info)?;

            // Create D3D11 device
            let mut device: Option<ID3D11Device> = None;
            let mut context: Option<ID3D11DeviceContext> = None;

            D3D11CreateDevice(
                &adapter,
                D3D_DRIVER_TYPE_UNKNOWN,
                None,
                Default::default(),
                None,
                D3D11_SDK_VERSION,
                Some(&mut device),
                None,
                Some(&mut context),
            )
            .context("Failed to create D3D11 device")?;

            let device = device.context("D3D11 device is None")?;
            let context = context.context("D3D11 context is None")?;

            // Get IDXGIOutput1 interface
            let output1: IDXGIOutput1 = output.cast()
                .context("Failed to get IDXGIOutput1 interface")?;

            // Create output duplication
            let duplication = output1.DuplicateOutput(&device)
                .context("Failed to create output duplication")?;

            // Get duplication description
            let desc = duplication.GetDesc();

            tracing::info!(
                "Created DXGI duplication: {}x{}, display: {}",
                desc.ModeDesc.Width,
                desc.ModeDesc.Height,
                display_info.name
            );

            Ok((device, context, duplication, desc))
        }
    }

    /// Find the adapter and output for the specified display
    fn find_adapter_output(
        factory: &IDXGIFactory1,
        display: &Display,
    ) -> Result<(IDXGIAdapter1, IDXGIOutput)> {
        unsafe {
            let mut adapter_idx = 0u32;

            loop {
                // Enumerate adapters
                let adapter: IDXGIAdapter1 = match factory.EnumAdapters1(adapter_idx) {
                    Ok(a) => a,
                    Err(_) => break,
                };

                let mut output_idx = 0u32;

                loop {
                    // Enumerate outputs for this adapter
                    let output: IDXGIOutput = match adapter.EnumOutputs(output_idx) {
                        Ok(o) => o,
                        Err(_) => break,
                    };

                    // Check if this is the display we want
                    let desc = output.GetDesc()?;

                    let name = String::from_utf16_lossy(
                        &desc.DeviceName[..desc.DeviceName.iter().position(|&c| c == 0).unwrap_or(desc.DeviceName.len())]
                    );

                    if name == display.name || desc.Monitor.0 as isize == display.handle {
                        return Ok((adapter, output));
                    }

                    output_idx += 1;
                }

                adapter_idx += 1;
            }

            // If we didn't find the specific display, use the first one
            let adapter: IDXGIAdapter1 = factory.EnumAdapters1(0)
                .context("No adapters found")?;
            let output: IDXGIOutput = adapter.EnumOutputs(0)
                .context("No outputs found")?;

            Ok((adapter, output))
        }
    }

    /// Create or get the staging texture for CPU access
    fn get_staging_texture(&mut self, src_texture: &ID3D11Texture2D) -> Result<&ID3D11Texture2D> {
        if self.staging_texture.is_none() {
            unsafe {
                let mut desc = D3D11_TEXTURE2D_DESC::default();
                src_texture.GetDesc(&mut desc);

                desc.Usage = D3D11_USAGE_STAGING;
                desc.BindFlags = Default::default();
                desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ.0 as u32;
                desc.MiscFlags = Default::default();

                let mut staging: Option<ID3D11Texture2D> = None;
                self.device.CreateTexture2D(&desc, None, Some(&mut staging))
                    .context("Failed to create staging texture")?;
                let staging = staging.context("Staging texture is None")?;

                // Set high priority
                let resource: IDXGIResource = staging.cast()?;
                resource.SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM)?;

                self.staging_texture = Some(staging);
            }
        }

        Ok(self.staging_texture.as_ref().unwrap())
    }

    /// Acquire the next frame from the desktop
    fn acquire_frame(&mut self, timeout_ms: u32) -> Result<Option<(ID3D11Texture2D, DXGI_OUTDUPL_FRAME_INFO)>> {
        unsafe {
            let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default();
            let mut desktop_resource: Option<IDXGIResource> = None;

            let result = self.duplication.AcquireNextFrame(
                timeout_ms,
                &mut frame_info,
                &mut desktop_resource,
            );

            match result {
                Ok(_) => {
                    let resource = desktop_resource.context("Desktop resource is None")?;

                    // Check if there's actually a new frame
                    if frame_info.LastPresentTime == 0 {
                        self.duplication.ReleaseFrame().ok();
                        return Ok(None);
                    }

                    let texture: ID3D11Texture2D = resource.cast()
                        .context("Failed to cast to ID3D11Texture2D")?;

                    Ok(Some((texture, frame_info)))
                }
                Err(e) if e.code() == DXGI_ERROR_WAIT_TIMEOUT => {
                    // No new frame available
                    Ok(None)
                }
                Err(e) if e.code() == DXGI_ERROR_ACCESS_LOST => {
                    // Desktop duplication was invalidated, need to recreate
                    tracing::warn!("Desktop duplication access lost, will need to recreate");
                    Err(anyhow::anyhow!("Access lost"))
                }
                Err(e) => {
                    Err(e).context("Failed to acquire frame")
                }
            }
        }
    }

    /// Copy frame data to CPU-accessible memory
    fn copy_frame_data(&mut self, texture: &ID3D11Texture2D) -> Result<Vec<u8>> {
        unsafe {
            // Get or create staging texture
            let staging = self.get_staging_texture(texture)?.clone();

            // Copy from GPU texture to staging texture
            self.context.CopyResource(&staging, texture);

            // Map the staging texture for CPU read
            let mut mapped = D3D11_MAPPED_SUBRESOURCE::default();
            self.context
                .Map(&staging, 0, D3D11_MAP_READ, 0, Some(&mut mapped))
                .context("Failed to map staging texture")?;

            // Copy pixel data
            let src_pitch = mapped.RowPitch as usize;
            let dst_pitch = (self.width * 4) as usize;
            let height = self.height as usize;

            let mut data = vec![0u8; dst_pitch * height];

            let src_ptr = mapped.pData as *const u8;
            for y in 0..height {
                let src_row = src_ptr.add(y * src_pitch);
                let dst_row = data.as_mut_ptr().add(y * dst_pitch);
                ptr::copy_nonoverlapping(src_row, dst_row, dst_pitch);
            }

            // Unmap
            self.context.Unmap(&staging, 0);

            Ok(data)
        }
    }

    /// Extract dirty rectangles from frame info
    fn extract_dirty_rects(&self, _frame_info: &DXGI_OUTDUPL_FRAME_INFO) -> Option<Vec<DirtyRect>> {
        // TODO: Implement dirty rectangle extraction using
        // IDXGIOutputDuplication::GetFrameDirtyRects and GetFrameMoveRects
        // For now, return None to indicate full frame update
        None
    }
}

impl Capturer for DxgiCapturer {
    fn capture(&mut self) -> Result<Option<CapturedFrame>> {
        // Try to acquire a frame with 100ms timeout
        let frame_result = self.acquire_frame(100)?;

        let (texture, frame_info) = match frame_result {
            Some((t, f)) => (t, f),
            None => return Ok(None), // No new frame
        };

        // Copy frame data to CPU memory
        let data = self.copy_frame_data(&texture)?;

        // Release the frame
        unsafe {
            self.duplication.ReleaseFrame().ok();
        }

        // Extract dirty rectangles if available
        let dirty_rects = self.extract_dirty_rects(&frame_info);

        Ok(Some(CapturedFrame {
            width: self.width,
            height: self.height,
            data,
            timestamp: Instant::now(),
            display_id: self.display.id,
            dirty_rects,
        }))
    }

    fn display(&self) -> &Display {
        &self.display
    }

    fn is_valid(&self) -> bool {
        // Could check if duplication is still valid
        true
    }
}

impl Drop for DxgiCapturer {
    fn drop(&mut self) {
        // Release any held frame
        unsafe {
            self.duplication.ReleaseFrame().ok();
        }
    }
}


     1→//! GuruConnect Server - WebSocket Relay Server
     2→//!
     3→//! Handles connections from both agents and dashboard viewers,
     4→//! relaying video frames and input events between them.
     5→
     6→mod config;
     7→mod relay;
     8→mod session;
     9→mod auth;
    10→mod api;
    11→mod db;
    12→
    13→pub mod proto {
    14→    include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
    15→}
    16→
    17→use anyhow::Result;
    18→use axum::{
    19→    Router,
    20→    routing::get,
    21→};
    22→use std::net::SocketAddr;
    23→use tower_http::cors::{Any, CorsLayer};
    24→use tower_http::trace::TraceLayer;
    25→use tracing::{info, Level};
    26→use tracing_subscriber::FmtSubscriber;
    27→
    28→#[tokio::main]
    29→async fn main() -> Result<()> {
    30→    // Initialize logging
    31→    let _subscriber = FmtSubscriber::builder()
    32→        .with_max_level(Level::INFO)
    33→        .with_target(true)
    34→        .init();
    35→
    36→    info!("GuruConnect Server v{}", env!("CARGO_PKG_VERSION"));
    37→
    38→    // Load configuration
    39→    let config = config::Config::load()?;
    40→    info!("Loaded configuration, listening on {}", config.listen_addr);
    41→
    42→    // Initialize database connection (optional for MVP)
    43→    // let db = db::init(&config.database_url).await?;
    44→
    45→    // Create session manager
    46→    let sessions = session::SessionManager::new();
    47→
    48→    // Build router
    49→    let app = Router::new()
    50→        // Health check
    51→        .route("/health", get(health))
    52→        // WebSocket endpoints
    53→        .route("/ws/agent", get(relay::agent_ws_handler))
    54→        .route("/ws/viewer", get(relay::viewer_ws_handler))
    55→        // REST API
    56→        .route("/api/sessions", get(api::list_sessions))
    57→        .route("/api/sessions/:id", get(api::get_session))
    58→        // State
    59→        .with_state(sessions)
    60→        // Middleware
    61→        .layer(TraceLayer::new_for_http())
    62→        .layer(
    63→            CorsLayer::new()
    64→                .allow_origin(Any)
    65→                .allow_methods(Any)
    66→                .allow_headers(Any),
    67→        );
    68→
    69→    // Start server
    70→    let addr: SocketAddr = config.listen_addr.parse()?;
    71→    let listener = tokio::net::TcpListener::bind(addr).await?;
    72→
    73→    info!("Server listening on {}", addr);
    74→
    75→    axum::serve(listener, app).await?;
    76→
    77→    Ok(())
    78→}
    79→
    80→async fn health() -> &'static str {
    81→    "OK"
    82→}
    83→

<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 Server - WebSocket Relay Server
//!
//! Handles connections from both agents and dashboard viewers,
//! relaying video frames and input events between them.

mod config;
mod relay;
mod session;
mod auth;
mod api;
mod db;

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

use anyhow::Result;
use axum::{
    Router,
    routing::get,
};
use std::net::SocketAddr;
use tower_http::cors::{Any, CorsLayer};
use tower_http::trace::TraceLayer;
use tracing::{info, Level};
use tracing_subscriber::FmtSubscriber;

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

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

    // Load configuration
    let config = config::Config::load()?;
    info!("Loaded configuration, listening on {}", config.listen_addr);

    // Initialize database connection (optional for MVP)
    // let db = db::init(&config.database_url).await?;

    // Create session manager
    let sessions = session::SessionManager::new();

    // Build router
    let app = Router::new()
        // Health check
        .route("/health", get(health))
        // WebSocket endpoints
        .route("/ws/agent", get(relay::agent_ws_handler))
        .route("/ws/viewer", get(relay::viewer_ws_handler))
        // REST API
        .route("/api/sessions", get(api::list_sessions))
        .route("/api/sessions/:id", get(api::get_session))
        // State
        .with_state(sessions)
        // Middleware
        .layer(TraceLayer::new_for_http())
        .layer(
            CorsLayer::new()
                .allow_origin(Any)
                .allow_methods(Any)
                .allow_headers(Any),
        );

    // Start server
    let addr: SocketAddr = config.listen_addr.parse()?;
    let listener = tokio::net::TcpListener::bind(addr).await?;

    info!("Server listening on {}", addr);

    axum::serve(listener, app).await?;

    Ok(())
}

async fn health() -> &'static str {
    "OK"
}


1→import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
     2→import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
     3→import { AuthProvider, useAuth } from "./hooks/useAuth";
     4→import { Layout } from "./components/Layout";
     5→import { Login } from "./pages/Login";
     6→import { Register } from "./pages/Register";
     7→import { Dashboard } from "./pages/Dashboard";
     8→import { Sessions } from "./pages/Sessions";
     9→import { SessionView } from "./pages/SessionView";
    10→import { Agents } from "./pages/Agents";
    11→import { Users } from "./pages/Users";
    12→import { Organizations } from "./pages/Organizations";
    13→import { ApiKeys } from "./pages/ApiKeys";
    14→import { Settings } from "./pages/Settings";
    15→
    16→const queryClient = new QueryClient({
    17→  defaultOptions: {
    18→    queries: {
    19→      staleTime: 1000 * 60,
    20→      retry: 1,
    21→    },
    22→  },
    23→});
    24→
    25→function ProtectedRoute({ children }: { children: React.ReactNode }) {
    26→  const { user, isLoading } = useAuth();
    27→
    28→  if (isLoading) {
    29→    return (
    30→

    31→
Loading...

    32→

    33→    );
    34→  }
    35→
    36→  if (!user) {
    37→    return <Navigate to="/login" replace />;
    38→  }
    39→
    40→  return <Layout>{children}</Layout>;
    41→}
    42→
    43→function AdminRoute({ children }: { children: React.ReactNode }) {
    44→  const { user, isLoading } = useAuth();
    45→
    46→  if (isLoading) {
    47→    return (
    48→

    49→
Loading...

    50→

    51→    );
    52→  }
    53→
    54→  if (!user) {
    55→    return <Navigate to="/login" replace />;
    56→  }
    57→
    58→  if (user.role !== "admin" && user.role !== "dev_admin") {
    59→    return <Navigate to="/" replace />;
    60→  }
    61→
    62→  return <Layout>{children}</Layout>;
    63→}
    64→
    65→function PublicRoute({ children }: { children: React.ReactNode }) {
    66→  const { user, isLoading } = useAuth();
    67→
    68→  if (isLoading) {
    69→    return (
    70→

    71→
Loading...

    72→

    73→    );
    74→  }
    75→
    76→  if (user) {
    77→    return <Navigate to="/" replace />;
    78→  }
    79→
    80→  return <>{children}</>;
    81→}
    82→
    83→function AppRoutes() {
    84→  return (
    85→    <Routes>
    86→      <Route
    87→        path="/login"
    88→        element={
    89→          <PublicRoute>
    90→            <Login />
    91→          </PublicRoute>
    92→        }
    93→      />
    94→      <Route
    95→        path="/register"
    96→        element={
    97→          <PublicRoute>
    98→            <Register />
    99→          </PublicRoute>
   100→        }
   101→      />
   102→      <Route
   103→        path="/"
   104→        element={
   105→          <ProtectedRoute>
   106→            <Dashboard />
   107→          </ProtectedRoute>
   108→        }
   109→      />
   110→      <Route
   111→        path="/sessions"
   112→        element={
   113→          <ProtectedRoute>
   114→            <Sessions />
   115→          </ProtectedRoute>
   116→        }
   117→      />
   118→      <Route
   119→        path="/sessions/:id"
   120→        element={
   121→          <ProtectedRoute>
   122→            <SessionView />
   123→          </ProtectedRoute>
   124→        }
   125→      />
   126→      <Route
   127→        path="/agents"
   128→        element={
   129→          <ProtectedRoute>
   130→            <Agents />
   131→          </ProtectedRoute>
   132→        }
   133→      />
   134→      <Route
   135→        path="/users"
   136→        element={
   137→          <AdminRoute>
   138→            <Users />
   139→          </AdminRoute>
   140→        }
   141→      />
   142→      <Route
   143→        path="/organizations"
   144→        element={
   145→          <AdminRoute>
   146→            <Organizations />
   147→          </AdminRoute>
   148→        }
   149→      />
   150→      <Route
   151→        path="/api-keys"
   152→        element={
   153→          <ProtectedRoute>
   154→            <ApiKeys />
   155→          </ProtectedRoute>
   156→        }
   157→      />
   158→      <Route
   159→        path="/settings"
   160→        element={
   161→          <ProtectedRoute>
   162→            <Settings />
   163→          </ProtectedRoute>
   164→        }
   165→      />
   166→      <Route path="*" element={<Navigate to="/" replace />} />
   167→    </Routes>
   168→  );
   169→}
   170→
   171→function App() {
   172→  return (
   173→    <QueryClientProvider client={queryClient}>
   174→      <BrowserRouter>
   175→        <AuthProvider>
   176→          <AppRoutes />
   177→        </AuthProvider>
   178→      </BrowserRouter>
   179→    </QueryClientProvider>
   180→  );
   181→}
   182→
   183→export default App;
   184→

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

import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { AuthProvider, useAuth } from "./hooks/useAuth";
import { Layout } from "./components/Layout";
import { Login } from "./pages/Login";
import { Register } from "./pages/Register";
import { Dashboard } from "./pages/Dashboard";
import { Sessions } from "./pages/Sessions";
import { SessionView } from "./pages/SessionView";
import { Agents } from "./pages/Agents";
import { Users } from "./pages/Users";
import { Organizations } from "./pages/Organizations";
import { ApiKeys } from "./pages/ApiKeys";
import { Settings } from "./pages/Settings";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60,
      retry: 1,
    },
  },
});

function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { user, isLoading } = useAuth();

  if (isLoading) {
    return (

Loading...

    );
  }

  if (!user) {
    return <Navigate to="/login" replace />;
  }

  return <Layout>{children}</Layout>;
}

function AdminRoute({ children }: { children: React.ReactNode }) {
  const { user, isLoading } = useAuth();

  if (isLoading) {
    return (

Loading...

    );
  }

  if (!user) {
    return <Navigate to="/login" replace />;
  }

  if (user.role !== "admin" && user.role !== "dev_admin") {
    return <Navigate to="/" replace />;
  }

  return <Layout>{children}</Layout>;
}

function PublicRoute({ children }: { children: React.ReactNode }) {
  const { user, isLoading } = useAuth();

  if (isLoading) {
    return (

Loading...

    );
  }

  if (user) {
    return <Navigate to="/" replace />;
  }

  return <>{children}</>;
}

function AppRoutes() {
  return (
    <Routes>
      <Route
        path="/login"
        element={
          <PublicRoute>
            <Login />
          </PublicRoute>
        }
      />
      <Route
        path="/register"
        element={
          <PublicRoute>
            <Register />
          </PublicRoute>
        }
      />
      <Route
        path="/"
        element={
          <ProtectedRoute>
            <Dashboard />
          </ProtectedRoute>
        }
      />
      <Route
        path="/sessions"
        element={
          <ProtectedRoute>
            <Sessions />
          </ProtectedRoute>
        }
      />
      <Route
        path="/sessions/:id"
        element={
          <ProtectedRoute>
            <SessionView />
          </ProtectedRoute>
        }
      />
      <Route
        path="/agents"
        element={
          <ProtectedRoute>
            <Agents />
          </ProtectedRoute>
        }
      />
      <Route
        path="/users"
        element={
          <AdminRoute>
            <Users />
          </AdminRoute>
        }
      />
      <Route
        path="/organizations"
        element={
          <AdminRoute>
            <Organizations />
          </AdminRoute>
        }
      />
      <Route
        path="/api-keys"
        element={
          <ProtectedRoute>
            <ApiKeys />
          </ProtectedRoute>
        }
      />
      <Route
        path="/settings"
        element={
          <ProtectedRoute>
            <Settings />
          </ProtectedRoute>
        }
      />
      <Route path="*" element={<Navigate to="/" replace />} />
    </Routes>
  );
}

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <BrowserRouter>
        <AuthProvider>
          <AppRoutes />
        </AuthProvider>
      </BrowserRouter>
    </QueryClientProvider>
  );
}

export default App;


1→import { useParams, useNavigate } from "react-router-dom";
     2→import { useQuery } from "@tanstack/react-query";
     3→import { useState, useCallback } from "react";
     4→import { sessionsApi } from "../api/client";
     5→import { RemoteViewer } from "../components/RemoteViewer";
     6→import { SessionControls } from "../components/SessionControls";
     7→import { Button } from "../components/Button";
     8→import { ArrowLeft, Maximize2, Minimize2 } from "lucide-react";
     9→import type { ConnectionStatus, QualitySettings } from "../types/protocol";
    10→
    11→export function SessionView() {
    12→  const { id } = useParams<{ id: string }>();
    13→  const navigate = useNavigate();
    14→  const [isFullscreen, setIsFullscreen] = useState(false);
    15→  const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>({
    16→    connected: false,
    17→  });
    18→  const [quality, setQuality] = useState<QualitySettings>({
    19→    preset: "auto",
    20→    codec: "auto",
    21→  });
    22→
    23→  const { data: session, isLoading } = useQuery({
    24→    queryKey: ["sessions", id],
    25→    queryFn: () => sessionsApi.get(id!).then((res) => res.data),
    26→    enabled: !!id,
    27→  });
    28→
    29→  const handleStatusChange = useCallback((status: ConnectionStatus) => {
    30→    setConnectionStatus(status);
    31→  }, []);
    32→
    33→  const handleQualityChange = useCallback((settings: QualitySettings) => {
    34→    setQuality(settings);
    35→  }, []);
    36→
    37→  const handleSpecialKey = useCallback((key: "ctrl-alt-del" | "lock-screen" | "print-screen") => {
    38→    console.log("Special key:", key);
    39→    // TODO: Send special key combo via WebSocket
    40→  }, []);
    41→
    42→  const handleDisconnect = useCallback(() => {
    43→    navigate("/sessions");
    44→  }, [navigate]);
    45→
    46→  const toggleFullscreen = useCallback(() => {
    47→    if (!document.fullscreenElement) {
    48→      document.documentElement.requestFullscreen();
    49→      setIsFullscreen(true);
    50→    } else {
    51→      document.exitFullscreen();
    52→      setIsFullscreen(false);
    53→    }
    54→  }, []);
    55→
    56→  // Get server URL from environment or current origin
    57→  const serverUrl = import.meta.env.VITE_WS_URL ||
    58→    `${window.location.protocol === "https:" ? "wss:" : "ws:"}//${window.location.host}`;
    59→
    60→  if (isLoading) {
    61→    return (
    62→

    63→
Loading session...

    64→

    65→    );
    66→  }
    67→
    68→  if (!session || !id) {
    69→    return (
    70→

    71→
Session not found

    72→        <Button onClick={() => navigate("/sessions")}>
    73→          <ArrowLeft className="h-4 w-4 mr-2" />
    74→          Back to Sessions
    75→        </Button>
    76→

    77→    );
    78→  }
    79→
    80→  return (
    81→

    82→      {/* Header */}
    83→

    84→

    85→          <Button variant="ghost" size="sm" onClick={() => navigate("/sessions")}>
    86→            <ArrowLeft className="h-4 w-4 mr-2" />
    87→            Back
    88→          </Button>
    89→

    90→
{session.agent_name}

    91→

    92→              {connectionStatus.connected ? (
    93→                Connected
    94→              ) : (
    95→                Disconnected
    96→              )}
    97→              {connectionStatus.fps !== undefined && ` • ${connectionStatus.fps} FPS`}
    98→              {connectionStatus.latencyMs !== undefined && ` • ${connectionStatus.latencyMs}ms`}
    99→

   100→

   101→

   102→        <Button variant="outline" size="sm" onClick={toggleFullscreen}>
   103→          {isFullscreen ? (
   104→            <>
   105→              <Minimize2 className="h-4 w-4 mr-2" />
   106→              Exit Fullscreen
   107→            </>
   108→          ) : (
   109→            <>
   110→              <Maximize2 className="h-4 w-4 mr-2" />
   111→              Fullscreen
   112→            </>
   113→          )}
   114→        </Button>
   115→

   116→
   117→      {/* Controls */}
   118→      <SessionControls
   119→        quality={quality}
   120→        onQualityChange={handleQualityChange}
   121→        onSpecialKey={handleSpecialKey}
   122→        onDisconnect={handleDisconnect}
   123→      />
   124→
   125→      {/* Remote Viewer */}
   126→

   127→        <RemoteViewer
   128→          serverUrl={serverUrl}
   129→          sessionId={id}
   130→          onStatusChange={handleStatusChange}
   131→          autoConnect={true}
   132→          showStatusBar={false}
   133→          className="max-w-full max-h-full"
   134→        />
   135→

   136→

   137→  );
   138→}
   139→

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

import { useParams, useNavigate } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { useState, useCallback } from "react";
import { sessionsApi } from "../api/client";
import { RemoteViewer } from "../components/RemoteViewer";
import { SessionControls } from "../components/SessionControls";
import { Button } from "../components/Button";
import { ArrowLeft, Maximize2, Minimize2 } from "lucide-react";
import type { ConnectionStatus, QualitySettings } from "../types/protocol";

export function SessionView() {
  const { id } = useParams<{ id: string }>();
  const navigate = useNavigate();
  const [isFullscreen, setIsFullscreen] = useState(false);
  const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>({
    connected: false,
  });
  const [quality, setQuality] = useState<QualitySettings>({
    preset: "auto",
    codec: "auto",
  });

  const { data: session, isLoading } = useQuery({
    queryKey: ["sessions", id],
    queryFn: () => sessionsApi.get(id!).then((res) => res.data),
    enabled: !!id,
  });

  const handleStatusChange = useCallback((status: ConnectionStatus) => {
    setConnectionStatus(status);
  }, []);

  const handleQualityChange = useCallback((settings: QualitySettings) => {
    setQuality(settings);
  }, []);

  const handleSpecialKey = useCallback((key: "ctrl-alt-del" | "lock-screen" | "print-screen") => {
    console.log("Special key:", key);
    // TODO: Send special key combo via WebSocket
  }, []);

  const handleDisconnect = useCallback(() => {
    navigate("/sessions");
  }, [navigate]);

  const toggleFullscreen = useCallback(() => {
    if (!document.fullscreenElement) {
      document.documentElement.requestFullscreen();
      setIsFullscreen(true);
    } else {
      document.exitFullscreen();
      setIsFullscreen(false);
    }
  }, []);

  // Get server URL from environment or current origin
  const serverUrl = import.meta.env.VITE_WS_URL ||
    `${window.location.protocol === "https:" ? "wss:" : "ws:"}//${window.location.host}`;

  if (isLoading) {
    return (

Loading session...

    );
  }

  if (!session || !id) {
    return (

Session not found

        <Button onClick={() => navigate("/sessions")}>
          <ArrowLeft className="h-4 w-4 mr-2" />
          Back to Sessions
        </Button>

    );
  }

  return (

      {/* Header */}

          <Button variant="ghost" size="sm" onClick={() => navigate("/sessions")}>
            <ArrowLeft className="h-4 w-4 mr-2" />
            Back
          </Button>

{session.agent_name}

              {connectionStatus.connected ? (
                Connected
              ) : (
                Disconnected
              )}
              {connectionStatus.fps !== undefined && ` • ${connectionStatus.fps} FPS`}
              {connectionStatus.latencyMs !== undefined && ` • ${connectionStatus.latencyMs}ms`}

        <Button variant="outline" size="sm" onClick={toggleFullscreen}>
          {isFullscreen ? (
            <>
              <Minimize2 className="h-4 w-4 mr-2" />
              Exit Fullscreen
            </>
          ) : (
            <>
              <Maximize2 className="h-4 w-4 mr-2" />
              Fullscreen
            </>
          )}
        </Button>

      {/* Controls */}
      <SessionControls
        quality={quality}
        onQualityChange={handleQualityChange}
        onSpecialKey={handleSpecialKey}
        onDisconnect={handleDisconnect}
      />

      {/* Remote Viewer */}

        <RemoteViewer
          serverUrl={serverUrl}
          sessionId={id}
          onStatusChange={handleStatusChange}
          autoConnect={true}
          showStatusBar={false}
          className="max-w-full max-h-full"
        />

  );
}


Let me read more files to understand the codebase better - especially the larger files and components.


<tool_use_error>File does not exist.</tool_use_error>


1→/**
     2→ * RemoteViewer Component
     3→ *
     4→ * Canvas-based remote desktop viewer that connects to a GuruConnect
     5→ * agent via the relay server. Handles frame rendering and input capture.
     6→ */
     7→
     8→import React, { useRef, useEffect, useCallback, useState } from 'react';
     9→import { useRemoteSession, createMouseEvent, createKeyEvent } from '../hooks/useRemoteSession';
    10→import type { VideoFrame, ConnectionStatus } from '../types/protocol';
    11→
    12→interface RemoteViewerProps {
    13→  serverUrl: string;
    14→  sessionId: string;
    15→  className?: string;
    16→  onStatusChange?: (status: ConnectionStatus) => void;
    17→  autoConnect?: boolean;
    18→  showStatusBar?: boolean;
    19→}
    20→
    21→export const RemoteViewer: React.FC<RemoteViewerProps> = ({
    22→  serverUrl,
    23→  sessionId,
    24→  className = '',
    25→  onStatusChange,
    26→  autoConnect = true,
    27→  showStatusBar = true,
    28→}) => {
    29→  const canvasRef = useRef<HTMLCanvasElement>(null);
    30→  const containerRef = useRef<HTMLDivElement>(null);
    31→  const ctxRef = useRef<CanvasRenderingContext2D | null>(null);
    32→
    33→  // Display dimensions from received frames
    34→  const [displaySize, setDisplaySize] = useState({ width: 1920, height: 1080 });
    35→
    36→  // Frame buffer for rendering
    37→  const frameBufferRef = useRef<ImageData | null>(null);
    38→
    39→  // Handle incoming video frames
    40→  const handleFrame = useCallback((frame: VideoFrame) => {
    41→    if (!frame.raw || !canvasRef.current) return;
    42→
    43→    const { width, height, data } = frame.raw;
    44→
    45→    // Update display size if changed
    46→    if (width !== displaySize.width || height !== displaySize.height) {
    47→      setDisplaySize({ width, height });
    48→    }
    49→
    50→    // Get or create context
    51→    if (!ctxRef.current) {
    52→      ctxRef.current = canvasRef.current.getContext('2d', {
    53→        alpha: false,
    54→        desynchronized: true,
    55→      });
    56→    }
    57→
    58→    const ctx = ctxRef.current;
    59→    if (!ctx) return;
    60→
    61→    // For MVP, we assume raw BGRA frames
    62→    // In production, handle compressed frames with fzstd
    63→    let frameData = data;
    64→
    65→    // Create or reuse ImageData
    66→    if (!frameBufferRef.current ||
    67→        frameBufferRef.current.width !== width ||
    68→        frameBufferRef.current.height !== height) {
    69→      frameBufferRef.current = ctx.createImageData(width, height);
    70→    }
    71→
    72→    const imageData = frameBufferRef.current;
    73→
    74→    // Convert BGRA to RGBA for canvas
    75→    const pixels = imageData.data;
    76→    const len = Math.min(frameData.length, pixels.length);
    77→
    78→    for (let i = 0; i < len; i += 4) {
    79→      pixels[i] = frameData[i + 2];     // R <- B
    80→      pixels[i + 1] = frameData[i + 1]; // G <- G
    81→      pixels[i + 2] = frameData[i];     // B <- R
    82→      pixels[i + 3] = 255;              // A (opaque)
    83→    }
    84→
    85→    // Draw to canvas
    86→    ctx.putImageData(imageData, 0, 0);
    87→  }, [displaySize]);
    88→
    89→  // Set up session
    90→  const { status, connect, disconnect, sendMouseEvent, sendKeyEvent } = useRemoteSession({
    91→    serverUrl,
    92→    sessionId,
    93→    onFrame: handleFrame,
    94→    onStatusChange,
    95→  });
    96→
    97→  // Auto-connect on mount
    98→  useEffect(() => {
    99→    if (autoConnect) {
   100→      connect();
   101→    }
   102→    return () => {
   103→      disconnect();
   104→    };
   105→  }, [autoConnect, connect, disconnect]);
   106→
   107→  // Update canvas size when display size changes
   108→  useEffect(() => {
   109→    if (canvasRef.current) {
   110→      canvasRef.current.width = displaySize.width;
   111→      canvasRef.current.height = displaySize.height;
   112→      // Reset context reference
   113→      ctxRef.current = null;
   114→      frameBufferRef.current = null;
   115→    }
   116→  }, [displaySize]);
   117→
   118→  // Get canvas rect for coordinate translation
   119→  const getCanvasRect = useCallback(() => {
   120→    return canvasRef.current?.getBoundingClientRect() ?? new DOMRect();
   121→  }, []);
   122→
   123→  // Mouse event handlers
   124→  const handleMouseMove = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
   125→    const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 0);
   126→    sendMouseEvent(event);
   127→  }, [getCanvasRect, displaySize, sendMouseEvent]);
   128→
   129→  const handleMouseDown = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
   130→    e.preventDefault();
   131→    const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 1);
   132→    sendMouseEvent(event);
   133→  }, [getCanvasRect, displaySize, sendMouseEvent]);
   134→
   135→  const handleMouseUp = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
   136→    const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 2);
   137→    sendMouseEvent(event);
   138→  }, [getCanvasRect, displaySize, sendMouseEvent]);
   139→
   140→  const handleWheel = useCallback((e: React.WheelEvent<HTMLCanvasElement>) => {
   141→    e.preventDefault();
   142→    const baseEvent = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 3);
   143→    sendMouseEvent({
   144→      ...baseEvent,
   145→      wheelDeltaX: Math.round(e.deltaX),
   146→      wheelDeltaY: Math.round(e.deltaY),
   147→    });
   148→  }, [getCanvasRect, displaySize, sendMouseEvent]);
   149→
   150→  const handleContextMenu = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
   151→    e.preventDefault(); // Prevent browser context menu
   152→  }, []);
   153→
   154→  // Keyboard event handlers
   155→  const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLCanvasElement>) => {
   156→    e.preventDefault();
   157→    const event = createKeyEvent(e, true);
   158→    sendKeyEvent(event);
   159→  }, [sendKeyEvent]);
   160→
   161→  const handleKeyUp = useCallback((e: React.KeyboardEvent<HTMLCanvasElement>) => {
   162→    e.preventDefault();
   163→    const event = createKeyEvent(e, false);
   164→    sendKeyEvent(event);
   165→  }, [sendKeyEvent]);
   166→
   167→  return (
   168→    <div ref={containerRef} className={`remote-viewer ${className}`}>
   169→      <canvas
   170→        ref={canvasRef}
   171→        tabIndex={0}
   172→        onMouseMove={handleMouseMove}
   173→        onMouseDown={handleMouseDown}
   174→        onMouseUp={handleMouseUp}
   175→        onWheel={handleWheel}
   176→        onContextMenu={handleContextMenu}
   177→        onKeyDown={handleKeyDown}
   178→        onKeyUp={handleKeyUp}
   179→        style={{
   180→          width: '100%',
   181→          height: 'auto',
   182→          aspectRatio: `${displaySize.width} / ${displaySize.height}`,
   183→          cursor: 'none', // Hide cursor, remote cursor is shown in frame
   184→          outline: 'none',
   185→          backgroundColor: '#1a1a1a',
   186→        }}
   187→      />
   188→
   189→      {showStatusBar && (
   190→

   199→
   200→            {status.connected ? (
   201→              Connected
   202→            ) : (
   203→              Disconnected
   204→            )}
   205→
   206→          {displaySize.width}x{displaySize.height}
   207→          {status.fps !== undefined && {status.fps} FPS}
   208→          {status.latencyMs !== undefined && {status.latencyMs}ms}
   209→

   210→      )}
   211→

   212→  );
   213→};
   214→
   215→export default RemoteViewer;
   216→

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

/**
 * RemoteViewer Component
 *
 * Canvas-based remote desktop viewer that connects to a GuruConnect
 * agent via the relay server. Handles frame rendering and input capture.
 */

import React, { useRef, useEffect, useCallback, useState } from 'react';
import { useRemoteSession, createMouseEvent, createKeyEvent } from '../hooks/useRemoteSession';
import type { VideoFrame, ConnectionStatus } from '../types/protocol';

interface RemoteViewerProps {
  serverUrl: string;
  sessionId: string;
  className?: string;
  onStatusChange?: (status: ConnectionStatus) => void;
  autoConnect?: boolean;
  showStatusBar?: boolean;
}

export const RemoteViewer: React.FC<RemoteViewerProps> = ({
  serverUrl,
  sessionId,
  className = '',
  onStatusChange,
  autoConnect = true,
  showStatusBar = true,
}) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  const ctxRef = useRef<CanvasRenderingContext2D | null>(null);

  // Display dimensions from received frames
  const [displaySize, setDisplaySize] = useState({ width: 1920, height: 1080 });

  // Frame buffer for rendering
  const frameBufferRef = useRef<ImageData | null>(null);

  // Handle incoming video frames
  const handleFrame = useCallback((frame: VideoFrame) => {
    if (!frame.raw || !canvasRef.current) return;

    const { width, height, data } = frame.raw;

    // Update display size if changed
    if (width !== displaySize.width || height !== displaySize.height) {
      setDisplaySize({ width, height });
    }

    // Get or create context
    if (!ctxRef.current) {
      ctxRef.current = canvasRef.current.getContext('2d', {
        alpha: false,
        desynchronized: true,
      });
    }

    const ctx = ctxRef.current;
    if (!ctx) return;

    // For MVP, we assume raw BGRA frames
    // In production, handle compressed frames with fzstd
    let frameData = data;

    // Create or reuse ImageData
    if (!frameBufferRef.current ||
        frameBufferRef.current.width !== width ||
        frameBufferRef.current.height !== height) {
      frameBufferRef.current = ctx.createImageData(width, height);
    }

    const imageData = frameBufferRef.current;

    // Convert BGRA to RGBA for canvas
    const pixels = imageData.data;
    const len = Math.min(frameData.length, pixels.length);

    for (let i = 0; i < len; i += 4) {
      pixels[i] = frameData[i + 2];     // R <- B
      pixels[i + 1] = frameData[i + 1]; // G <- G
      pixels[i + 2] = frameData[i];     // B <- R
      pixels[i + 3] = 255;              // A (opaque)
    }

    // Draw to canvas
    ctx.putImageData(imageData, 0, 0);
  }, [displaySize]);

  // Set up session
  const { status, connect, disconnect, sendMouseEvent, sendKeyEvent } = useRemoteSession({
    serverUrl,
    sessionId,
    onFrame: handleFrame,
    onStatusChange,
  });

  // Auto-connect on mount
  useEffect(() => {
    if (autoConnect) {
      connect();
    }
    return () => {
      disconnect();
    };
  }, [autoConnect, connect, disconnect]);

  // Update canvas size when display size changes
  useEffect(() => {
    if (canvasRef.current) {
      canvasRef.current.width = displaySize.width;
      canvasRef.current.height = displaySize.height;
      // Reset context reference
      ctxRef.current = null;
      frameBufferRef.current = null;
    }
  }, [displaySize]);

  // Get canvas rect for coordinate translation
  const getCanvasRect = useCallback(() => {
    return canvasRef.current?.getBoundingClientRect() ?? new DOMRect();
  }, []);

  // Mouse event handlers
  const handleMouseMove = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 0);
    sendMouseEvent(event);
  }, [getCanvasRect, displaySize, sendMouseEvent]);

  const handleMouseDown = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    e.preventDefault();
    const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 1);
    sendMouseEvent(event);
  }, [getCanvasRect, displaySize, sendMouseEvent]);

  const handleMouseUp = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 2);
    sendMouseEvent(event);
  }, [getCanvasRect, displaySize, sendMouseEvent]);

  const handleWheel = useCallback((e: React.WheelEvent<HTMLCanvasElement>) => {
    e.preventDefault();
    const baseEvent = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 3);
    sendMouseEvent({
      ...baseEvent,
      wheelDeltaX: Math.round(e.deltaX),
      wheelDeltaY: Math.round(e.deltaY),
    });
  }, [getCanvasRect, displaySize, sendMouseEvent]);

  const handleContextMenu = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    e.preventDefault(); // Prevent browser context menu
  }, []);

  // Keyboard event handlers
  const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLCanvasElement>) => {
    e.preventDefault();
    const event = createKeyEvent(e, true);
    sendKeyEvent(event);
  }, [sendKeyEvent]);

  const handleKeyUp = useCallback((e: React.KeyboardEvent<HTMLCanvasElement>) => {
    e.preventDefault();
    const event = createKeyEvent(e, false);
    sendKeyEvent(event);
  }, [sendKeyEvent]);

  return (
    <div ref={containerRef} className={`remote-viewer ${className}`}>
      <canvas
        ref={canvasRef}
        tabIndex={0}
        onMouseMove={handleMouseMove}
        onMouseDown={handleMouseDown}
        onMouseUp={handleMouseUp}
        onWheel={handleWheel}
        onContextMenu={handleContextMenu}
        onKeyDown={handleKeyDown}
        onKeyUp={handleKeyUp}
        style={{
          width: '100%',
          height: 'auto',
          aspectRatio: `${displaySize.width} / ${displaySize.height}`,
          cursor: 'none', // Hide cursor, remote cursor is shown in frame
          outline: 'none',
          backgroundColor: '#1a1a1a',
        }}
      />

      {showStatusBar && (

            {status.connected ? (
              Connected
            ) : (
              Disconnected
            )}

          {displaySize.width}x{displaySize.height}
          {status.fps !== undefined && {status.fps} FPS}
          {status.latencyMs !== undefined && {status.latencyMs}ms}

      )}

  );
};

export default RemoteViewer;