# Performance Optimizations Ideation Agent

You are a senior performance engineer. Your task is to analyze a codebase and identify performance bottlenecks, optimization opportunities, and efficiency improvements.

## Context

You have access to:
- Project index with file structure and dependencies
- Source code for analysis
- Package manifest with bundle dependencies
- Database schemas and queries (if applicable)
- Build configuration files
- 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 (`performance_optimizations`), use them to:
1. **Avoid duplicates**: Don't suggest optimizations that have already been implemented
2. **Build on success**: Prioritize optimization patterns that worked well in the past
3. **Learn from failures**: Avoid optimizations that previously caused regressions
4. **Leverage context**: Use historical profiling knowledge to identify high-impact areas

## Your Mission

Identify performance opportunities across these categories:

### 1. Bundle Size
- Large dependencies that could be replaced
- Unused exports and dead code
- Missing tree-shaking opportunities
- Duplicate dependencies
- Client-side code that should be server-side
- Unoptimized assets (images, fonts)

### 2. Runtime Performance
- Inefficient algorithms (O(n²) when O(n) possible)
- Unnecessary computations in hot paths
- Blocking operations on main thread
- Missing memoization opportunities
- Expensive regular expressions
- Synchronous I/O operations

### 3. Memory Usage
- Memory leaks (event listeners, closures, timers)
- Unbounded caches or collections
- Large object retention
- Missing cleanup in components
- Inefficient data structures

### 4. Database Performance
- N+1 query problems
- Missing indexes
- Unoptimized queries
- Over-fetching data
- Missing query result limits
- Inefficient joins

### 5. Network Optimization
- Missing request caching
- Unnecessary API calls
- Large payload sizes
- Missing compression
- Sequential requests that could be parallel
- Missing prefetching

### 6. Rendering Performance
- Unnecessary re-renders
- Missing React.memo / useMemo / useCallback
- Large component trees
- Missing virtualization for lists
- Layout thrashing
- Expensive CSS selectors

### 7. Caching Opportunities
- Repeated expensive computations
- Cacheable API responses
- Static asset caching
- Build-time computation opportunities
- Missing CDN usage

## Analysis Process

1. **Bundle Analysis**
   - Analyze package.json dependencies
   - Check for alternative lighter packages
   - Identify import patterns

2. **Code Complexity**
   - Find nested loops and recursion
   - Identify hot paths (frequently called code)
   - Check algorithmic complexity

3. **React/Component Analysis**
   - Find render patterns
   - Check prop drilling depth
   - Identify missing optimizations

4. **Database Queries**
   - Analyze query patterns
   - Check for N+1 issues
   - Review index usage

5. **Network Patterns**
   - Check API call patterns
   - Review payload sizes
   - Identify caching opportunities

## Output Format

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

```json
{
  "performance_optimizations": [
    {
      "id": "perf-001",
      "type": "performance_optimizations",
      "title": "Replace moment.js with date-fns for 90% bundle reduction",
      "description": "The project uses moment.js (300KB) for simple date formatting. date-fns is tree-shakeable and would reduce the date utility footprint to ~30KB.",
      "rationale": "moment.js is the largest dependency in the bundle and only 3 functions are used: format(), add(), and diff(). This is low-hanging fruit for bundle size reduction.",
      "category": "bundle_size",
      "impact": "high",
      "affectedAreas": ["src/utils/date.ts", "src/components/Calendar.tsx", "package.json"],
      "currentMetric": "Bundle includes 300KB for moment.js",
      "expectedImprovement": "~270KB reduction in bundle size, ~20% faster initial load",
      "implementation": "1. Install date-fns\n2. Replace moment imports with date-fns equivalents\n3. Update format strings to date-fns syntax\n4. Remove moment.js dependency",
      "tradeoffs": "date-fns format strings differ from moment.js, requiring updates",
      "estimatedEffort": "small"
    }
  ],
  "metadata": {
    "totalBundleSize": "2.4MB",
    "largestDependencies": ["react-dom", "moment", "lodash"],
    "filesAnalyzed": 145,
    "potentialSavings": "~400KB",
    "generatedAt": "2024-12-11T10:00:00Z"
  }
}
```

## Impact Classification

| Impact | Description | User Experience |
|--------|-------------|-----------------|
| high | Major improvement visible to users | Significantly faster load/interaction |
| medium | Noticeable improvement | Moderately improved responsiveness |
| low | Minor improvement | Subtle improvements, developer benefit |

## Common Anti-Patterns

### Bundle Size
```javascript
// BAD: Importing entire library
import _ from 'lodash';
_.map(arr, fn);

// GOOD: Import only what's needed
import map from 'lodash/map';
map(arr, fn);
```

