,

Pause Ownership in Wallnetic: Who Paused the Live Wallpaper?

Pause Ownership in Wallnetic: Who Paused the Live Wallpaper? — T-Square engineering blog

TL;DR — Wallnetic 1.4.2 has six pause conditions and one rule above them: no lock, wake or power event resumes a pause the user asked for. Getting there took a 1.4.1 regression we shipped, a notification that could never fire and a watchdog that must tell frozen from paused.

Pause ownership in Wallnetic playbackAny of six conditions may pause the wallpaper, but when a lock, sleep or power condition clears, Wallnetic resumes only a pause it claimed itself. — /pause ownership pause conditions · one flag each Display sleep Screen saver Lock · user switch Low Power Mode On battery setting Fullscreen app setting shouldBePaused OR of live flags User Play / Pause hands ownership back Ownership gate pausedByPower Renderers play · pause PlaybackWatchdog every 5 s · stands down while paused · rate 1.0 → each condition is its own flag; the verdict is computed on every read → lock, wake and power events resume only a pause we claimed → lock state re-derived from the window server on every wake and unlock
Any of six conditions may pause the wallpaper, but when a lock, sleep or power condition clears, Wallnetic resumes only a pause it claimed itself.

A live wallpaper has many reasons to stand still — a lock, a saver, another user switching in, a fullscreen game. The hard question comes on resume: who paused it? Wallnetic answers with pause ownership. This is part 3 of our product notes, after what is new in Wallnetic 1.4 and SCORM and H5P in Blink AI.

Six flags, one verdict

Six conditions feed shouldBePaused; PowerManager keeps each as its own flag and recomputes the verdict on every read:

  • Display sleep — NSWorkspace notifications
  • Screen saver — a cross-process notification
  • Screen lock and fast user switching — one session flag, new in 1.4.1; video used to keep decoding behind the login window
  • Low Power Mode — always pauses
  • Battery power — a setting, on by default, with a prompt to keep playing
  • A fullscreen app — a setting, on by default, found by a 2-second window poll

Who owns the pause

Nothing had ever recorded who paused. In 1.4.1 we let lock, wake and saver resumes bypass the “Auto-resume when conditions change” setting — nobody asks for a lock — which exposed the gap on every unlock: pause it yourself, lock, unlock, and it played again. Every time. In 1.4.2, PowerManager claims a pause (pausedByPower) only if the wallpaper was playing; Play or Pause hands ownership back through userDidTogglePlayback(). The pure PowerPauseOwnership type decides:

SituationResumes?
You paused, then locked and unlockedNo — nothing was claimed
Lock, user switch, sleep or saver paused it, then clearedYes, even with auto-resume off
Battery, Low Power Mode or a fullscreen app paused it, then clearedYes, if auto-resume is on
Any condition still activeNo

The rule governs power and session events only. A wallpaper applied by hand, by a playlist or by time-of-day switching starts playing unless a condition blocks it.

The notification that could never fire

Screen-saver pausing predated 1.4.1 and never worked. Its observers sat on NotificationCenter.default, but com.apple.screensaver.didstart is a cross-process name that only arrives on DistributedNotificationCenter. In 1.4.1 we moved them there with .deliverImmediately — by default, distributed delivery is held back while the app is inactive, which a wallpaper app always is when the screen locks.

Notifications also get lost. Every wake, unlock and saver exit calls resyncSessionState(), which re-reads lock state from the window server (CGSessionCopyCurrentDictionary) instead of trusting the event. Since 1.4.2 it also runs at launch, for login items starting behind a locked screen.

Truthful state and the watchdog

  • Truthful state. Until 1.4.2, togglePlayback() marked the wallpaper playing before the power gate swallowed the request, so the menu bar said Playing over a still desktop. Now playbackPlay() reports whether playback started, and the UI says so.
  • The watchdog. A wedged AVPlayer keeps showing its last frame, so a stall looks like a pause. DesktopWindowController samples playback every 5 seconds, but the verdict is the pure PlaybackWatchdog.isFrozen: frozen only if playback is intended, shouldBePaused is false and the clock has not moved. A deliberate pause() stops the timer.
  • Rate, not just progress. Since 1.4.2 each tick also pins a drifting rate to 1.0 — no seek, which would jump back a keyframe.

What was hard

  • A regression no test caught. The 1.4.1 bypass was right for locks and wrong for user pauses; we moved the decision into PowerPauseOwnership, where that exact case is now a unit test.
  • A safety net with a hole. The watchdog stands down while paused, so a flag that never clears is a permanent freeze. Display sleep was the one flag no resync touched — and every resync ran from a resume handler, after the very notification it was meant to back up. We added a clear-only CGDisplayIsAsleep check, including on the existing 2-second poll — no new timer.
  • Failing towards playing. During a normal wake a display can read as asleep for an instant, so the check only clears the flag, never sets it.

What this buys

A wallpaper that stops while the Mac is locked, asleep, behind a saver or switched away, comes back when you return and keeps your own pause through all of it. Six live flags, one ownership bit, a few unit-tested pure rules — the model is small. Next in product notes: what it took to stop Blink AI’s coach from saying “great job” to a wrong answer.

Frequently asked questions

Why does my live wallpaper stay paused after I unlock my Mac?

Either you paused it yourself or a pause condition is still active. Since 1.4.2, unlocking or waking resumes only pauses Wallnetic caused; yours holds until you press Play or a wallpaper is applied. Wallnetic also stays paused in Low Power Mode and, with the default settings, on battery power or behind a fullscreen app.

How does Wallnetic recover a frozen live wallpaper?

A playback watchdog checks each display’s playback clock every 5 seconds. If the clock has not moved on two checks in a row while playback should be running, Wallnetic recovers playback on that display by itself, without reopening the app. It stands down during deliberate pauses, so it never fights a lock or your own Pause.

Working on something similar?

T-Square architects, builds and operates production systems for learning, AI and custom software products. Talk to a senior engineer for a second opinion.

— /more

Keep reading