本期只在新增文件里做,**未改动 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` 行为不变
9.6 KiB
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 源:[source.crates-io] replace-with = "ustc" [source.ustc] registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"
注:旧版本手册写的
D:\wordkb\...\uurev\tools\rust路径已不存在,按上面重新准备工具链即可。
3. 构建
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. 运行(两台机器,或同机双开验证)
被控端(要被控制的电脑):
agent.exe --port 47800 --code 888888
主控端(操作的电脑):
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 预期内)
- 带宽:1080p LZ4 裸帧约 20-60MB/s,仅适合局域网。公网可用必须上 H.264 硬编(二期第一个任务)。
- 安顿证书:客户端跳过服务端证书校验(代码内
SkipVerify已标注),存在中间人风险,仅限测试。 - 被控端需在交互会话中运行(RDP 断开后桌面复制会失效——二期做服务化+虚拟桌面处理)。
- 高 DPI/多屏:当前只采集主输出(EnumOutputs(0))。
- 主控端窗口固定为远端分辨率,颜色序 BGRA→0RGB 若异常可在
blit()一键翻转。
6.1 待修问题(已定位,尚未修,均在 Windows agent/)
-
错误验证码时
AuthFail送不达:agent/src/main.rs写完AuthFail后立刻send.finish()并返回,进程退出会把 QUIC 缓冲区里尚未发出的数据一起带走, 客户端只会看到connection lost而不是AuthFail。 修法:finish()后等对端读到并挂断(conn.closed()+ 超时)再退出。 -
软编路径下静止桌面仍不响应关键帧请求(
04c02d9只修了 NVENC 那一半):agent/src/capture.rs的Ok(false)(画面未变)分支里,force_key被keyreq.swap消费后,只有EncoderKind::Nvenc分支拿它去重编 IDR;EncoderKind::Soft分支仍然只是「重发最后一帧码流副本」,标志被直接丢弃。 后果:无 N 卡 / 驱动异常(走软编兜底)的机器上,解码端一旦出错,静止画面下 永远拿不到 IDR,表现为黑屏。 修法:软编路径同样保留最近一帧裸像素,force_key时重编一个真关键帧。 -
单次
KeyFrameReq可能被静默吞掉:同一个keyreq.swap(false, …)写在了if last_sent.elapsed() > 500ms的外面,因此请求若落在距上次发送 500ms 之内, 标志已被消费但分支不进入,该次请求就丢了,必须等下一次请求才可能生效 (主控端目前周期性重发,故表现为延迟而非永久黑屏)。 修法:把swap移进时间闸内,或先在闸外预判、进闸后再消费。
第 1、2 条在
feature/agent-linux分支的agent-core里已经实现(Linux 侧生效), 但按"不与他线争抢agent/"的约定,本期没有改动 Windows 代码。
7. 下一阶段任务队列(Phase 1 剩余)
- H.264:接 openh264(软编兜底)→ NVENC/QSV(硬编优先)
- 码率自适应 + 关键帧请求(协议已留
KeyFrameReq) - QUIC 多流:视频/控制/键鼠分离流(键鼠零阻塞)
- P2P 打洞 + coturn 中继(自建信令服务器)
- 被控端服务化(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):
export PATH="$USERPROFILE/.cargo/bin:/d/mingw64/bin:$PATH" # Git Bash 写法
cargo build --release
Linux 侧显式指定包(跨平台交叉编译不推荐,用容器最省事):
cargo build --release -p linux-agent
8.3 运行
# 被控 Linux 机器(Xorg 或 XWayland 均可,依赖 DISPLAY 环境变量)
./linux-agent --port 47800 --code 123456
# 主控端(Windows)命令行不变
viewer.exe --addr <被控机IP> --port 47800 --code 123456
8.4 冒烟测试(不需要图形桌面)
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 侧已知限制
- 仅支持 32bpp TrueColor 视觉(绝大多数桌面满足),其他格式启动即明确报错而非静默出错;
- 采集走核心协议
GetImage,每帧经 X socket 全量传输,静止桌面也会持续轮询抓帧; MIT-SHM 共享内存 + XDamage 脏区是下一步优化(锚点已写在linux-agent/src/capture.rs顶部注释); - 仅覆盖 X11 / XWayland;纯 Wayland(无 XWayland)需另走 PipeWire 门户;
- 键位表按 Xorg evdev 标准 keycode 编写,自定义键盘布局可能需要调整。