### Runtime Performance
```javascript
// BAD: O(n²) when O(n) is possible
users.forEach(user => {
  const match = allPosts.find(p => p.userId === user.id);
});

// GOOD: O(n) with map lookup
const postsByUser = new Map(allPosts.map(p => [p.userId, p]));
users.forEach(user => {
  const match = postsByUser.get(user.id);
});
```

### React Rendering
```jsx
// BAD: New function on every render
<Button onClick={() => handleClick(id)} />

// GOOD: Memoized callback
const handleButtonClick = useCallback(() => handleClick(id), [id]);
<Button onClick={handleButtonClick} />
```

### Database Queries
```sql
-- BAD: N+1 query pattern
SELECT * FROM users;
-- Then for each user:
SELECT * FROM posts WHERE user_id = ?;

-- GOOD: Single query with JOIN
SELECT u.*, p.* FROM users u
LEFT JOIN posts p ON p.user_id = u.id;
```

## Effort Classification

| Effort | Time | Complexity |
|--------|------|------------|
| trivial | < 1 hour | Config change, simple replacement |
| small | 1-4 hours | Single file, straightforward refactor |
| medium | 4-16 hours | Multiple files, some complexity |
| large | 1-3 days | Architectural change, significant refactor |

## Guidelines

- **Measure First**: Suggest profiling before and after when possible
- **Quantify Impact**: Include expected improvements (%, ms, KB)
- **Consider Tradeoffs**: Note any downsides (complexity, maintenance)
- **Prioritize User Impact**: Focus on user-facing performance
- **Avoid Premature Optimization**: Don't suggest micro-optimizations

## Categories Explained

| Category | Focus | Tools |
|----------|-------|-------|
| bundle_size | JavaScript/CSS payload | webpack-bundle-analyzer |
| runtime | Execution speed | Chrome DevTools, profilers |
| memory | RAM usage | Memory profilers, heap snapshots |
| database | Query efficiency | EXPLAIN, query analyzers |
| network | HTTP performance | Network tab, Lighthouse |
| rendering | Paint/layout | React DevTools, Performance tab |
| caching | Data reuse | Cache-Control, service workers |

## Performance Budget Considerations

Suggest improvements that help meet common performance budgets:
- Time to Interactive: < 3.8s
- First Contentful Paint: < 1.8s
- Largest Contentful Paint: < 2.5s
- Total Blocking Time: < 200ms
- Bundle size: < 200KB gzipped (initial)

Remember: Performance optimization should be data-driven. The best optimizations are those that measurably improve user experience without adding maintenance burden.

---

**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\performance_optimizations_ideas.json
**Max Ideas**: 5

Generate up to 5 Performance Optimizations ideas.
Avoid duplicating features that are already planned (see ideation_context.json).
Output your ideas to performance_optimizations_ideas.json.


The user wants me to analyze the GuruConnect codebase for performance optimization opportunities. Let me start by reading the context files and understanding the project structure, then analyze the code for performance bottlenecks.

Let me first read the ideation context and project index to understand what's already planned and what the project looks like.


I'll analyze the GuruConnect codebase for performance optimization opportunities. Let me start by reading the context files and exploring the project 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


C:\Users\MikeSwanson\claude-projects\guru-connect\Cargo.toml
C:\Users\MikeSwanson\claude-projects\guru-connect\server\Cargo.toml
C:\Users\MikeSwanson\claude-projects\guru-connect\agent\Cargo.toml


Now I have a good view of the project structure. Let me read the key source files to analyze for performance opportunities:

1. Agent code (screen capture, encoding, transport)
2. Server code (relay, sessions)
3. Dashboard code (React frontend)
4. Cargo.toml files for dependencies

Let me read the main source files to find performance bottlenecks.


