-
Notifications
You must be signed in to change notification settings - Fork 2
Screen Saver.zh.CN
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:
-
IsScreenSaverOn—— 若计时期间设置被关闭,中止并重启定时器。 - 主显示器壁纸存在 —— 屏保播放的是当前主显示器上的壁纸;若未设置任何壁纸,重启定时器。
-
SHQueryUserNotificationState—— 在以下状态中禁止启动:-
QUNS_NOT_PRESENT(用户不在桌前) -
QUNS_BUSY(全屏应用活动) -
QUNS_RUNNING_D3D_FULL_SCREEN(游戏或 D3D 应用运行中) -
QUNS_PRESENTATION_MODE(演示模式运行中)
-
-
白名单 —— 前台进程名称与
_scrWhiteListProcState(大小写不敏感的ConcurrentDictionary)进行匹配。匹配成功 → 重启定时器。
四项检查全部通过 → 调用 LaunchProc()。
屏保以 独立进程 运行,而非插件或进程内组件。这种完全隔离机制确保屏保崩溃不会影响主服务。
启动命令行(由 BuildStartInfo 构造):
VirtualPaper.ScreenSaver.exe
--file-path <壁纸文件路径>
--wallpaper-type <RImage | RVideo | RWeb | ...>
--effect <Bubble | none>
进程的 stdin、stdout 和 stderr 均被重定向,并被添加到 Windows 作业对象(IJobService)中,因此主服务崩溃时屏保进程会被自动终止。
-
MainWindow构造函数隐藏光标(Mouse.OverrideCursor = Cursors.None)。 -
OnContentRendered初始化 WebView2 环境(隔离的用户数据目录TempScrWebView2Dir,--disable-web-security以允许本地文件访问)。 - WebView2 导航至
Players/Web/default.html—— 与普通壁纸播放使用的同一播放页面。 -
Webview2_NavigationCompleted通过ExecuteScriptAsync调用resourceLoad(wallpaperType, filePath)和play(),随后将VirtualPaperMessageWallpaperLoaded { Success = true }写入 stdout。 - 主服务在
Proc_OutputDataReceived上读取此消息,设置IsRunning = true。 -
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 秒一次),每次通过 DoubleAnimation 在 Canvas.Left 和 Canvas.Top 上产生动画气泡。气泡在屏幕边缘反弹,持续至屏保关闭。 |
none |
无覆盖层。壁纸按原样播放。 |
若 Settings.IsRunningLock 为 true,关闭屏保前调用 LockWorkStation()(Windows API),要求用户重新输入密码。此检查在发送关闭消息之前的 StopProc() 中执行。
| 维度 | Windows 原生 | VirtualPaper |
|---|---|---|
| 空闲检测 | Windows 轮询 GetLastInputInfo
|
WM_INPUT 全局挂钩,事件驱动 |
| 激活方式 | 注册的 .scr 文件(HKCU) |
自定义定时器 + 预启动检查 |
| 内容 |
.scr 可执行文件 |
任意壁纸类型(图像、视频、Web) |
| 覆盖特效 | 无 | 可插拔(Bubble 等) |
| 通信 | 无 | 双向 stdin/stdout IPC |
| 崩溃隔离 | 进程内 | 独立进程 + 作业对象 |
| 关闭锁定 | Windows 设置 | 按配置调用 LockWorkStation()
|