-
Notifications
You must be signed in to change notification settings - Fork 2
Screen Saver
VirtualPaper's screen saver is not the Windows native screen saver system. It does not register a .scr file, does not touch HKCU\Control Panel\Desktop settings, and does not rely on Windows to detect idle time. Instead, it is a fully self-managed subsystem built from two independent components:
| Component | Project | Role |
|---|---|---|
ScrControl |
VirtualPaper (main service) |
Idle detection, timer, process lifecycle management |
VirtualPaper.ScreenSaver.exe |
VirtualPaper.ScreenSaver |
Fullscreen WPF + WebView2 player process |
Main Service (VirtualPaper)
│
├── RawInputMsgWindow ──────────────────────────────────────────┐
│ (global low-level mouse/keyboard hooks via WM_INPUT) │
│ ▼
└── ScrControl ──────── DispatcherTimer (WaitingTime minutes) │
│ │ Tick │
│ pre-launch checks │ any input
│ ├── IsScreenSaverOn? │ → ResetTimer()
│ ├── primary wallpaper exists? │
│ ├── SHQueryUserNotificationState OK? │
│ └── foreground process NOT in whitelist? │
│ │ pass │
│ ProcessLauncher.Launch() │
│ │ │
│ VirtualPaper.ScreenSaver.exe ◄───────────┘
│ (stdout IPC: msg_wploaded → IsRunning=true)
│ (stdin IPC: cmd_close → App.ShutDown())
│
└── Proc_Exited → CleanupProc() → RestartTimerAfterExit()
ScrControl does not poll GetLastInputInfo. Instead, it hooks into a RawInputMsgWindow — a hidden window that receives WM_INPUT messages for the entire desktop — and listens to four events:
_msgWindow.MouseMoveRaw += MsgWindow_MouseMoveRaw;
_msgWindow.MouseDownRaw += MsgWindow_MouseDownRaw;
_msgWindow.MouseUpRaw += MsgWindow_MouseUpRaw;
_msgWindow.KeyboardClickRaw += MsgWindow_KeyboardClickRaw;Any of these events calls ResetTimer(), which stops the current DispatcherTimer and restarts it from zero. This approach gives sub-millisecond input responsiveness and avoids the polling overhead of a periodic idle check.
When Start() is called, a DispatcherTimer is started with an interval equal to Settings.WaitingTime (minutes). On Tick:
-
IsScreenSaverOn— if the setting was toggled off since the timer started, abort and restart. - Primary wallpaper exists — the screen saver plays the current primary-monitor wallpaper; if none is set, restart the timer.
-
SHQueryUserNotificationState— suppresses launch during:-
QUNS_NOT_PRESENT(user not at desk) -
QUNS_BUSY(full-screen app active) -
QUNS_RUNNING_D3D_FULL_SCREEN(game or D3D app) -
QUNS_PRESENTATION_MODE(presentation running)
-
-
Whitelist — the foreground process name is checked against
_scrWhiteListProcState(case-insensitiveConcurrentDictionary). Match → restart the timer.
All four checks pass → LaunchProc() is called.
The screen saver runs as a separate process, not a plugin or in-process component. This provides full isolation: a crash in the screen saver cannot affect the main service.
Launch command line (built in BuildStartInfo):
VirtualPaper.ScreenSaver.exe
--file-path <path to wallpaper file>
--wallpaper-type <RImage | RVideo | RWeb | ...>
--effect <Bubble | none>
The process has stdin, stdout, and stderr all redirected, and is added to a Windows Job Object (IJobService) so it is automatically killed if the main service crashes.
-
MainWindowconstructor hides the cursor (Mouse.OverrideCursor = Cursors.None). -
OnContentRenderedinitializes a WebView2 environment (isolated user-data dirTempScrWebView2Dir,--disable-web-securityto allow local file access). - WebView2 navigates to
Players/Web/default.html— the same player page used for normal wallpaper playback. -
Webview2_NavigationCompletedcallsresourceLoad(wallpaperType, filePath)andplay()viaExecuteScriptAsync, then writesVirtualPaperMessageWallpaperLoaded { Success = true }to stdout. - The main service reads this message on
Proc_OutputDataReceivedand setsIsRunning = true. -
InitEffect()starts the optional overlay effect (currently: Bubble).
When any user input is detected, ScrControl.ResetTimer() calls StopProc(), which sends a VirtualPaperCloseCmd JSON message over stdin:
private void StopProc() {
SendMessage(new VirtualPaperCloseCmd());
if (_isRunningLock) _nativeService.LockWorkStation(); // optional lock screen
}MainWindow.StdInListener receives cmd_close and calls App.ShutDown(). The process exits, Proc_Exited fires, CleanupProc() resets all state flags, and RestartTimerAfterExit() restarts the idle timer.
The screen saver supports an overlay effect layer rendered on a WPF Canvas on top of the WebView2 content. The effect is selected via the --effect argument.
Available effects:
| Effect | Description |
|---|---|
Bubble |
Spawns colored semi-transparent bubbles that float across the screen. Up to 20 spawn events (every 3 s), each producing a bubble animated with a DoubleAnimation on Canvas.Left and Canvas.Top. Bubbles bounce at screen edges and continue until the screen saver closes. |
none |
No overlay. The wallpaper plays as-is. |
If Settings.IsRunningLock is true, dismissing the screen saver calls LockWorkStation() (Windows API), requiring the user to re-enter their password. This is checked in StopProc() immediately before the close message is sent.
| Aspect | Windows Native | VirtualPaper |
|---|---|---|
| Idle detection |
GetLastInputInfo polled by Windows |
WM_INPUT global hook, event-driven |
| Activation | Registered .scr file in HKCU
|
Custom timer + pre-launch checks |
| Content |
.scr executable |
Any wallpaper type (image, video, web) |
| Overlay effects | None | Pluggable (Bubble, …) |
| Communication | None | Bidirectional stdin/stdout IPC |
| Crash isolation | In-process | Separate process + Job Object |
| Lock on dismiss | Windows setting | Per-config LockWorkStation() call |