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
326 lines
18 KiB
Markdown
326 lines
18 KiB
Markdown
# 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 IronRDP,Negotiation + TLS 证书、无凭据
|
||
├── RDP Viewer IronRDP/winit,D3D11 swap chain CPU 上传呈现
|
||
└── Native Video Helper GStreamer/D3D11,Linux 桌面默认
|
||
```
|
||
|
||
客户端只运行于 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 Adapter,decoder、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 自定义 transport;mstsc 回退只有标准 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` 不约束 RDP:RDP 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。
|
||
- 多会话、窗口关闭、应用退出和诊断脱敏。
|