remote/构建运行手册.md
yu 9c340813a8 feat(agent): 新增 agent-core 与 Linux 被控端(X11 + XTest,纯新增不动 agent/)
本期只在新增文件里做,**未改动 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` 行为不变
2026-10-23 13:31:08 +08:00

9.6 KiB
Raw Blame History

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 预期内)

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

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 侧已知限制

  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 编写,自定义键盘布局可能需要调整。