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

18 KiB
Raw Permalink Blame History

RemoteDesk Windows 客户端设计

1. 产品结构

Windows 客户端的目标形态由 Tauri 主程序和按需启动的原生 helper 组成。当前 M0 便携版使用 React 管理界面、Rust 控制服务、两个 RDP helper,并保留 Linux native-video 管线规划 helper

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. 主界面

+----------------------------------------------------------+
| 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_uploadIronRDP 只转换 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

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

统一状态机:

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

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:0desktopwidthdesktopheight
  • 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 不存在时本次回退默认,不修改配置。
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_deltaCtrl+Alt+Home 切换捕获,失焦、重连和退出解除抓取并释放远端输入。该能力属于 X11 helper,不由当前单屏 RDP viewer 声明;带布局 generation 的多显示器坐标仍未实现。

13. HostProfile

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。
  • 多会话、窗口关闭、应用退出和诊断脱敏。