# 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` 绑定 `