Let me read the key source files for performance analysis:


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


     1→//! WebSocket relay handlers
     2→//!
     3→//! Handles WebSocket connections from agents and viewers,
     4→//! relaying video frames and input events between them.
     5→
     6→use axum::{
     7→    extract::{
     8→        ws::{Message, WebSocket, WebSocketUpgrade},
     9→        Query, State,
    10→    },
    11→    response::IntoResponse,
    12→};
    13→use futures_util::{SinkExt, StreamExt};
    14→use prost::Message as ProstMessage;
    15→use serde::Deserialize;
    16→use tracing::{error, info, warn};
    17→
    18→use crate::proto;
    19→use crate::session::SessionManager;
    20→
    21→#[derive(Debug, Deserialize)]
    22→pub struct AgentParams {
    23→    agent_id: String,
    24→    #[serde(default)]
    25→    agent_name: Option<String>,
    26→}
    27→
    28→#[derive(Debug, Deserialize)]
    29→pub struct ViewerParams {
    30→    session_id: String,
    31→}
    32→
    33→/// WebSocket handler for agent connections
    34→pub async fn agent_ws_handler(
    35→    ws: WebSocketUpgrade,
    36→    State(sessions): State<SessionManager>,
    37→    Query(params): Query<AgentParams>,
    38→) -> impl IntoResponse {
    39→    let agent_id = params.agent_id;
    40→    let agent_name = params.agent_name.unwrap_or_else(|| agent_id.clone());
    41→
    42→    ws.on_upgrade(move |socket| handle_agent_connection(socket, sessions, agent_id, agent_name))
    43→}
    44→
    45→/// WebSocket handler for viewer connections
    46→pub async fn viewer_ws_handler(
    47→    ws: WebSocketUpgrade,
    48→    State(sessions): State<SessionManager>,
    49→    Query(params): Query<ViewerParams>,
    50→) -> impl IntoResponse {
    51→    let session_id = params.session_id;
    52→
    53→    ws.on_upgrade(move |socket| handle_viewer_connection(socket, sessions, session_id))
    54→}
    55→
    56→/// Handle an agent WebSocket connection
    57→async fn handle_agent_connection(
    58→    socket: WebSocket,
    59→    sessions: SessionManager,
    60→    agent_id: String,
    61→    agent_name: String,
    62→) {
    63→    info!("Agent connected: {} ({})", agent_name, agent_id);
    64→
    65→    // Register the agent and get channels
    66→    let (session_id, frame_tx, mut input_rx) = sessions.register_agent(agent_id.clone(), agent_name.clone()).await;
    67→
    68→    info!("Session created: {}", session_id);
    69→
    70→    let (mut ws_sender, mut ws_receiver) = socket.split();
    71→
    72→    // Task to forward input events from viewers to agent
    73→    let input_forward = tokio::spawn(async move {
    74→        while let Some(input_data) = input_rx.recv().await {
    75→            if ws_sender.send(Message::Binary(input_data.into())).await.is_err() {
    76→                break;
    77→            }
    78→        }
    79→    });
    80→
    81→    // Main loop: receive frames from agent and broadcast to viewers
    82→    while let Some(msg) = ws_receiver.next().await {
    83→        match msg {
    84→            Ok(Message::Binary(data)) => {
    85→                // Try to decode as protobuf message
    86→                match proto::Message::decode(data.as_ref()) {
    87→                    Ok(proto_msg) => {
    88→                        if let Some(proto::message::Payload::VideoFrame(_)) = &proto_msg.payload {
    89→                            // Broadcast frame to all viewers
    90→                            let _ = frame_tx.send(data.to_vec());
    91→                        }
    92→                    }
    93→                    Err(e) => {
    94→                        warn!("Failed to decode agent message: {}", e);
    95→                    }
    96→                }
    97→            }
    98→            Ok(Message::Close(_)) => {
    99→                info!("Agent disconnected: {}", agent_id);
   100→                break;
   101→            }
   102→            Ok(Message::Ping(data)) => {
   103→                // Pong is handled automatically by axum
   104→                let _ = data;
   105→            }
   106→            Ok(_) => {}
   107→            Err(e) => {
   108→                error!("WebSocket error from agent {}: {}", agent_id, e);
   109→                break;
   110→            }
   111→        }
   112→    }
   113→
   114→    // Cleanup
   115→    input_forward.abort();
   116→    sessions.remove_session(session_id).await;
   117→    info!("Session {} ended", session_id);
   118→}
   119→
   120→/// Handle a viewer WebSocket connection
   121→async fn handle_viewer_connection(
   122→    socket: WebSocket,
   123→    sessions: SessionManager,
   124→    session_id_str: String,
   125→) {
   126→    // Parse session ID
   127→    let session_id = match uuid::Uuid::parse_str(&session_id_str) {
   128→        Ok(id) => id,
   129→        Err(_) => {
   130→            warn!("Invalid session ID: {}", session_id_str);
   131→            return;
   132→        }
   133→    };
   134→
   135→    // Join the session
   136→    let (mut frame_rx, input_tx) = match sessions.join_session(session_id).await {
   137→        Some(channels) => channels,
   138→        None => {
   139→            warn!("Session not found: {}", session_id);
   140→            return;
   141→        }
   142→    };
   143→
   144→    info!("Viewer joined session: {}", session_id);
   145→
   146→    let (mut ws_sender, mut ws_receiver) = socket.split();
   147→
   148→    // Task to forward frames from agent to this viewer
   149→    let frame_forward = tokio::spawn(async move {
   150→        while let Ok(frame_data) = frame_rx.recv().await {
   151→            if ws_sender.send(Message::Binary(frame_data.into())).await.is_err() {
   152→                break;
   153→            }
   154→        }
   155→    });
   156→
   157→    // Main loop: receive input from viewer and forward to agent
   158→    while let Some(msg) = ws_receiver.next().await {
   159→        match msg {
   160→            Ok(Message::Binary(data)) => {
   161→                // Try to decode as protobuf message
   162→                match proto::Message::decode(data.as_ref()) {
   163→                    Ok(proto_msg) => {
   164→                        match &proto_msg.payload {
   165→                            Some(proto::message::Payload::MouseEvent(_)) |
   166→                            Some(proto::message::Payload::KeyEvent(_)) => {
   167→                                // Forward input to agent
   168→                                let _ = input_tx.send(data.to_vec()).await;
   169→                            }
   170→                            _ => {}
   171→                        }
   172→                    }
   173→                    Err(e) => {
   174→                        warn!("Failed to decode viewer message: {}", e);
   175→                    }
   176→                }
   177→            }
   178→            Ok(Message::Close(_)) => {
   179→                info!("Viewer disconnected from session: {}", session_id);
   180→                break;
   181→            }
   182→            Ok(_) => {}
   183→            Err(e) => {
   184→                error!("WebSocket error from viewer: {}", e);
   185→                break;
   186→            }
   187→        }
   188→    }
   189→
   190→    // Cleanup
   191→    frame_forward.abort();
   192→    sessions.leave_session(session_id).await;
   193→    info!("Viewer left session: {}", session_id);
   194→}
   195→

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

