Skip to content

Screen Saver

PaperHammer edited this page May 22, 2026 · 1 revision

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

Architecture Overview

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()

Idle Detection

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.


Timer & Pre-launch Checks

When Start() is called, a DispatcherTimer is started with an interval equal to Settings.WaitingTime (minutes). On Tick:

  1. IsScreenSaverOn — if the setting was toggled off since the timer started, abort and restart.
  2. Primary wallpaper exists — the screen saver plays the current primary-monitor wallpaper; if none is set, restart the timer.
  3. 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)
  4. Whitelist — the foreground process name is checked against _scrWhiteListProcState (case-insensitive ConcurrentDictionary). Match → restart the timer.

All four checks pass → LaunchProc() is called.


Screen Saver Process (VirtualPaper.ScreenSaver.exe)

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.

Startup sequence inside the process

  1. MainWindow constructor hides the cursor (Mouse.OverrideCursor = Cursors.None).
  2. OnContentRendered initializes a WebView2 environment (isolated user-data dir TempScrWebView2Dir, --disable-web-security to allow local file access).
  3. WebView2 navigates to Players/Web/default.html — the same player page used for normal wallpaper playback.
  4. Webview2_NavigationCompleted calls resourceLoad(wallpaperType, filePath) and play() via ExecuteScriptAsync, then writes VirtualPaperMessageWallpaperLoaded { Success = true } to stdout.
  5. The main service reads this message on Proc_OutputDataReceived and sets IsRunning = true.
  6. InitEffect() starts the optional overlay effect (currently: Bubble).

Shutdown sequence

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.


Dynamic Effects

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.

Lock Screen on Dismiss

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.


Key Differences from Windows Native Screen Saver

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

Clone this wiki locally