Files
RemoteDesk/docs/windows-client.md
曾志威 5db6b9ef68
ci / rust (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / package-preview (push) Canceled after 0s
ci / package-installer (push) Canceled after 0s
ci / linux-agent (push) Canceled after 0s
ci / edge-service (push) Canceled after 0s
ci / coturn-pop (push) Canceled after 0s
ci / package-windows-host (push) Canceled after 0s
Initial commit
2026-08-14 00:35:42 +08:00

326 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RemoteDesk Windows 客户端设计
## 1. 产品结构
Windows 客户端的目标形态由 Tauri 主程序和按需启动的原生 helper 组成。当前 M0 便携版使用 React 管理界面、Rust 控制服务、两个 RDP helper,并保留 Linux native-video 管线规划 helper
```text
RemoteDesk Tauri Client
├── 管理主窗口 React/TypeScript
├── Linux 终端窗口 xterm.js
├── Rust Core 配对、配置、Edge
├── RDP Protocol Probe IronRDPNegotiation + TLS 证书、无凭据
├── RDP Viewer IronRDP/winitD3D11 swap chain CPU 上传呈现
└── Native Video Helper GStreamer/D3D11Linux 桌面默认
```
客户端只运行于 Windows。Linux 只部署 Agent。
## 2. 主界面
```text
+----------------------------------------------------------+
| RemoteDesk | Search [+ Add] [Settings]|
+------------+---------------------------------------------+
| All | Name Type State Last used |
| Windows | dev-win RDP Offline Yesterday |
| Linux | build Terminal Ready 10:42 |
| Favorites | desktop Wayland Running 09:18 |
| Recent | |
+------------+---------------------------------------------+
| status / non-sensitive diagnostics |
+----------------------------------------------------------+
```
- 列表优先,适合重复连接和状态扫描。
- 连接和编辑使用图标按钮和 tooltip。
- 状态不只依赖颜色。
- 不设置营销首页、超大标题或嵌套卡片。
## 3. Tauri 窗口
### 3.1 Main Window
- 主机列表、搜索、编辑和设置。
- 配对设备、权限和撤销。
- 全局 GPU、网络、画质和重定向策略。
### 3.2 Terminal Window
- xterm.js 独立会话。
- 支持搜索、复制、粘贴确认和窗口尺寸同步。
- 终端页不加载远端网页,不执行远端 HTML/JS。
## 4. Native Helpers
### 4.1 RDP Protocol Probe
独立 `remotedesk-rdp-session.exe`
- 从有界 stdin JSON 读取目标和超时,不允许账号或密码字段。
- 执行 DNS、TCP、X.224 和 RDP Negotiation Confirm。
- 验证服务端是否提供兼容的增强安全协议,不进入 CredSSP/NLA,也不验证登录结果。
### 4.2 Native RDP Viewer
独立 `remotedesk-rdp-viewer.exe`
- 使用 IronRDP 和 winit 建立客户端内单屏或全部本地显示器 RDP 会话;多屏路径由 Win32 枚举真实 geometry,并通过 RDPEDISP 发送主屏相对 monitor layout。
- 控制服务以安全 Named Pipe 交付目标、opaque 凭据引用和显示参数;viewer 命令行只含随机 pipe name。viewer 只有在严格 JSON schema 和所有配置字段通过自身校验后才返回绑定载荷摘要的认证 ACK,控制服务收到合法 ACK 后才把启动握手视为成功。
- 凭据只在 helper 的本地遮罩提示中输入,不经过 argv、HTTP、React 或临时 `.rdp` 文件。
- RDP frame 留在原生进程,不进入 Tauri/WebView。
- Windows 硬件 D3D11 可用时使用 `d3d11_cpu_upload`IronRDP 只转换 `GraphicsUpdate` dirty region 的紧凑像素块,viewer 将其写入持久 CPU framebuffer,并把 redraw 前合并后的变化区域局部上传到完整 GPU frame texture,再复制到 DXGI back buffer;创建失败时回退 `software_framebuffer`
- 窗口聚焦时通过 IronRDP FastPath 转发物理键盘扫描码、五键鼠标、水平/垂直滚轮和按窗口缩放的绝对指针坐标;失焦和关闭前统一释放远端按键及鼠标状态。
- 支持启动时全屏,并用 `F11`/`Alt+Enter` 切换全屏、`Esc` 退出全屏、`Ctrl+Shift+Q` 关闭会话。
- 使用 `Ctrl+Alt+Home` 触发受控的远端 `Ctrl+Alt+End` 按下/释放序列,以调用 RDP 远程安全界面;不宣称捕获或发送本机 `Ctrl+Alt+Del`
- 通过随机会话标识关联脱敏诊断快照,回传凭据等待、连接、结束状态、帧数、桌面尺寸、本地解码处理耗时、本地呈现耗时、最近一次像素转换数量、frame upload 模式和上传像素数;解码计时覆盖网络图形 PDU 进入 IronRDP active stage 后的处理及 dirty region 像素转换,不将输入、resize 或帧间隔冒充解码;RDP 服务器发送 `NetworkCharacteristicsResult` 时另回传会话平均 RTT、基础 RTT 和估算带宽,未发送时保持空值;不写入目标、账户、凭据、证书或原始错误。
- 无凭据探针完成 RDP Negotiation 后进入 TLS,返回完整服务器证书 DER 的 SHA-256;用户确认的指纹随原生启动请求传入独立 viewer,实际会话 TLS verifier 同时强制匹配指纹并验证 TLS 1.2/1.3 握手签名。指纹缺失或变化时 fail closed。
- “全部本地显示器”创建覆盖 Windows 虚拟桌面的无边框组合窗口,最多接受 16 个显示器并要求恰好一个主屏;`Esc` 退出组合全屏后仍可缩放查看,`F11`/`Alt+Enter` 恢复组合全屏。自定义显示器子集仍使用 `mstsc`
D3D11 持久 frame texture、合并 dirty-rect 的局部 CPU 上传与 swap chain 呈现已接入;交换链使用 discard 语义,因此每次 Present 前从完整 frame texture 执行 GPU resource copy,不能把该路径称为零拷贝。硬件解码和经过验证的零拷贝仍属于后续目标。
### 4.3 Native Video Helper
独立 `remotedesk-native-video.exe` 是 Linux 桌面默认呈现端。它使用 gstreamer-rs/webrtcbin,在窗口所属或用户指定的 DXGI Adapter 上建立硬件 decoder、`GstD3D11Memory` 和 d3d11videosink。原始帧不离开 D3D11 surface,不经过 Tauri/WebView/Named Pipe。
Helper 完成短样本解码和呈现验证后报告 `EndpointMemoryPath`。只有 decoder、sink 和输出位于同一 Adapter 且 ETW/GStreamer 诊断未发现 CPU map/copy,才报告 `zero_copy`。严格模式验证失败会终止媒体协商;不自动切换 WebView2。
## 5. Helper IPC
```text
Tauri Rust Core
|
restricted Named Pipe
|
Native Helper
```
规则:
- Pipe 使用当前 Windows SID 的保护 DACL、单实例和 `PIPE_REJECT_REMOTE_CLIENTS`
- 随机 pipe name、一次性 32 字节 bootstrap key 和服务端 challenge nonce。
- 以 OS 返回的 pipe 客户端 PID 为准,校验已启动进程的创建时间、实际镜像路径和 SHA-256 build hash,并要求 HMAC-SHA256 challenge 通过。
- 每个 viewer 绑定独立的 `KILL_ON_JOB_CLOSE` Job Object;握手失败终止 Job 并写入结构化 `secure_pipe_failed` 诊断,控制服务异常结束时由 Windows 内核清理遗留 helper。
- 凭据、Session Ticket 和 TURN credential 不进入命令行。
- IPC 使用 64 KiB 上限、长度前缀、拒绝未知字段的版本化 JSON;会话载荷另带绑定 challenge 的 HMAC。viewer 完成严格 schema 和字段校验后返回载荷 SHA-256 与 challenge 绑定的 HMAC ACK;摘要、MAC、版本错误或连接提前关闭都会使控制服务拒绝启动。连接、认证、配置交付和 ACK 共用一个 10 秒绝对 deadline,任一阶段停滞都会超时并终止对应 Job。
- 只传命令、状态、指标和错误,不传 frame/PCM。
- Heartbeat 失效后主程序清理会话并提示重启。
- Helper 不能访问主应用全部 Tauri command。
## 6. Session Controller
统一状态机:
```text
Idle
-> Preflight
-> StartingTarget
-> Authenticating
-> Authorizing
-> Negotiating
-> Connected
-> Degraded
-> Reconnecting
-> Closing
-> Closed | Failed
```
实现规则:
- Rust Core 中一个 actor 串行处理每个会话状态。
- WebView/helper 只提交 Event,不直接修改核心状态。
- 每次连接有 generation,旧窗口/helper 回调忽略。
- Close 幂等,可从任何非终态调用。
- Linux Terminal、RDP helper 和 Native Video 映射到同一错误模型。
- 会话窗口关闭不自动关闭主应用。
## 7. Adapter
```text
SessionAdapter
├── LinuxWebCompatibilityAdapter
├── LinuxNativeAdapter
├── TerminalAdapter
├── IronRdpHelperAdapter
└── MstscFallbackAdapter
```
Adapter 提供:
- capability。
- connect/disconnect/reconnect。
- resize/display topology。
- quality/network policy。
- input/clipboard/file permissions。
- metrics 和结构化错误。
## 8. 分辨率控制
会话工具栏使用分辨率菜单,提供:
- 自动跟随窗口(默认)。
- 1280x720、1600x900、1920x1080、2560x1440、3840x2160。
- 自定义宽高;当前 RDP 启动边界要求宽高都是 200 到 8192 的整数。
IronRDP 窗口 resize 经过 1 秒防抖,每个稳定请求分配单调 generation。服务端支持 Display Control 时,只有收到与请求尺寸一致的远端图像才标记确认;调整后的协议对齐尺寸会回传给 viewer,旧 generation 的提交、失败和图像不会覆盖新请求。请求发送后 5 秒没有对应图像则进入结构化超时状态。
不同 Adapter 的语义:
- IronRDP:通过客户端 resize 事件请求调整远程会话桌面;服务端不支持动态调整或确认超时时,vendored client 保持当前连接,不再静默重连。原生窗口明确提示目标尺寸,只有用户确认才按新尺寸重连,取消后保留当前会话;诊断 API 和 React 同步显示 pending、reconnect_required、reconnecting、confirmed 或 cancelled。
- `mstsc`:跟随窗口写入 `dynamic resolution:i:1`;固定/自定义尺寸写入 `dynamic resolution:i:0``desktopwidth``desktopheight`
- Linux Native:保持 Wayland/Xorg 源显示器模式不变,在 Agent 编码前缩放到目标尺寸。宽高比不同则使用等比缩放和黑边,不静默裁剪。
- Terminal:分辨率菜单不可用;终端继续按字符行列 resize。
固定尺寸默认是自适应算法的上限,差网络时允许暂时降档。用户可开启“锁定分辨率”,此时只降低码率和帧率;带宽不足时 UI 显示退化状态。原始尺寸、编码尺寸、接收尺寸和本地 viewport 分别进入诊断指标。
### 8.1 多显示器
客户端通过 DXGI/Win32 枚举本地 output,并生成稳定显示器 ID、物理像素矩形、主屏和 DPI scale。用户可选择主屏、全部屏幕或自定义子集。
Windows RDP helper 将所选本地布局规范化为 RDP Display Control Dynamic Monitor Layout,保留主屏左侧/上方的负坐标和每屏方向、尺寸及 DPI。布局受服务端最大显示器数量、单屏尺寸和总桌面面积限制;能力不满足时返回结构化错误。服务端不支持动态更新时,客户端提示“重连后应用”,必须由用户确认。
Linux native helper 接收的是 Agent 发布的远端显示器选择,但本地呈现仍绑定窗口所在 output。远端多屏可使用单一组合桌面或多个原生窗口;无论哪种方式,输入坐标都携带稳定显示器 ID 和布局 generation。
显示器热插拔、DPI、方向、位置、分辨率或选择变化会递增 generation,取消旧布局计划,并使严格零拷贝报告进入 Revalidating。严格模式下 decoder、render 和最终 display Adapter 必须一致;跨 GPU 多屏只能在 Compatibility 中标记为 `CrossAdapterCopy`
## 9. Linux Desktop Video
默认流程:
1. Rust 完成设备认证、Session Ticket 和双端零拷贝能力预检。
2. 启动 native-video helper,通过受限 Named Pipe 交付短期会话材料。
3. Helper 建立 WebRTC、D3D11 hardware decoder 和 d3d11videosink。
4. Agent 与 helper 分别提交经过验证的 `EndpointMemoryPath`
5. 两端都为 `zero_copy` 且 generation 匹配后,SessionController 才进入 Connected。
用户显式启用 `compatibility` 后,可创建受限 WebView2 页面,将 WebRTC `MediaStream` 绑定 `<video>.srcObject`。该路径不得获取 Agent 长期私钥或其他主机配置,并固定报告 `opaque_gpu_path`,不宣称客户端零拷贝。
## 10. GPU 模式
### 10.1 默认
- Linux native-video helper 使用窗口显示器所属 DXGI Adapterdecoder、sink 和 swap chain 保持同 Adapter。
- RDP helper 默认选择窗口显示器所属 Adapter。
- 用户界面显示实际 GPU、硬解状态以及 Agent/client 两端 memory path。
### 10.2 手动
- RDP helper 直接指定 DXGI Adapter LUID。
- Linux desktop native-video helper 使用指定 DXGI Adapter。
- 主机级选择覆盖全局选择。
- 指定 GPU 不存在时本次回退默认,不修改配置。
```text
host manual > global manual > default
```
切换 GPU 需要重建当前会话呈现/decoder,但不重启主程序。
## 11. 网络路径
策略:
- 智能优选。
- 直连优先。
- CDN 优先。
每次连接重新探测并记录:
- Direct/Single Edge/Dual Edge。
- Client POP、Agent POP 和 TURN transport。
- Backbone/last-mile RTT、loss、jitter。
- Path generation 和 ICE restart。
Linux native-video helper 直接使用 WebRTC ICE/TURN。RDP helper 通过 L4 Edge Connector 自定义 transportmstsc 回退只有标准 RD Gateway/L4 服务时才能进入 CDN 路径。
## 12. 输入
当前 RDP viewer
- winit 物理扫描码和 IronRDP FastPath 键盘事件。
- 当前单屏桌面的缩放绝对鼠标坐标、五键鼠标和水平/垂直滚轮。
- 失焦和关闭时释放远端按键及鼠标按钮。
- 本地保留紧急释放组合键。
Linux 桌面 helper 的协议 minor 10 源码已接入 winit/Windows Raw Input 相对鼠标:只有显式启用并成功抓取窗口光标时才合并发送有界 `pointer_delta``Ctrl+Alt+Home` 切换捕获,失焦、重连和退出解除抓取并释放远端输入。该能力属于 X11 helper,不由当前单屏 RDP viewer 声明;带布局 generation 的多显示器坐标仍未实现。
## 13. HostProfile
```text
id
name
kind: windows_rdp | linux_agent
address/device_id
credential_ref
preferred_session: auto | wayland | xorg | terminal
linux_renderer: native
gpu_policy: inherit | default | manual(adapter_identity)
agent_gpu_policy: capture_device | manual(adapter_identity)
zero_copy_policy: required_end_to_end | compatibility
network_policy: smart | direct_preferred | cdn_preferred
display_policy:
selection: primary | all | custom(stable_display_ids)
resolution_mode: follow_window | source_native | fixed
width/height optional
adaptive_downscale: true | false
local_scale: fit | one_to_one | stretch
quality_policy
redirect_policy
last_used_at
```
SQLite 不保存密码、私钥或 Token。POP 不固定写入 HostProfile,每次根据质量重新选择。
Linux 图形主机默认保存 `native + capture_device + required_end_to_end`。Agent 手动 GPU 与捕获 DRM device 不一致时严格拒绝,不能跨 GPU 搬运原始帧。用户只有显式选择 `compatibility` 时才允许原生 helper 使用软件回退,不能由故障恢复或远端指令自动完成。`zero_copy_policy` 不约束 RDPRDP helper 若只从 IronRDP 得到 CPU bitmap,则报告 `cpu_upload` 并继续使用 dirty-rect 优化;只有 IronRDP 暴露可验证的 GPU/video surface 时才允许报告 `zero_copy`
### 13.1 RDP 凭据
HostProfile 只保存 opaque `credential_ref`,它指向 Windows Credential Manager 中的 generic credential target。账户、密码和可选域信息由 Rust Core 写入系统密钥库;密码不返回 React 状态,不进入 SQLite、日志、诊断包或配置导出。
RDP helper 必须先通过 PID、创建时间、build hash 和 challenge MAC 完成本地 Named Pipe 身份认证,主进程才交付短期会话材料。更新凭据采用覆盖写;删除主机时询问是否同时删除密钥库项,避免无主凭据。凭据读取失败、target 不匹配或引用为空时,连接在进入 NLA 前失败并重新打开凭据界面。
## 14. WebView 安全
- 严格 CSP,不允许远端 script、iframe 和任意导航。
- Tauri capability 按 main/desktop/terminal window 分开。
- Release 禁用 devtools。
- 只允许必要的 clipboard 和本地资源权限。
- TURN credential/SDP 只进入受限原生 helper。
- `postMessage`/Tauri command 校验 window label、session ID、generation 和 payload size。
- Agent 发来的文本必须作为文本显示,不使用 `innerHTML`
## 15. 错误体验
用户错误页展示:
- 稳定错误名称。
- 当前 Adapter/render mode/path mode。
- 是否可重试或切换原生 helper/mstsc。
- correlation ID 和诊断入口。
- 当前 `zero_copy/gpu_copy/cpu_upload/software/opaque_gpu_path`,以及发生回退的具体阶段。
不直接显示 Rust Debug、SDP、TURN credential、GStreamer dump 或远端路径。
## 16. 多会话
- 每个会话独立 window/helper、cancel token 和 generation。
- 主进程跟踪原生 helper/GPU/硬解 session 数量。
- 后台 Linux native 会话降低呈现/质量策略,但保持输入控制通道。
- Helper crash 不终止其他会话。
- 应用退出先请求所有会话优雅关闭,再清理超时进程。
## 17. 测试
- Tauri 主机列表与会话状态。
- Native helper 的 D3D11 decoder -> GstD3D11Memory -> sink 全程零拷贝。
- 原生 WebRTC 输入、系统组合键和 Raw Input。
- xterm.js 中文、全屏 TUI、高频输出和 resize。
- Named Pipe ACL、nonce、版本、helper crash/restart。
- IronRDP/NLA/D3D11 和 mstsc 回退。
- 自动跟随、固定/自定义尺寸、原始尺寸、锁定策略和动态分辨率失败回退。
- Linux 调整编码尺寸时不改变 Wayland/Xorg 实体显示模式,黑边后的输入坐标正确。
- Wayland DMA-BUF、Xorg DRI3 和客户端 D3D11 双端零拷贝;跨 GPU/CPU 路径默认拒绝。
- 默认 GPU、手动 GPU/native helper 和 GPU 缺失回退。
- Direct/Single/Dual Edge 和 RDP L4 Connector。
- 多会话、窗口关闭、应用退出和诊断脱敏。