//! WebSocket relay handlers
//!
//! Handles WebSocket connections from agents and viewers,
//! relaying video frames and input events between them.

use axum::{
    extract::{
        ws::{Message, WebSocket, WebSocketUpgrade},
        Query, State,
    },
    response::IntoResponse,
};
use futures_util::{SinkExt, StreamExt};
use prost::Message as ProstMessage;
use serde::Deserialize;
use tracing::{error, info, warn};

use crate::proto;
use crate::session::SessionManager;

#[derive(Debug, Deserialize)]
pub struct AgentParams {
    agent_id: String,
    #[serde(default)]
    agent_name: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct ViewerParams {
    session_id: String,
}

/// WebSocket handler for agent connections
pub async fn agent_ws_handler(
    ws: WebSocketUpgrade,
    State(sessions): State<SessionManager>,
    Query(params): Query<AgentParams>,
) -> impl IntoResponse {
    let agent_id = params.agent_id;
    let agent_name = params.agent_name.unwrap_or_else(|| agent_id.clone());

    ws.on_upgrade(move |socket| handle_agent_connection(socket, sessions, agent_id, agent_name))
}

/// WebSocket handler for viewer connections
pub async fn viewer_ws_handler(
    ws: WebSocketUpgrade,
    State(sessions): State<SessionManager>,
    Query(params): Query<ViewerParams>,
) -> impl IntoResponse {
    let session_id = params.session_id;

    ws.on_upgrade(move |socket| handle_viewer_connection(socket, sessions, session_id))
}

/// Handle an agent WebSocket connection
async fn handle_agent_connection(
    socket: WebSocket,
    sessions: SessionManager,
    agent_id: String,
    agent_name: String,
) {
    info!("Agent connected: {} ({})", agent_name, agent_id);

    // Register the agent and get channels
    let (session_id, frame_tx, mut input_rx) = sessions.register_agent(agent_id.clone(), agent_name.clone()).await;

    info!("Session created: {}", session_id);

    let (mut ws_sender, mut ws_receiver) = socket.split();

    // Task to forward input events from viewers to agent
    let input_forward = tokio::spawn(async move {
        while let Some(input_data) = input_rx.recv().await {
            if ws_sender.send(Message::Binary(input_data.into())).await.is_err() {
                break;
            }
        }
    });

    // Main loop: receive frames from agent and broadcast to viewers
    while let Some(msg) = ws_receiver.next().await {
        match msg {
            Ok(Message::Binary(data)) => {
                // Try to decode as protobuf message
                match proto::Message::decode(data.as_ref()) {
                    Ok(proto_msg) => {
                        if let Some(proto::message::Payload::VideoFrame(_)) = &proto_msg.payload {
                            // Broadcast frame to all viewers
                            let _ = frame_tx.send(data.to_vec());
                        }
                    }
                    Err(e) => {
                        warn!("Failed to decode agent message: {}", e);
                    }
                }
            }
            Ok(Message::Close(_)) => {
                info!("Agent disconnected: {}", agent_id);
                break;
            }
            Ok(Message::Ping(data)) => {
                // Pong is handled automatically by axum
                let _ = data;
            }
            Ok(_) => {}
            Err(e) => {
                error!("WebSocket error from agent {}: {}", agent_id, e);
                break;
            }
        }
    }

    // Cleanup
    input_forward.abort();
    sessions.remove_session(session_id).await;
    info!("Session {} ended", session_id);
}

/// Handle a viewer WebSocket connection
async fn handle_viewer_connection(
    socket: WebSocket,
    sessions: SessionManager,
    session_id_str: String,
) {
    // Parse session ID
    let session_id = match uuid::Uuid::parse_str(&session_id_str) {
        Ok(id) => id,
        Err(_) => {
            warn!("Invalid session ID: {}", session_id_str);
            return;
        }
    };

    // Join the session
    let (mut frame_rx, input_tx) = match sessions.join_session(session_id).await {
        Some(channels) => channels,
        None => {
            warn!("Session not found: {}", session_id);
            return;
        }
    };

    info!("Viewer joined session: {}", session_id);

    let (mut ws_sender, mut ws_receiver) = socket.split();

    // Task to forward frames from agent to this viewer
    let frame_forward = tokio::spawn(async move {
        while let Ok(frame_data) = frame_rx.recv().await {
            if ws_sender.send(Message::Binary(frame_data.into())).await.is_err() {
                break;
            }
        }
    });

    // Main loop: receive input from viewer and forward to agent
    while let Some(msg) = ws_receiver.next().await {
        match msg {
            Ok(Message::Binary(data)) => {
                // Try to decode as protobuf message
                match proto::Message::decode(data.as_ref()) {
                    Ok(proto_msg) => {
                        match &proto_msg.payload {
                            Some(proto::message::Payload::MouseEvent(_)) |
                            Some(proto::message::Payload::KeyEvent(_)) => {
                                // Forward input to agent
                                let _ = input_tx.send(data.to_vec()).await;
                            }
                            _ => {}
                        }
                    }
                    Err(e) => {
                        warn!("Failed to decode viewer message: {}", e);
                    }
                }
            }
            Ok(Message::Close(_)) => {
                info!("Viewer disconnected from session: {}", session_id);
                break;
            }
            Ok(_) => {}
            Err(e) => {
                error!("WebSocket error from viewer: {}", e);
                break;
            }
        }
    }

    // Cleanup
    frame_forward.abort();
    sessions.leave_session(session_id).await;
    info!("Viewer left session: {}", session_id);
}


     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→[target.'cfg(windows)'.dependencies]
    55→# Windows APIs for screen capture and input
    56→windows = { version = "0.58", features = [
    57→    "Win32_Foundation",
    58→    "Win32_Graphics_Gdi",
    59→    "Win32_Graphics_Dxgi",
    60→    "Win32_Graphics_Dxgi_Common",
    61→    "Win32_Graphics_Direct3D",
    62→    "Win32_Graphics_Direct3D11",
    63→    "Win32_UI_Input_KeyboardAndMouse",
    64→    "Win32_UI_WindowsAndMessaging",
    65→    "Win32_System_LibraryLoader",
    66→    "Win32_System_Threading",
    67→    "Win32_Security",
    68→]}
    69→
    70→# Windows service support
    71→windows-service = "0.7"
    72→
    73→[build-dependencies]
    74→prost-build = "0.13"
    75→
    76→[profile.release]
    77→lto = true
    78→codegen-units = 1
    79→opt-level = "z"
    80→strip = true
    81→panic = "abort"
    82→

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

[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_Security",
]}

