18 KiB
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 IronRDP,Negotiation + TLS 证书、无凭据
├── RDP Viewer IronRDP/winit,D3D11 swap chain CPU 上传呈现
└── Native Video Helper GStreamer/D3D11,Linux 桌面默认
客户端只运行于 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_upload:IronRDP 只转换GraphicsUpdatedirty 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_CLOSEJob 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: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
默认流程:
- Rust 完成设备认证、Session Ticket 和双端零拷贝能力预检。
- 启动 native-video helper,通过受限 Named Pipe 交付短期会话材料。
- Helper 建立 WebRTC、D3D11 hardware decoder 和 d3d11videosink。
- Agent 与 helper 分别提交经过验证的
EndpointMemoryPath。 - 两端都为
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 不存在时本次回退默认,不修改配置。
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
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。
- 多会话、窗口关闭、应用退出和诊断脱敏。