# RemoteDesk 技术栈规划 > 当前决策:所有发布运行时使用 Rust;Agent 是服务端;原生客户端使用 > winit、wgpu 和 egui;实时传输使用 str0m WebRTC。Go、Hysteria2、 > Tauri 和 WebView 不属于目标组件。详见 > [ADR 0001](adr/0001-all-rust-runtime.md)。下文保留的旧技术栈内容仅用于 > 迁移对照,后续随代码迁移删除。 ## 1. 混合架构 RemoteDesk Windows 客户端采用 Web UI 与 Rust 原生会话进程混合方案。JavaScript 负责管理界面和终端;Linux 桌面 WebRTC、Windows RDP 和 GPU 呈现均由 Rust 原生 helper 承载,原始视频像素不进入 WebView。 | 模块 | 技术栈 | 职责 | |---|---|---| | Client Shell | Tauri 2、Rust、Tokio | 应用生命周期、凭据、配对、窗口和 helper 管理 | | Management UI | React、TypeScript、Vite | 主机、设置、诊断和会话工具栏 | | Linux Terminal | xterm.js、DataChannel | ANSI/VT 终端和输入 | | RDP Protocol Probe | Rust helper、IronRDP | 无凭据检查 DNS、TCP、X.224 与 RDP 安全协议协商 | | Native RDP Viewer | Rust helper、IronRDP、winit、D3D11/DXGI | Windows RDP 原生窗口;优先使用 D3D11 swap chain CPU 上传,失败时回退 software framebuffer | | Native Linux Video | Rust helper、gstreamer-rs、D3D11 | 默认 WebRTC、硬解和零拷贝呈现 | | Linux Agent | Rust、Tokio、GStreamer、systemd | Wayland/Xorg/PTY 被控端 | | 协议 | Protobuf、TLS WebSocket、WebRTC | 配对、会话和媒体数据 | | Edge | Rust Rendezvous/Allocator、双 POP TURN、L4 Connector | CDN 优质骨干路径 | | 数据 | SQLite、Windows Credential Manager | 配置和敏感凭据 | ## 2. 进程模型 ```text remotedesk.exe (Tauri/Rust) ├── WebView2 main window ├── WebView2 terminal window ├── Rust session/auth/storage/edge services │ ├── remotedesk-rdp-session.exe │ └── IronRDP protocol + TLS certificate probe (no credentials) │ ├── remotedesk-rdp-viewer.exe │ └── IronRDP + winit native window + D3D11 CPU upload fallback │ └── remotedesk-native-video.exe (default Linux desktop) └── GStreamer WebRTC + selected D3D11 adapter ``` 原生 helper 是独立进程,原因: - Tauri/WebView2 与 winit/D3D11 不共享事件循环。 - RDP 或 GPU driver failure 不拖垮主界面。 - helper 可以拥有独立 GPU、DPI、Raw Input 和全屏生命周期。 - 主进程只交换状态和命令,不传输视频帧。 ## 3. Tauri Client Shell ### 3.1 Rust Core - Tauri 2 application runtime。 - Tokio 处理信令、配对、Edge 和 helper IPC。 - rusqlite 保存 HostProfile 与非敏感设置。 - Windows Credential Manager 保存密钥、密码和 Token。 - reqwest/rustls 调用边缘控制面。 - tracing 输出结构化日志。 Tauri command 使用最小 capability allowlist。JavaScript 不能直接读取 Credential Manager、任意文件、Agent 长期私钥。 ### 3.2 React UI - React + TypeScript + Vite。 - 状态按 Host/Profile/Session/Diagnostics 拆分。 - 图标使用成熟图标库,不在业务代码中手写 SVG。 - 页面资源全部本地打包,不加载 CDN 脚本或远端网页。 - Release 禁用开发者工具、任意导航和不必要 Web API。 UI 只接收结构化状态,不接收密码、长期私钥或媒体帧。 ## 5. Linux Terminal - xterm.js 负责 ANSI/VT、Unicode、中文宽字符、选择和滚动区。 - PTY 字节通过可靠 DataChannel 直接进入页面。 - 大量输出有有界缓存和背压。 - 多行粘贴前确认。 - Alternate Screen 与普通历史分离。 - JS 不解析或拼接远端 shell 命令,只呈现 PTY 字节。 xterm.js 性能通过高频输出 Spike 验证;若无法满足,再启用 Rust 原生终端 helper,不影响协议。 ## 6. Native RDP Helpers ### 6.1 当前实现 - Rust stable。 - `remotedesk-rdp-session.exe`:从有界 stdin JSON 接收目标地址,完成 DNS、TCP、X.224、RDP Negotiation Confirm 和无凭据 TLS 证书交换,不接收账号密码,也不进入 CredSSP/NLA。 - `remotedesk-rdp-viewer.exe`:使用 IronRDP 与 winit 建立单屏或全部本地显示器原生会话;账号和密码只在 helper 的本地遮罩提示中输入。 - viewer 的桌面帧留在原生进程,不进入 React、Tauri IPC 或浏览器。 - 当前 viewer 优先报告 `d3d11_cpu_upload`,并通过独立能力 `d3d11_dirty_rect_upload` 声明局部上传;D3D11/DXGI 创建失败时报告 `software_framebuffer`。单屏支持窗口尺寸驱动的动态分辨率,全部本地显示器使用 Win32 geometry 和 RDPEDISP 多 monitor layout,自定义显示器子集仍切换系统 `mstsc`。 ### 6.2 目标渲染路径 渲染路径: ```text IronRDP dirty rect -> D3D11 upload/merge -> GPU texture -> native swap chain ``` IronRDP 关键能力不足时,主程序启动 mstsc 回退;mstsc 不伪装成内嵌原生 helper。 当前已完成 D3D11 device、持久 CPU framebuffer 与完整 GPU frame texture、dirty region 紧凑像素转换、多个待呈现 dirty rect 合并、局部 `UpdateSubresource` 和 swap chain 呈现。为保证 discard swap chain 下未变化区域正确,每次呈现仍从完整 frame texture 复制到 back buffer。因此该路径继续报告 `d3d11_cpu_upload`,不得报告硬件解码或零拷贝。 ### 6.3 Helper IPC 主程序与 helper 使用 Windows Named Pipe: - Pipe 使用当前用户 SID 的严格 ACL。 - RDP viewer 启动时命令行只接收随机 pipe name;一次性 32 字节 bootstrap key 只进入目标子进程环境并在读取后清除。 - 控制服务校验 pipe API 返回的客户端 PID、进程创建时间、实际可执行文件路径和 SHA-256 build hash,再完成 HMAC-SHA256 challenge。 - viewer 严格校验版本化配置后返回载荷 SHA-256 与 challenge 绑定的 HMAC ACK;服务端验证版本、摘要和 MAC 后才确认配置已接受。 - 连接、challenge/auth、配置帧和 ACK 共用有界绝对 deadline;非阻塞分段读取支持正常短读,但不会因已连接 helper 停滞而永久占用握手线程。 - viewer 启动后立即加入设置 `KILL_ON_JOB_CLOSE` 的独立 Job Object;控制服务退出或握手失败时由 Job 终止遗留进程。 - 当前 RDP 管道发送证书指纹、opaque 凭据引用和连接参数,密码仍由 viewer 直接从 Credential Manager 读取;Session Ticket 后续复用同一有界协议。 - IPC 只传控制、状态、指标和输入命令,不传视频 frame。 - helper crash 后主程序显示结构化错误并可重启。 ## 7. Native Linux Video Helper 这是 Linux 桌面默认客户端媒体进程,负责 WebRTC、DataChannel、Raw Input、硬件解码和呈现。默认选择窗口显示器所属 Adapter,用户也可手动指定。 当前 helper 的 Windows 源码除 D3D11 device 和 H.264 decoder allocation probe 外,还提供有界本地文件解码及原生窗口播放入口。它使用启用 DXVA/hardware transforms 且绑定 D3D11 device manager 的 Media Foundation Source Reader,将 H.264 MP4/M4V/MOV 请求为 NV12;输出必须是同一 device 上的 `IMFDXGIBuffer` texture,系统内存 buffer 会失败。播放器跟踪媒体类型的可见尺寸和变化标志,按 100 ns 时间戳调度 texture subresource,以同 device `ID3D11VideoProcessor` 转换和缩放到 BGRA swap chain,黑边由 render target 清除,过程中不映射 CPU frame。该链路提交真实压缩样本并实际调用窗口呈现,但在 ETW/GPUView 尚未证明 video decode engine 和无隐藏 copy 前仍报告 `hardware_decode_verified=false`、`zero_copy_verified=false`。 Linux 桌面控制 helper 的 Windows 模块还将远端 H.264 RTP 按 RFC 6184 重组为有界 Annex-B AU,覆盖单 NAL、STAP-A 和 FU-A。跨帧序号中断或本地一槽邮箱积压会清除参考链、请求 PLI 并等待新关键帧;重连时销毁旧解码器。AU 优先以 `MFVideoFormat_H264_ES` 输入 D3D11-aware H.264 MFT,只接受同一 device 的 NV12 `IMFDXGIBuffer`,再复用 VideoProcessor/swap-chain 模式呈现到 Linux 桌面窗口。第一帧呈现后,协议 minor 9 在完整 zlib 帧 ACK 后确认 H.264-only;随后每个 sequence 只在 D3D11 呈现成功后 ACK,Agent 才捕获下一帧,不再执行同帧 zlib 编码和传输。初始化、解码、媒体、sender/encoder 或 ACK 失败时恢复 zlib 软件 surface。该远程链路尚未编译和实机验证,不能据此打开 `linux.native_video`。 管线: ```text gstreamer-rs webrtcbin -> D3D11 hardware decoder on selected adapter -> GstD3D11Memory -> d3d11videosink -> native HWND ``` decoder、sink 和 swap chain 必须位于同一 Adapter;验证到 CPU map 或跨 Adapter copy 时,`required_end_to_end` 协商失败。该 helper 与 RDP helper 共享 WindowHost、GPU selection、IPC 和诊断 crate,但不共享协议 Adapter。 ## 8. Linux Agent ```text agent/ └── crates/ ├── agentd ├── desktop-session ├── shell-session ├── agent-core ├── agent-media ├── wayland-backend ├── xorg-backend └── platform-linux ``` 主要技术:Tokio、Axum/rustls、zbus/ashpd、gstreamer-rs、x11rb、rustix/nix、prost、rusqlite 和 tracing。 Wayland 使用 Portal/PipeWire/libei;Xorg 默认使用 XComposite/DRI3/XDamage/XRandR/XTest,XShm 仅兼容模式;终端使用 PAM/PTY。unsafe FFI 必须隔离在专用 crate。 Linux 视频内存策略: ```text preferred: PipeWire memory:DMABuf -> GPU color convert/scale -> VA-API or NVENC hardware encoder -> RTP explicit compatibility fallback: PipeWire MemPtr / XShm -> CPU or GPU upload -> encoder -> RTP ``` Agent 媒体核心只传递带所有权和 fence 的 surface handle,不提供接收原始 RGBA `Vec` 的通用接口。禁止用 `appsink` 拉取原始帧再送入编码器。每种 GStreamer/驱动组合必须通过 tracer、perf 和 GPU 工具证明没有 CPU map/copy 后,才能把会话标记为 `zero_copy`。 Agent 默认从 DMA-BUF/DRI3 元数据解析 DRM render node,并在同一设备上选择 VA-API/NVENC video processor 和编码器。服务端手动 GPU 只有在能直接导入该 surface 时可用;跨 GPU 选择不满足严格模式。 ## 9. 协议与 Edge - Protobuf/Buf 定义共享消息契约。 - Rust 端使用 prost,Web UI 使用生成的 TypeScript codec。 - TLS WebSocket 负责配对和信令。 - WebRTC RTP/RTCP 负责媒体和质量反馈。 - IronRDP 原生会话把 RDP `NetworkCharacteristicsResult` 中服务器测得的平均 RTT、基础 RTT 和带宽提升为脱敏诊断事件;不以帧间隔推算网络时延。 - IronRDP 图形事件记录网络 PDU 在本地 active stage 的处理与 framebuffer 像素转换耗时;本地事件保持空值,且该指标不代表服务端编码或 GPU 视频解码。 - DataChannel 负责输入、终端、控制、剪贴板和文件。 - 智能比较 Direct、Single Edge 和 Dual Edge。 - Linux WebRTC 使用双 POP TURN;Windows RDP 使用透明 L4 Connector。 TURN credentials、SDP 和 Session Ticket 通过受限 Named Pipe 交给 native-video helper;长期设备私钥、Agent 授权和短期会话材料均不进入 JavaScript。 ## 10. GPU 选择 配置模式: ```text inherit global default manual(adapter identity) ``` - RDP helper:默认和手动均可精确控制 DXGI Adapter。 - Linux native default:选择窗口显示器所属 Adapter,使 decoder、sink 和输出保持同 GPU。 - Linux manual:native-video helper 使用指定 LUID;窗口不在该 Adapter 的 output 上时严格模式拒绝跨 GPU copy。 - 指定 GPU 缺失时本次回退默认,不修改用户配置。 - 诊断展示 requested/effective GPU、decoder、driver 和 copy path。 - 零拷贝策略为默认 `required_end_to_end` 或显式 `compatibility`;前者要求 Agent/client 两端同时验证通过。 ## 11. 分辨率实现 - Tauri/React:提供自动跟随、原始、固定预设和自定义尺寸菜单,保存主机级策略。 - IronRDP helper:把尺寸请求映射到 Display Control Dynamic Monitor Layout;不支持时仅在确认后重连。 - Linux Agent:使用 VA-API/CUDA 等 GPU video processor/scaler 和 DMA-BUF caps 在编码前缩放,不使用可能落入 CPU raw frame 的通用 `videoscale`,也不改变 Wayland/Xorg 实体显示模式。 - Native renderer:按收到的实际编码尺寸重建呈现资源,保持等比缩放并正确换算黑边输入坐标。 - Protobuf:传递 capability、request/applied、generation 和 source/encoded size,防止乱序 resize 覆盖新状态。 自动跟随窗口必须防抖;固定尺寸默认允许弱网临时下调,也可由用户锁定。所有模式都受双方最大纹理尺寸、编码器像素率和资源预算限制。 ## 12. 仓库结构 ```text RemoteDesk/ ├── Cargo.toml ├── rust-toolchain.toml ├── client/ │ ├── src-tauri/ │ ├── web/ │ └── helpers/ │ ├── rdp-session/ │ └── native-video/ ├── agent/ ├── shared/ │ ├── protocol/ │ ├── session/ │ ├── helper-ipc/ │ └── diagnostics/ ├── services/ │ ├── rendezvous/ │ └── relay-allocator/ ├── protocol/ ├── packaging/ └── tests/ ``` ## 13. 构建 Rust: - Cargo workspace、`Cargo.lock`、`rust-toolchain.toml`。 - rustfmt、Clippy、cargo-deny 和 cargo-audit。 Web: - pnpm lockfile。 - TypeScript strict mode。 - ESLint、Vitest 和 Playwright。 - Vite 构建为本地静态资源。 Windows: - Tauri/WiX MSI。 - 安装主程序、native helpers 和必要 GStreamer runtime。 - WebView2 Evergreen Runtime 作为前置条件。 - 主程序、helper 和安装包全部签名。 Linux: - Cargo + 发行版 GStreamer/PipeWire/PAM/X11/libei。 - DEB/RPM 与 systemd units。 ## 14. 测试 | 层级 | 工具 | |---|---| | Rust | cargo test、proptest | | React/TypeScript | Vitest、Testing Library | | WebRTC 页面 | Playwright + synthetic MediaStream | | RDP | IronRDP + Windows VM | | GPU | GPUView/ETW、GStreamer tracer | | Agent | Cargo integration、真实 Wayland/Xorg | | Edge | coturn、多 POP/netem | | 端到端 | pytest + 虚拟化测试机 | Web 视频测试必须证明 frame 不经过 JS/Canvas:页面不注册逐帧像素处理,Chromium media internals 显示硬解路径,CPU/拷贝指标符合基线。Linux Agent 同时使用 GStreamer tracer、`perf` 和驱动工具验证 DMA-BUF 未被 CPU map;仅看到 Video Encode 引擎占用不能作为零拷贝证据。 ## 15. 安全 - WebView 使用严格 CSP:本地资源、必要的 media/connect source,禁止任意导航。 - Tauri capability 按窗口最小授权。 - Release 禁用 devtools 和远端脚本。 - Web 页面不获取长期私钥或 RDP 密码。 - Native helper IPC 使用 Named Pipe ACL、一次性 token 和进程验证。 - TURN/SDP/session material 限时并在窗口关闭后销毁。 - 依赖生成 SBOM,npm 与 Cargo 同时审计。 ## 16. 许可证 - Tauri、React、xterm.js、IronRDP 和 Rust crates 进入许可证 allowlist。 - WebView2 Runtime 遵循 Microsoft 分发条款。 - GStreamer 插件逐项审计,不默认捆绑 GPL x264。 - 每个安装包附带第三方许可证和机器可读 SBOM。 ## 17. M0 门槛 1. Native-video helper 证明 D3D11 decoder 到 d3d11videosink 无 CPU/cross-GPU copy。 2. Linux 输入、DataChannel 和系统组合键回退正确。 3. xterm.js 通过中文、全屏 TUI 和高频输出测试。 4. Native RDP helper 的 IronRDP/NLA/D3D11 通过。 5. Named Pipe IPC 的 ACL、崩溃和重启通过。 6. 默认显示器 GPU 与手动 GPU 的 native-video 零拷贝路径通过。 7. Direct/Single/Dual Edge 路径通过。 8. Wayland DMA-BUF 和 Xorg DRI3 到硬件编码器的零拷贝路径通过;任一端回退时默认拒绝。 ## 18. 明确不做 - 不把视频 frame 通过 Tauri IPC 传给页面。 - 不用 Canvas/WebGL/WebGPU 逐帧绘制 Linux 远程视频。 - 不在 Web UI 中实现 RDP 协议。 - 不从零实现 RDP、WebRTC、TURN、编解码或密码学。 - 不把 mstsc 回退描述为内嵌 RDP。 该方案用 Web 平台降低管理 UI 和终端开发成本,用 Rust 原生 helper 承载所有默认桌面视频、RDP、精确 GPU、Raw Input 和驱动恢复能力。