本期只在新增文件里做,**未改动 Windows agent/ 一个字节**(与 NVENC 线并行,避免争抢)。
- 新增 agent-core:平台无关会话层 —— QUIC/TLS 握手、传输调优(沿用 512KB 发送窗口
结论)、验证码认证、会话/select 循环、latest-wins 单槽、输入分发,以及软件编码
视频管线(openh264)。同步跟进协议新增的 Pong{lost,sent} 下行丢包统计字段
- 新增 linux-agent:X11 GetImage 采集 + 整帧变更检测;XTest 注入 + 扫描码→X11
keycode 映射(主键区 = scancode+8,E0 扩展键必须查表,含单元测试)
- 协议零改动:复用 Set-1 扫描码与虚拟桌面归一化坐标,viewer 无需任何修改
- 新增端到端冒烟测试 tests/smoke.rs 与 scripts/linux-smoke.sh(自建 Xvfb,无需
图形桌面):采集→编码→QUIC 解帧、认证(含错误码拒绝)、Ping/Pong(含丢包字段)、
静止桌面关键帧恢复、输入注入实证(注入后从另一条 X11 连接读回指针真实坐标)
- 该关键帧用例做过变异测试:把修复退回会失败、恢复后通过,确认断言有效
- .gitattributes:shell 脚本强制 LF(Windows checkout 成 CRLF 会导致容器内无法执行)
- 手册:修正已失效的工具链路径;补充 cargo 镜像;新增第 8 节(Linux)与 6.1 节
(三条待修问题,含两条 Windows 侧仍存在的关键帧缺陷)
验证:
- Linux(容器, USTC 源 + Xvfb): cargo test -p linux-agent 通过,2 passed
- Windows: 本期未改 agent/,default-members 未变,`cargo build --release` 行为不变
191 lines
9.6 KiB
Markdown
191 lines
9.6 KiB
Markdown
# 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 编写,自定义键盘布局可能需要调整。
|