# Windows service support
windows-service = "0.7"

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

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


     1→[package]
     2→name = "guruconnect-server"
     3→version = "0.1.0"
     4→edition = "2021"
     5→authors = ["AZ Computer Guru"]
     6→description = "GuruConnect Remote Desktop Relay Server"
     7→
     8→[dependencies]
     9→# Async runtime
    10→tokio = { version = "1", features = ["full", "sync", "time", "rt-multi-thread", "macros"] }
    11→
    12→# Web framework
    13→axum = { version = "0.7", features = ["ws", "macros"] }
    14→tower = "0.5"
    15→tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip"] }
    16→
    17→# WebSocket
    18→futures-util = "0.3"
    19→
    20→# Database
    21→sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "uuid", "chrono", "json"] }
    22→
    23→# Protocol (protobuf)
    24→prost = "0.13"
    25→prost-types = "0.13"
    26→bytes = "1"
    27→
    28→# Serialization
    29→serde = { version = "1", features = ["derive"] }
    30→serde_json = "1"
    31→
    32→# Logging
    33→tracing = "0.1"
    34→tracing-subscriber = { version = "0.3", features = ["env-filter"] }
    35→
    36→# Error handling
    37→anyhow = "1"
    38→thiserror = "1"
    39→
    40→# Configuration
    41→toml = "0.8"
    42→
    43→# Auth
    44→jsonwebtoken = "9"
    45→argon2 = "0.5"
    46→
    47→# Crypto
    48→ring = "0.17"
    49→
    50→# UUID
    51→uuid = { version = "1", features = ["v4", "serde"] }
    52→
    53→# Time
    54→chrono = { version = "0.4", features = ["serde"] }
    55→
    56→[build-dependencies]
    57→prost-build = "0.13"
    58→
    59→[profile.release]
    60→lto = true
    61→codegen-units = 1
    62→strip = true
    63→

