This page explains, end to end, how a Web wallpaper actually runs inside Sucrose: how the page is served (a local HTTP server, not file://), how live audio and system data flows from the Backgroundog service across an inter-process channel into your page's JavaScript callbacks, how customization properties are pushed on start and on change, and how mouse and keyboard input reaches your page. It is the developer-facing companion to the creator pages — read it to understand the moving parts behind Create Audio API, Create System API, and Create Property Listener Filters.
A running Web wallpaper spans several separate Windows processes that coordinate over inter-process channels (Sucrose is multi-process by design — see Architecture Overview):
| Process | Role in a Web wallpaper |
|---|---|
Live engine (Sucrose.Live.WebView or Sucrose.Live.CefSharp) |
Hosts the browser, serves the page, runs JavaScript, receives the data bundle, and calls your callbacks. |
| Sucrose.Backgroundog | Captures audio (WASAPI loopback → FFT) and polls hardware/media-session info, then ships a bundle to the engine. |
| Sucrose.Commandog | Launches the engine process (via its Live command) when you apply a wallpaper. The type → engine mapping itself is resolved upstream by the shared Sucrose.Shared.Live.Helper.Run.Start() launch logic (which reads SucroseInfo.json's Type and looks up the configured engine) before the Live command is dispatched to Commandog. |
| Sucrose.Launcher / Sucrose.Portal | UI and tray; trigger apply/restart and push customization changes. |
Only WebView and CefSharp participate in this data channel. The Backgroundog data pump is web-engine-only — MpvPlayer, VlcPlayer, Aurora, Nebula, Vexana, and Xavier do not receive the audio/system bundle. See Choosing Engines.
When you apply a Web wallpaper:
SucroseInfo.json, sees Type is Web (value 2), and picks the configured engine for that type.Source from the manifest, for example Matrix.html).LocalHttpServer) on http://localhost:{port}/ and http://127.0.0.1:{port}/, where {port} is a free loopback port chosen automatically at startup. Content is served over HTTP, not file:// — this matters because some browser security restrictions (modules, fetch, WebGL texture loads) behave differently under file://.Gif, Video, and YouTube types are loaded through generated HTML wrappers (GifContent.html, VideoContent.html, YouTubeContent.html); the Url type points the engine straight at the remote URL.
On initialization, the engine reads your SucroseProperties.json (or an encoded variant) and applies all current property values by calling your SucrosePropertyListener once per property. This means your wallpaper always starts in the user's last-saved configuration — you do not need to read settings yourself. See Create Property Listener Filters and Create Customization Controls.
When the user later changes a control in the Portal, the engine's FileSystemWatcher detects the updated properties file and re-pushes the changed values to the running page via the same call — live, no restart. The trigger file is cleaned up after ~3 seconds.
Independently of properties, the Backgroundog service runs a periodic timer. On each tick it:
Get*Info() collector for each category: Bios, Date, Audio, Memory, Battery, Graphic, Network, Storage, Processor, Motherboard.Backgroundog payload object (ten nullable JSON sub-objects) and serializes it to JSON.Audio specifically comes from WASAPI loopback capture run through an FFT, producing the mono Data magnitude spectrum plus the per-channel DataLeft / DataRight — each MaxSample = 128 samples normalised to [0, 1] (log-spaced 20 Hz–16 kHz) and used by visualizers — see Create Audio API. Hardware and media-session info is polled by the same service — see Backgroundog Service.
On the engine side, the transport's handler receives the JSON, deserializes it back into the Backgroundog payload, and stores each non-null sub-object as a JSON string in the engine's internal fields (AudioData, ProcessorData, MemoryData, …). Then, for each non-empty hook you declared in SucroseCompatible.json, the engine formats your template with the stored JSON (string.Format(hook, json)) and runs it via ExecuteScriptAsync on the WebView2/CefSharp page. That call invokes your JavaScript function:
// You declared: "SystemAudio": "SucroseAudioData({0});"
// The engine runs: SucroseAudioData({ "Data":[...], "DataLeft":[...], "DataRight":[...], "Title":"...", ... });
window.SucroseAudioData = function (obj) { spectrum = obj.Data; /* obj.DataLeft / obj.DataRight = per-channel */ };
If a hook string is empty (or you never declared it), the engine does not fire it — so you only pay for the data categories you request.
When the desktop is focused, mouse and keyboard activity is forwarded into the page. The browser engines reference Sucrose's raw-input layer, translating raw input into Win32 messages (and a synthetic scroll script) so that standard web events — mousemove, click, wheel, keydown — fire inside your wallpaper. This is what makes Web wallpapers interactive (for example, the Fluid Simulation Showcase reacts to the cursor). See Type-Web.
The bundle that crosses the channel is the same regardless of transport — ten nullable JSON sub-objects:
Bios, Date, Audio, Memory, Battery, Graphic, Network, Storage, Processor, Motherboard
Each non-null sub-object is re-serialized on the engine side and fanned out to its matching SucroseCompatible.json hook. The Audio sub-object is the audio-reactive channel; the other nine are the System Status API. Field-by-field documentation lives on Create Audio API and Create System API.
The live-data channel can use one of three interchangeable transports, chosen by the CommunicationType setting (default Transmission). They carry the identical JSON payload — only the carrier differs.
| Transport | Mechanism | Channel name |
|---|---|---|
| Transmission (default) | TCP on the loopback address (127.0.0.1) at a dynamically allocated port (saved to TransmissionPort) |
created on demand |
| Pipe | Named pipe (System.IO.Pipes) |
Sucrose.Wallpaper.Engine.Backgroundog.Pipe |
| Signal | JSON file + FileSystemWatcher under %AppData%\Sucrose\Cache\SignalT |
Backgroundog.sgnl (with GUID-suffixed instances) |
The engine picks the transport from CommunicationType and stands up exactly one server; Backgroundog reads the same setting (and the engine-set *Required/TransmissionPort flags) to know which client to use. Full details, including the Launcher tray-control channel, are on the IPC page.
[Sucrose.Backgroundog] [Sucrose.Live.WebView | CefSharp]
periodic timer
collect Bios/Date/Audio/Memory/Battery/
Graphic/Network/Storage/
Processor/Motherboard (JObject)
build Backgroundog payload -> JSON
send over CommunicationType transport ----> server receives JSON
(Pipe | Signal | Transmission) deserialize -> Backgroundog payload
store each non-null field as JSON string
for each non-empty SucroseCompatible hook:
string.Format(hook, json)
ExecuteScriptAsync(js) ----> page JS
SucroseAudioData(obj)
SucroseProcessorData(obj)
...
(separately) properties applied on init and on change -> SucrosePropertyListener(name, val)
(separately) raw mouse/keyboard -> Win32 messages -> page events
:::mermaid
sequenceDiagram
participant BG as Backgroundog
participant Eng as Browser engine
participant Page as Wallpaper page
Note over Eng: on apply: start local HTTP server, serve theme
Eng->>Page: SucrosePropertyListener (init values)
loop each tick
BG->>BG: collect 10 categories, build payload, to JSON
BG->>Eng: send over Pipe / Signal / Transmission
Eng->>Eng: deserialize, store as JSON strings
Eng->>Page: for each hook, ExecuteScriptAsync
Note over Page: SucroseAudioData(obj), SucroseProcessorData(obj), ...
end
Note over Eng,Page: property change: FileSystemWatcher then SucrosePropertyListener
Note over Eng,Page: raw mouse/keyboard then Win32 messages then page events
Sucrose*Data callbacks and Sucrose calls them on each tick — there is no API to request a reading on demand.file://. Relative paths, modules, fetch, and WebGL all run against http://localhost:{port}/, so author your page as if it were on a tiny web server.ExecuteScriptAsync work; unused categories are never pushed.SucrosePropertyListener cheap and idempotent — it is called repeatedly, including for live re-application.SucroseAudioData payload (FFT + now-playing)