# MVP 远程控制 Demo · 构建运行手册 > 项目路径:`D:\code\remote\` > 形态:Rust workspace:`common`(协议)、`agent-core`(平台无关会话层,当前供 linux-agent 使用)、`agent`(Windows 被控端)、`linux-agent`(Linux 被控端)、`viewer`(主控端) ## 1. 功能范围(量级一 Demo) - ✅ DXGI Desktop Duplication 全屏采集 + NVENC 硬编(无 N 卡时自动回落 openh264 软编) - ✅ QUIC 加密传输(自签名证书,传输层已加密) - ✅ 验证码认证(默认 123456) - ✅ 键鼠远程控制(SendInput 注入,虚拟桌面绝对坐标 + 扫描码) - ✅ 桌面静止时低频重发(保持画面新鲜) - ✅ Linux 被控端(X11 采集 + XTest 注入,见第 8 节) - ❌ 暂无:P2P 打洞/中继(当前仅局域网/可直连地址)、音频、文件传输 ## 2. 环境要求 - Windows 8+(被控端需要 DXGI Duplication;主控端 Win7 亦可) - Rust(GNU host:`x86_64-pc-windows-gnu`)+ MinGW-w64,已验证可用组合: - `rustup default stable-x86_64-pc-windows-gnu`,装到 `%USERPROFILE%\.cargo\bin` - MinGW-w64:`D:\mingw64\bin`(gcc 16.2.0) - 构建时 PATH 需同时包含上面两个目录(缺 MinGW 会在链接 openh264 的 C++ 时失败) - **cargo 源**:国内直连 crates.io 会超时(cargo 不读系统代理),需配置镜像。已在本机 `%USERPROFILE%\.cargo\config.toml` 写入 USTC 源: ```toml [source.crates-io] replace-with = "ustc" [source.ustc] registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/" ``` > 注:旧版本手册写的 `D:\wordkb\...\uurev\tools\rust` 路径已不存在,按上面重新准备工具链即可。 ## 3. 构建 ```bash export RUSTUP_HOME="D:\\wordkb\\2026-10-22-15-52-47\\uurev\\tools\\rust\\rustup" export CARGO_HOME="D:\\wordkb\\2026-10-22-15-52-47\\uurev\\tools\\rust\\cargo" export PATH="/d/wordkb/2026-10-22-15-52-47/uurev/tools/rust/cargo/bin:/d/wordkb/2026-10-22-15-52-47/uurev/tools/mingw64/bin:$PATH" cd /d/wordkb/2026-10-22-15-52-47/mvp-remote cargo build --release ``` 产物: - 被控端 `target/release/agent.exe` - 主控端 `target/release/viewer.exe` ## 4. 运行(两台机器,或同机双开验证) 被控端(要被控制的电脑): ```bash agent.exe --port 47800 --code 888888 ``` 主控端(操作的电脑): ```bash viewer.exe --addr <被控端IP> --port 47800 --code 888888 ``` 同机自测:被控端 `--port 47800`,主控端 `--addr 127.0.0.1`。 操作说明:窗口内直接键鼠操作;**Esc 退出**。窗口尺寸=被控端分辨率(暂不可缩放)。 ## 5. 代码结构导读(演进锚点) | 文件 | 职责 | 二期改造点 | |---|---|---| | `common/src/lib.rs` | 控制消息/视频帧线格式 | 换 protobuf;加 FEC/序列号 | | `agent/src/capture.rs` | DXGI 采集 + LZ4 | 加脏矩形、NVENC/QSV 硬编(替换 LZ4) | | `agent/src/input.rs` | SendInput 注入 | 加剪贴板/多显示器虚拟桌面细化 | | `viewer/src/main.rs` | minifb 渲染 + 输入采集 | 换 GPU 解码渲染(D3D11/YUV);加自适应码率 | | `viewer/src/keymap.rs` | 扫描码映射 | 补全 F 区/小键盘/IME 文本通道 | ## 6. 已知限制(MVP 预期内) 1. 带宽:1080p LZ4 裸帧约 20-60MB/s,**仅适合局域网**。公网可用必须上 H.264 硬编(二期第一个任务)。 2. 安顿证书:客户端跳过服务端证书校验(代码内 `SkipVerify` 已标注),存在中间人风险,仅限测试。 3. 被控端需在**交互会话**中运行(RDP 断开后桌面复制会失效——二期做服务化+虚拟桌面处理)。 4. 高 DPI/多屏:当前只采集主输出(EnumOutputs(0))。 5. 主控端窗口固定为远端分辨率,颜色序 BGRA→0RGB 若异常可在 `blit()` 一键翻转。 ### 6.1 待修问题(已定位,尚未修,均在 Windows `agent/`) 1. **错误验证码时 `AuthFail` 送不达**:`agent/src/main.rs` 写完 `AuthFail` 后立刻 `send.finish()` 并返回,进程退出会把 QUIC 缓冲区里尚未发出的数据一起带走, 客户端只会看到 `connection lost` 而不是 `AuthFail`。 修法:`finish()` 后等对端读到并挂断(`conn.closed()` + 超时)再退出。 2. **软编路径下静止桌面仍不响应关键帧请求**(`04c02d9` 只修了 NVENC 那一半): `agent/src/capture.rs` 的 `Ok(false)`(画面未变)分支里,`force_key` 被 `keyreq.swap` 消费后,只有 `EncoderKind::Nvenc` 分支拿它去重编 IDR; `EncoderKind::Soft` 分支仍然只是「重发最后一帧码流副本」,标志被直接丢弃。 后果:**无 N 卡 / 驱动异常(走软编兜底)的机器上,解码端一旦出错,静止画面下 永远拿不到 IDR,表现为黑屏。** 修法:软编路径同样保留最近一帧裸像素,`force_key` 时重编一个真关键帧。 3. **单次 `KeyFrameReq` 可能被静默吞掉**:同一个 `keyreq.swap(false, …)` 写在了 `if last_sent.elapsed() > 500ms` 的**外面**,因此请求若落在距上次发送 500ms 之内, 标志已被消费但分支不进入,该次请求就丢了,必须等下一次请求才可能生效 (主控端目前周期性重发,故表现为延迟而非永久黑屏)。 修法:把 `swap` 移进时间闸内,或先在闸外预判、进闸后再消费。 > 第 1、2 条在 `feature/agent-linux` 分支的 `agent-core` 里已经实现(**Linux 侧生效**), > 但按"不与他线争抢 `agent/`"的约定,**本期没有改动 Windows 代码**。 ## 7. 下一阶段任务队列(Phase 1 剩余) 1. H.264:接 openh264(软编兜底)→ NVENC/QSV(硬编优先) 2. 码率自适应 + 关键帧请求(协议已留 `KeyFrameReq`) 3. QUIC 多流:视频/控制/键鼠分离流(键鼠零阻塞) 4. P2P 打洞 + coturn 中继(自建信令服务器) 5. 被控端服务化(Windows Service + 会话切换处理) ## 8. Linux 被控端(linux-agent) ### 8.1 现状与边界(重要) 本期**只在新增文件里做,未改动 Windows `agent/`**,原因见下: | crate / 文件 | 职责 | 当前使用者 | |---|---|---| | `agent-core/src/lib.rs` | QUIC/TLS 握手、传输调优、验证码认证、会话/select 循环、latest-wins 单槽、输入分发,以及软件编码视频管线(openh264) | **仅 linux-agent** | | `linux-agent/src/capture.rs` | X11 `GetImage` 采集 + 整帧变更检测 | Linux | | `linux-agent/src/input.rs` | XTest 注入 + 扫描码→X11 keycode 映射 | Linux | | `agent/`(Windows) | 保留原有自持会话循环 + NVENC/软编双路编码,**本期未动** | Windows | **为什么 Windows 端没有一并接过来**:`agent/` 的 NVENC 路径是「GPU 内 BGRA→H.264 零转换零拷贝」, 编码器必须直接吃 DXGI 的 GPU 纹理;而 `agent-core` 目前的接缝是 `ScreenCapturer`(返回 CPU 上的 BGRA 字节),硬接会把零拷贝优势废掉。正确的接缝应是「平台视频管线产出**已编码帧**」 (`VideoSource` 语义),待 `agent/` 由 NVENC 那条线稳定后再重切,届时应把 `openh264` 从 `agent-core` 移到各平台 crate。 在此之前 Windows 与 Linux 各持一份会话循环,属于**有意的过渡态**:`common/` 协议改动需要两处同步 (例如本期跟着补的 `Pong{lost,sent}`)。 ### 8.2 构建 Windows 侧照旧(`default-members` 已排除 linux-agent,产物仍是 agent.exe / viewer.exe): ```bash export PATH="$USERPROFILE/.cargo/bin:/d/mingw64/bin:$PATH" # Git Bash 写法 cargo build --release ``` Linux 侧显式指定包(跨平台交叉编译不推荐,用容器最省事): ```bash cargo build --release -p linux-agent ``` ### 8.3 运行 ```bash # 被控 Linux 机器(Xorg 或 XWayland 均可,依赖 DISPLAY 环境变量) ./linux-agent --port 47800 --code 123456 # 主控端(Windows)命令行不变 viewer.exe --addr <被控机IP> --port 47800 --code 123456 ``` ### 8.4 冒烟测试(不需要图形桌面) ```bash bash scripts/linux-smoke.sh ``` 脚本自建 Xvfb 虚拟屏后跑端到端测试,覆盖:X11 采集 → openh264 编码 → QUIC 下行解帧、 验证码认证(含**错误验证码被拒**)、Ping/Pong(含下行丢包统计字段)、静止桌面下的关键帧 恢复,以及**输入注入实证**——注入鼠标移动后,从另一条 X11 连接读回指针真实坐标。 > 静止关键帧那条用例做过**变异测试**:把修复退回会失败、恢复后通过,确认断言不是摆设。 > 注意扰动量必须小(画 64x64 小方块)——整屏刷白会触发编码器场景切换自动 IDR, > 于是"重发旧帧"和"真重编关键帧"拿到同样的字节,用例就失去区分度。 ### 8.5 键码映射(两端最关键的差异) 协议里键盘沿用 Windows 口味的 PS/2 Set-1 扫描码,而 X11 要的是 keycode,两者**并非处处等价**: - 主键区:满足 `keycode = scancode + 8`; - **E0 扩展键编号体系完全不同**(`↑` 是 0x48,但 X11 keycode 是 111 而不是 0x50)——必须查表。 映射表在 `linux-agent/src/input.rs::keycode_of`,带单元测试防回归。 ### 8.6 Linux 侧已知限制 1. 仅支持 32bpp TrueColor 视觉(绝大多数桌面满足),其他格式启动即明确报错而非静默出错; 2. 采集走核心协议 `GetImage`,每帧经 X socket 全量传输,静止桌面也会持续轮询抓帧; **MIT-SHM 共享内存 + XDamage 脏区**是下一步优化(锚点已写在 `linux-agent/src/capture.rs` 顶部注释); 3. 仅覆盖 X11 / XWayland;纯 Wayland(无 XWayland)需另走 PipeWire 门户; 4. 键位表按 Xorg evdev 标准 keycode 编写,自定义键盘布局可能需要调整。