<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-server"
version = "0.1.0"
edition = "2021"
authors = ["AZ Computer Guru"]
description = "GuruConnect Remote Desktop Relay Server"

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

# Web framework
axum = { version = "0.7", features = ["ws", "macros"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip"] }

# WebSocket
futures-util = "0.3"

# Database
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "uuid", "chrono", "json"] }

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

# Auth
jsonwebtoken = "9"
argon2 = "0.5"

# Crypto
ring = "0.17"

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

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

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

[profile.release]
lto = true
codegen-units = 1
strip = true


I have a good understanding of the project now. Let me look at more files to understand more potential performance bottlenecks:

1. Dashboard frontend code (React)
2. Session management
3. The capture module
4. Transport websocket

Let me read more files to find additional performance opportunities.


Let me read more files to analyze the frontend dashboard and additional backend code:


     1→{
     2→  "name": "guruconnect-dashboard",
     3→  "private": true,
     4→  "version": "0.1.0",
     5→  "type": "module",
     6→  "description": "GuruConnect Remote Desktop Admin Dashboard",
     7→  "author": "AZ Computer Guru",
     8→  "scripts": {
     9→    "dev": "vite",
    10→    "build": "tsc -b && vite build",
    11→    "lint": "eslint .",
    12→    "preview": "vite preview"
    13→  },
    14→  "dependencies": {
    15→    "@tanstack/react-query": "^5.90.12",
    16→    "axios": "^1.13.2",
    17→    "clsx": "^2.1.1",
    18→    "fzstd": "^0.1.1",
    19→    "lucide-react": "^0.561.0",
    20→    "react": "^19.2.0",
    21→    "react-dom": "^19.2.0",
    22→    "react-router-dom": "^7.10.1",
    23→    "tailwind-merge": "^3.4.0"
    24→  },
    25→  "devDependencies": {
    26→    "@eslint/js": "^9.39.1",
    27→    "@tailwindcss/vite": "^4.1.18",
    28→    "@types/node": "^24.10.1",
    29→    "@types/react": "^19.2.5",
    30→    "@types/react-dom": "^19.2.3",
    31→    "@vitejs/plugin-react": "^5.1.1",
    32→    "autoprefixer": "^10.4.22",
    33→    "eslint": "^9.39.1",
    34→    "eslint-plugin-react-hooks": "^7.0.1",
    35→    "eslint-plugin-react-refresh": "^0.4.24",
    36→    "globals": "^16.5.0",
    37→    "postcss": "^8.5.6",
    38→    "tailwindcss": "^4.1.18",
    39→    "typescript": "~5.9.3",
    40→    "typescript-eslint": "^8.46.4",
    41→    "vite": "^7.2.4"
    42→  }
    43→}
    44→

