Skip to content

Screen Saver.zh.CN

paperhammer edited this page May 22, 2026 · 1 revision

屏保

VirtualPaper 的屏保 不是 Windows 原生的屏幕保护程序。它不会注册 .scr 文件,不会修改 HKCU\Control Panel\Desktop 注册表设置,也不依赖 Windows 来检测空闲时间。它是一个完全自管理的子系统,由两个独立组件构成:

组件 项目 角色
ScrControl VirtualPaper(主服务) 空闲检测、定时器、进程生命周期管理
VirtualPaper.ScreenSaver.exe VirtualPaper.ScreenSaver 全屏 WPF + WebView2 播放进程

架构总览

主服务 (VirtualPaper)
│
├── RawInputMsgWindow ───────────────────────────────────────────┐
│   (全局低级鼠标/键盘钩子,通过 WM_INPUT 消息)                      │
│                                                                 ▼
└── ScrControl ──────── DispatcherTimer(WaitingTime 分钟)       │
        │                       │ Tick                            │
        │               预启动检查                                 │ 任何输入
        │               ├── IsScreenSaverOn?                      │ → ResetTimer()
        │               ├── 主显示器壁纸存在?                       │
        │               ├── SHQueryUserNotificationState OK?      │
        │               └── 前台进程不在白名单?                     │
        │                       │ 全部通过                        │
        │               ProcessLauncher.Launch()                  │
        │                       │                                 │
        │               VirtualPaper.ScreenSaver.exe ◄────────────┘
        │               (stdout IPC:msg_wploaded → IsRunning=true)
        │               (stdin  IPC:cmd_close     → App.ShutDown())
        │
        └── Proc_Exited → CleanupProc() → RestartTimerAfterExit()

空闲检测

ScrControl 不会 轮询 GetLastInputInfo,而是在一个 RawInputMsgWindow——一个接收整个桌面 WM_INPUT 消息的隐藏窗口——上挂钩四个事件:

_msgWindow.MouseMoveRaw    += MsgWindow_MouseMoveRaw;
_msgWindow.MouseDownRaw    += MsgWindow_MouseDownRaw;
_msgWindow.MouseUpRaw      += MsgWindow_MouseUpRaw;
_msgWindow.KeyboardClickRaw += MsgWindow_KeyboardClickRaw;

上述任何事件触发都会调用 ResetTimer(),停止当前 DispatcherTimer 并从零重新启动。这种方式提供了亚毫秒级的输入响应,避免了周期性空闲检查的轮询开销。


定时器与预启动检查

调用 Start() 时,根据 Settings.WaitingTime(分钟)的间隔启动一个 DispatcherTimer。每次 Tick

  1. IsScreenSaverOn —— 若计时期间设置被关闭,中止并重启定时器。
  2. 主显示器壁纸存在 —— 屏保播放的是当前主显示器上的壁纸;若未设置任何壁纸,重启定时器。
  3. SHQueryUserNotificationState —— 在以下状态中禁止启动:
    • QUNS_NOT_PRESENT(用户不在桌前)
    • QUNS_BUSY(全屏应用活动)
    • QUNS_RUNNING_D3D_FULL_SCREEN(游戏或 D3D 应用运行中)
    • QUNS_PRESENTATION_MODE(演示模式运行中)
  4. 白名单 —— 前台进程名称与 _scrWhiteListProcState(大小写不敏感的 ConcurrentDictionary)进行匹配。匹配成功 → 重启定时器。

四项检查全部通过 → 调用 LaunchProc()


屏幕保护进程(VirtualPaper.ScreenSaver.exe

屏保以 独立进程 运行,而非插件或进程内组件。这种完全隔离机制确保屏保崩溃不会影响主服务。

启动命令行(由 BuildStartInfo 构造):

VirtualPaper.ScreenSaver.exe
  --file-path      <壁纸文件路径>
  --wallpaper-type <RImage | RVideo | RWeb | ...>
  --effect         <Bubble | none>

进程的 stdinstdoutstderr 均被重定向,并被添加到 Windows 作业对象(IJobService)中,因此主服务崩溃时屏保进程会被自动终止。

进程内部启动序列

  1. MainWindow 构造函数隐藏光标(Mouse.OverrideCursor = Cursors.None)。
  2. OnContentRendered 初始化 WebView2 环境(隔离的用户数据目录 TempScrWebView2Dir--disable-web-security 以允许本地文件访问)。
  3. WebView2 导航至 Players/Web/default.html —— 与普通壁纸播放使用的同一播放页面。
  4. Webview2_NavigationCompleted 通过 ExecuteScriptAsync 调用 resourceLoad(wallpaperType, filePath)play(),随后将 VirtualPaperMessageWallpaperLoaded { Success = true } 写入 stdout。
  5. 主服务在 Proc_OutputDataReceived 上读取此消息,设置 IsRunning = true
  6. InitEffect() 启动可选覆盖特效(目前仅:Bubble)。

关闭序列

当检测到任何用户输入时,ScrControl.ResetTimer() 调用 StopProc(),该函数通过 stdin 发送 VirtualPaperCloseCmd JSON 消息:

private void StopProc() {
    SendMessage(new VirtualPaperCloseCmd());
    if (_isRunningLock) _nativeService.LockWorkStation(); // 可选锁屏
}

MainWindow.StdInListener 收到 cmd_close,调用 App.ShutDown()。进程退出,Proc_Exited 触发,CleanupProc() 重置所有状态标志,RestartTimerAfterExit() 重新启动空闲定时器。


动态特效

屏保支持一个在 WebView2 内容上层渲染的 WPF Canvas 覆盖特效层。特效通过 --effect 参数选择。

可用特效:

特效 描述
Bubble 生成随机彩色半透明气泡,漂浮于屏幕之上。最多 20 次生成事件(每 3 秒一次),每次通过 DoubleAnimationCanvas.LeftCanvas.Top 上产生动画气泡。气泡在屏幕边缘反弹,持续至屏保关闭。
none 无覆盖层。壁纸按原样播放。

关闭时锁定工作站

Settings.IsRunningLocktrue,关闭屏保前调用 LockWorkStation()(Windows API),要求用户重新输入密码。此检查在发送关闭消息之前的 StopProc() 中执行。


与 Windows 原生屏保的主要区别

维度 Windows 原生 VirtualPaper
空闲检测 Windows 轮询 GetLastInputInfo WM_INPUT 全局挂钩,事件驱动
激活方式 注册的 .scr 文件(HKCU 自定义定时器 + 预启动检查
内容 .scr 可执行文件 任意壁纸类型(图像、视频、Web)
覆盖特效 可插拔(Bubble 等)
通信 双向 stdin/stdout IPC
崩溃隔离 进程内 独立进程 + 作业对象
关闭锁定 Windows 设置 按配置调用 LockWorkStation()

Clone this wiki locally