用途与实际影响
这项功能怎样使用
为什么需要它
多屏扩展下,Windows 可能把新开或失去焦点的窗口放到看不见的扩展屏;如果盲目开启“系统镜像”,又可能打乱物理屏高刷与 HDR(高动态范围)。因此需要一套先核对身份、再决定是否动作的窗口与捕获目标守护器。
举个实际例子
“家里的主屏真的不在线了,我想从手机继续看电脑。”守护器先确认屏幕已稳定离线、没人正在串流、显卡状态可用,才切到已认准的虚拟屏;主屏回来还要实际看窗口有没有回去。
最后我会得到什么
已经有屏幕识别、等待、空闲检查和窗口回迁的实现与测试;还没有真实证明拔线后手机画面不中断或所有窗口都能回到原处。
正常时
代码已覆盖应当保持主屏、何时尝试虚拟屏与如何计划窗口回迁;真实手机画面和拔线前后对照仍未验收。
发现问题时
9 月 18 日这次开机已有图形故障记录,所以当时停止捕获和显示写入;手机是否连接仍未知,更早的画面观察不能替代本次。
入口不可用或证据不足时
虚拟屏不唯一或驱动身份不清时停止切换,不把画面送到猜测的屏幕。
从哪里开始
在已配置的 Sunshine 守护环境中查看当前捕获目标;实际切换由守护器在主屏确实离线且串流空闲时进行,本页不能代替真实画面验收。
需要准备什么
- 要远程看的主机与屏幕
- 是否希望主屏离线时使用专用虚拟屏
- 窗口位置异常时指出现象
从开始到拿到结果
- 1
先读主机现场
守护器辨认物理主屏、专用虚拟屏和当前有没有人在串流;主屏正常时优先显示它,并拉回误入虚拟屏的普通窗口。
- 2
满足条件才兜底
主屏稳定离线、显卡状态允许且串流空闲后,才记下窗口位置并切到唯一的专用虚拟屏。
- 3
主屏恢复后再读回
条件允许时尝试把窗口和捕获目标拉回主屏;实际窗口位置与手机画面还要分别检查。
技术实现与依据
这里保留实现、关键条件、精确入口、历史记录和验证结果。
worker新鲜但GPU证据阻断,客户端Unknown(未验证);真实切换和手机验收仍未完成关键规则与设计选择
Sunshine 捕获源 output_name 只使用通过 PnP(即插即用设备)/EDID(扩展显示标识数据)计算的 UUIDv5(基于命名空间的稳定标识),不使用容易随热插拔改变的 \\.\DISPLAYN 编号。
物理主屏连续缺失 15 秒(排除瞬时休眠与驱动重置)且串流空闲 5 秒才切到 VDD,防止正常看视频时发生误切。
记录普通窗口的 HWND(窗口句柄)、PID(进程标识)与真实放置矩形;11–13 像素只是当前常见观测,代码按窗口 DPI 动态计算边框容差,取不到时回退 16 像素。
LIAN LI 水冷屏(TUR0000)与 HS2 机箱屏严格列为本项目黑名单:捕获选择和本项目的窗口迁移动作不以它们为目标。
主屏优先守护是日常默认;人工无头入口只在明确应急选择时写配置并留备份,不负责重启、手机验收或恢复日常策略。
本模块用到的名词
- EDID(扩展显示标识数据)
- 显示器提供的身份信息;项目把它与同一次 PnP/活动输出证据组合为稳定标识,并在每次操作前重新核对。
- CAS(Compare-And-Swap)
- 原子替换机制;写入配置文件前先校验原有内容是否被修改,防止多进程并发冲突覆写。
- RTSP 空闲门(Idle Gate)
- 会话须明确Idle并连续5秒才累计安全空闲;查询失败、RTSP监听缺失或进程不符为Unknown(未验证),不会当没有客户。实际端口来自当前base-port,不固定猜默认值。
专业定义
它处理“远程端只剩壁纸、窗口落在看不见的虚拟屏”这类事故:先确认物理主屏真的离线,再决定是否允许 failover(故障转移)。
解决什么
防止多屏混用与关屏串流时,failover(故障转移)状态机出现画面丢失、窗口错位、副屏被夺取或显卡驱动连锁崩溃。
当前怎样实现
- sunshine-capture-failover.psm1 实现活动显示快照采集、EDID 校验、Sunshine 配置 CAS(比较并交换)原子更新与 Win32 窗口位置控制。
- Invoke-SunshineCaptureFailover.ps1 作为常驻轮询工作器(3 秒稳态轮询,等待期 250ms 快速复核),用全局互斥锁避免重复实例,并让隐藏父启动器监视生命周期。
- 提交时把 pending(待处理)与 applied(已应用)状态分开:配置 CAS 后在同一 20 秒期限内重启并检查服务,失败恢复配置 preimage(变更前内容);窗口逐个复核身份、来源区域与写后位置,单个失败不阻塞其余窗口。
- Install-SunshineCaptureFailoverTask.ps1 将其注册为当前交互用户登录时触发的 Highest(最高权限)任务,配合 VBS(Visual Basic 脚本)启动器无窗运行并监视父进程。
- Set-SunshineHeadlessConfig.ps1 提供独立的人工应急固定 VDD 路径:只改受管配置键,保留原文件备份,不冒充已重载或已显示。
- tests/Test-SunshineCaptureFailover.ps1 提供 67 项自动化单元与模拟测试,覆盖已建模的身份、去抖、窗口与故障分支。
- 会话检测Active/Idle/Unknown三态,端口随Sunshine当前base-port解析;缺监听、失败或进程身份不符为Unknown(未验证)。短写操作共用互斥,runtime(运行环境)/worker-health.json绑定PID、创建时间、源码SHA和上次完整循环,旧心跳不能续成健康。
执行流程
- 1
常驻守护器每 3 秒获取一次系统显示快照与 RTSP 串流会话状态。
- 2
若物理主屏正常在线,将任何意外漂入或新建在 VDD 区域的普通窗口持续拉回主屏。
- 3
若物理主屏离线,启动 15 秒缺失计时器;计时满且串流空闲后,记录主屏普通窗口几何位置,将 output_name 原子替换为 VDD GUID 并迁移窗口。
- 4
主屏重新被证明健康并通过去抖后,守护程序只对符合身份与区域门的普通窗口执行拉回,再恢复物理捕获目标;实际结果需逐窗口与配置回读。
边界
- 不通过系统级复制/镜像显示器实现画面同步,杜绝模式冲突。
- 不触碰 Shell 桌面底栏、输入法、全屏独占游戏或无法证明归属的未知句柄窗口。
- 当前周期内存在显卡崩溃时,坚决禁止执行任何显示拓扑写操作。
失败与恢复
- 检测到多于一个同名 VDD 或存在未知虚拟驱动
- 拒绝推断目标,执行失败关闭,保持当前配置不动并记录错误日志。
- 当前 Windows 启动发生过 Kernel-Power 41、BugCheck、nvlddmkm 或匹配 WER 图形故障
- 保持BlockedByGpuStability,不切捕获或显示;只有空闲且证明来自VDD、目标是健康物理主屏的普通窗口单向回迁可独立处理,不任意搬窗。
- 单轮显示枚举瞬时失败(如驱动重置)
- 代码把它记为 cycle-transient-failure 并留待下一轮;自动化测试覆盖了不让该异常直接终止守护器的分支。
- 人工无头写入前 VDD 身份或原配置变化
- 拒绝覆盖;若已写入,仅在当前仍匹配本轮结果且 GPU、会话、身份门允许时回滚,未恢复项单独报告。工具本身不自动重启 Sunshine,也不把备份冒充恢复成功。
真实入口
sunshine-capture-failover.psm1核心状态机、窗口几何与原子配置读写引擎
Invoke-SunshineCaptureFailover.ps1常驻轮询执行体与空闲门检测
Set-SunshineHeadlessConfig.ps1人工应急固定 VDD 的只读预览与显式写入入口
tests/Test-SunshineCaptureFailover.ps167 项高强度自动化回归测试套件
如何验证
- 自动化测试 67/67 通过(涵盖设备快照、GUID 绑定、去抖、CAS 写入、BugCheck/WER/GPU 事件门和窗口计划);它们是合成分支,不是物理串流。
- 历史记录:2026年9月4日曾发现 output_name 与活动输出不匹配,并被当次 GPU 稳定门阻断。这是带日期的旧故障;不能拿它替代9月18日的当前启动周期证据,也不能由后来状态文件更新推定真实捕获已通过。
- capture-failover-state.json沿用sunshine.capture-failover-state.v1;本次UpdatedAtUtc为2026-09-14T04:20:38.1960156Z,Mode=Vdd、PendingTargetKind=Physical。状态新鲜不证明捕获选择或窗口迁移已通过。
- 系统计划任务 SunshineCaptureFailover-Interactive 保持 Running 状态。
与其他模块的关系
为整个远程串流系统提供坚固的显示可用性基石;与 VDD 独立显示参数管理及传输层紧密协作。