<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→//! Session management for GuruConnect
     2→//!
     3→//! Manages active remote desktop sessions, tracking which agents
     4→//! are connected and which viewers are watching them.
     5→
     6→use std::collections::HashMap;
     7→use std::sync::Arc;
     8→use tokio::sync::{broadcast, RwLock};
     9→use uuid::Uuid;
    10→
    11→/// Unique identifier for a session
    12→pub type SessionId = Uuid;
    13→
    14→/// Unique identifier for an agent
    15→pub type AgentId = String;
    16→
    17→/// Session state
    18→#[derive(Debug, Clone)]
    19→pub struct Session {
    20→    pub id: SessionId,
    21→    pub agent_id: AgentId,
    22→    pub agent_name: String,
    23→    pub started_at: chrono::DateTime<chrono::Utc>,
    24→    pub viewer_count: usize,
    25→}
    26→
    27→/// Channel for sending frames from agent to viewers
    28→pub type FrameSender = broadcast::Sender<Vec<u8>>;
    29→pub type FrameReceiver = broadcast::Receiver<Vec<u8>>;
    30→
    31→/// Channel for sending input events from viewer to agent
    32→pub type InputSender = tokio::sync::mpsc::Sender<Vec<u8>>;
    33→pub type InputReceiver = tokio::sync::mpsc::Receiver<Vec<u8>>;
    34→
    35→/// Internal session data with channels
    36→struct SessionData {
    37→    info: Session,
    38→    /// Channel for video frames (agent -> viewers)
    39→    frame_tx: FrameSender,
    40→    /// Channel for input events (viewer -> agent)
    41→    input_tx: InputSender,
    42→    input_rx: Option<InputReceiver>,
    43→}
    44→
    45→/// Manages all active sessions
    46→#[derive(Clone)]
    47→pub struct SessionManager {
    48→    sessions: Arc<RwLock<HashMap<SessionId, SessionData>>>,
    49→    agents: Arc<RwLock<HashMap<AgentId, SessionId>>>,
    50→}
    51→
    52→impl SessionManager {
    53→    pub fn new() -> Self {
    54→        Self {
    55→            sessions: Arc::new(RwLock::new(HashMap::new())),
    56→            agents: Arc::new(RwLock::new(HashMap::new())),
    57→        }
    58→    }
    59→
    60→    /// Register a new agent and create a session
    61→    pub async fn register_agent(&self, agent_id: AgentId, agent_name: String) -> (SessionId, FrameSender, InputReceiver) {
    62→        let session_id = Uuid::new_v4();
    63→
    64→        // Create channels
    65→        let (frame_tx, _) = broadcast::channel(16); // Buffer 16 frames
    66→        let (input_tx, input_rx) = tokio::sync::mpsc::channel(64); // Buffer 64 input events
    67→
    68→        let session = Session {
    69→            id: session_id,
    70→            agent_id: agent_id.clone(),
    71→            agent_name,
    72→            started_at: chrono::Utc::now(),
    73→            viewer_count: 0,
    74→        };
    75→
    76→        let session_data = SessionData {
    77→            info: session,
    78→            frame_tx: frame_tx.clone(),
    79→            input_tx,
    80→            input_rx: None, // Will be taken by the agent handler
    81→        };
    82→
    83→        let mut sessions = self.sessions.write().await;
    84→        sessions.insert(session_id, session_data);
    85→
    86→        let mut agents = self.agents.write().await;
    87→        agents.insert(agent_id, session_id);
    88→
    89→        (session_id, frame_tx, input_rx)
    90→    }
    91→
    92→    /// Get a session by agent ID
    93→    pub async fn get_session_by_agent(&self, agent_id: &str) -> Option<Session> {
    94→        let agents = self.agents.read().await;
    95→        let session_id = agents.get(agent_id)?;
    96→
    97→        let sessions = self.sessions.read().await;
    98→        sessions.get(session_id).map(|s| s.info.clone())
    99→    }
   100→
   101→    /// Get a session by session ID
   102→    pub async fn get_session(&self, session_id: SessionId) -> Option<Session> {
   103→        let sessions = self.sessions.read().await;
   104→        sessions.get(&session_id).map(|s| s.info.clone())
   105→    }
   106→
   107→    /// Join a session as a viewer
   108→    pub async fn join_session(&self, session_id: SessionId) -> Option<(FrameReceiver, InputSender)> {
   109→        let mut sessions = self.sessions.write().await;
   110→        let session_data = sessions.get_mut(&session_id)?;
   111→
   112→        session_data.info.viewer_count += 1;
   113→
   114→        let frame_rx = session_data.frame_tx.subscribe();
   115→        let input_tx = session_data.input_tx.clone();
   116→
   117→        Some((frame_rx, input_tx))
   118→    }
   119→
   120→    /// Leave a session as a viewer
   121→    pub async fn leave_session(&self, session_id: SessionId) {
   122→        let mut sessions = self.sessions.write().await;
   123→        if let Some(session_data) = sessions.get_mut(&session_id) {
   124→            session_data.info.viewer_count = session_data.info.viewer_count.saturating_sub(1);
   125→        }
   126→    }
   127→
   128→    /// Remove a session (when agent disconnects)
   129→    pub async fn remove_session(&self, session_id: SessionId) {
   130→        let mut sessions = self.sessions.write().await;
   131→        if let Some(session_data) = sessions.remove(&session_id) {
   132→            let mut agents = self.agents.write().await;
   133→            agents.remove(&session_data.info.agent_id);
   134→        }
   135→    }
   136→
   137→    /// List all active sessions
   138→    pub async fn list_sessions(&self) -> Vec<Session> {
   139→        let sessions = self.sessions.read().await;
   140→        sessions.values().map(|s| s.info.clone()).collect()
   141→    }
   142→}
   143→
   144→impl Default for SessionManager {
   145→    fn default() -> Self {
   146→        Self::new()
   147→    }
   148→}
   149→

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

//! Session management for GuruConnect
//!
//! Manages active remote desktop sessions, tracking which agents
//! are connected and which viewers are watching them.

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{broadcast, RwLock};
use uuid::Uuid;

/// Unique identifier for a session
pub type SessionId = Uuid;

/// Unique identifier for an agent
pub type AgentId = String;

/// Session state
#[derive(Debug, Clone)]
pub struct Session {
    pub id: SessionId,
    pub agent_id: AgentId,
    pub agent_name: String,
    pub started_at: chrono::DateTime<chrono::Utc>,
    pub viewer_count: usize,
}

/// Channel for sending frames from agent to viewers
pub type FrameSender = broadcast::Sender<Vec<u8>>;
pub type FrameReceiver = broadcast::Receiver<Vec<u8>>;

/// Channel for sending input events from viewer to agent
pub type InputSender = tokio::sync::mpsc::Sender<Vec<u8>>;
pub type InputReceiver = tokio::sync::mpsc::Receiver<Vec<u8>>;

/// Internal session data with channels
struct SessionData {
    info: Session,
    /// Channel for video frames (agent -> viewers)
    frame_tx: FrameSender,
    /// Channel for input events (viewer -> agent)
    input_tx: InputSender,
    input_rx: Option<InputReceiver>,
}

/// Manages all active sessions
#[derive(Clone)]
pub struct SessionManager {
    sessions: Arc<RwLock<HashMap<SessionId, SessionData>>>,
    agents: Arc<RwLock<HashMap<AgentId, SessionId>>>,
}

impl SessionManager {
    pub fn new() -> Self {
        Self {
            sessions: Arc::new(RwLock::new(HashMap::new())),
            agents: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    /// Register a new agent and create a session
    pub async fn register_agent(&self, agent_id: AgentId, agent_name: String) -> (SessionId, FrameSender, InputReceiver) {
        let session_id = Uuid::new_v4();

        // Create channels
        let (frame_tx, _) = broadcast::channel(16); // Buffer 16 frames
        let (input_tx, input_rx) = tokio::sync::mpsc::channel(64); // Buffer 64 input events

        let session = Session {
            id: session_id,
            agent_id: agent_id.clone(),
            agent_name,
            started_at: chrono::Utc::now(),
            viewer_count: 0,
        };

        let session_data = SessionData {
            info: session,
            frame_tx: frame_tx.clone(),
            input_tx,
            input_rx: None, // Will be taken by the agent handler
        };

        let mut sessions = self.sessions.write().await;
        sessions.insert(session_id, session_data);

        let mut agents = self.agents.write().await;
        agents.insert(agent_id, session_id);

        (session_id, frame_tx, input_rx)
    }

    /// Get a session by agent ID
    pub async fn get_session_by_agent(&self, agent_id: &str) -> Option<Session> {
        let agents = self.agents.read().await;
        let session_id = agents.get(agent_id)?;

        let sessions = self.sessions.read().await;
        sessions.get(session_id).map(|s| s.info.clone())
    }

    /// Get a session by session ID
    pub async fn get_session(&self, session_id: SessionId) -> Option<Session> {
        let sessions = self.sessions.read().await;
        sessions.get(&session_id).map(|s| s.info.clone())
    }

    /// Join a session as a viewer
    pub async fn join_session(&self, session_id: SessionId) -> Option<(FrameReceiver, InputSender)> {
        let mut sessions = self.sessions.write().await;
        let session_data = sessions.get_mut(&session_id)?;

        session_data.info.viewer_count += 1;

        let frame_rx = session_data.frame_tx.subscribe();
        let input_tx = session_data.input_tx.clone();

        Some((frame_rx, input_tx))
    }

    /// Leave a session as a viewer
    pub async fn leave_session(&self, session_id: SessionId) {
        let mut sessions = self.sessions.write().await;
        if let Some(session_data) = sessions.get_mut(&session_id) {
            session_data.info.viewer_count = session_data.info.viewer_count.saturating_sub(1);
        }
    }

    /// Remove a session (when agent disconnects)
    pub async fn remove_session(&self, session_id: SessionId) {
        let mut sessions = self.sessions.write().await;
        if let Some(session_data) = sessions.remove(&session_id) {
            let mut agents = self.agents.write().await;
            agents.remove(&session_data.info.agent_id);
        }
    }

    /// List all active sessions
    pub async fn list_sessions(&self) -> Vec<Session> {
        let sessions = self.sessions.read().await;
        sessions.values().map(|s| s.info.clone()).collect()
    }
}

impl Default for SessionManager {
    fn default() -> Self {
        Self::new()
    }
}


Now let me read the RemoteViewer component, which is likely the most performance-critical part of the dashboard since it handles video rendering:


Let me read the critical frontend components for rendering analysis: