cosmix-wallpaper

cosmix-wallpaper is a native Bevy background client. It uses a click-through layer-shell surface on each output, obtains window geometry from comp over the ABP Bus, and keeps simulation coordinates in output-local logical pixels. The renderer is independent of the compositor and Quoin.

User preferences

The wallpaper Bus service exposes wallpaper.props.get, .list, .describe and .set, plus .watch. Get/describe accept path; list accepts prefix. Omitting a get path returns the preference tree. Set accepts one leaf path and a typed value. Writes accept registered local callers and admitted mesh services over ABP. For mesh writes, the recipient's noded must stamp broker_origin=mesh, the admitted broker_peer and originating broker_service; the sender must be that peer's registered bridge-PEER. An origin label alone is insufficient. Missing or conflicting identity fields fail without changing preferences. These are broker-owned delivery fields, not parameters callers should supply.

PathDefaultAccepted values
enabledtrueDisabling removes the background surfaces; enabling recreates them
pausedfalseExplicit pause, independent of automatic lock/scene pauses
presetoceanocean, ember, twilight colour palettes
flock.count192Integer 0–1024 per output
speed805–300 logical pixels per second
pointer.radius1100–600 logical pixels
window.margin120–100 logical pixels
fps_limit30Integer 1–60; compositor callbacks may reduce the actual rate
seed42Unsigned 64-bit integer; changing it restarts the seeded flock

For example, save this as wallpaper-settings.mix and run mix --serve wallpaper-settings.mix --name wallpaper-settings on the desktop node. Serve mode registers the caller before running these writes; an anonymous one-shot caller can read properties but cannot write them.

send wallpaper wallpaper.props.get
send wallpaper wallpaper.props.set path="preset" value="ember"
send wallpaper wallpaper.props.set path="flock.count" value=96
send wallpaper wallpaper.props.set path="paused" value=true
send wallpaper wallpaper.props.describe path="fps_limit"
send wallpaper wallpaper.status
print($result)
quit()

From an admitted mesh node, use the same registered caller and address the desktop node, for example send wallpaper.alpha.bus wallpaper.props.set path="preset" value="ember" (on one line).

A successful write returns {path,old,new,persisted:true}. Invalid paths, types, ranges and failed writes return RC 10 without changing live preferences. The file is staged and synced before atomic replacement; failed staging leaves the previous complete file intact. Scene geometry, pointer samples and bird positions are never saved.

Subscribe to the native wallpaper.props.changed topic before calling wallpaper.props.watch. Watch accepts an optional valid preference path and returns {version:1,topic,instance,event_seq,scope:"",reconcile_ms:1000}. The shared topic always invalidates the whole preference tree; it does not create a per-caller subscription. Notifications have the same envelope, with an opaque process instance and a sequence that advances after a changed value has been saved. Successful set replies include that instance and sequence. No-op or rejected writes do not advance the sequence. Multiple writes can coalesce into one notification; a full outbound queue retains one pending invalidation for retry, and reconnect invalidates subscribers again.

Notifications are non-retained, best-effort hints. Re-read with .get after watching, after a notification or reconnect, and every second to recover from a missed final event. A new process instance resets the sequence. Do not reconstruct preferences by counting events or treating the notification as a snapshot. These counters and the instance are never persisted.

The default file is $XDG_STATE_HOME/cosmix/wallpaper.json, falling back to $HOME/.local/state/cosmix/wallpaper.json. An absolute COSMIX_WALLPAPER_STATE_FILE overrides it. The versioned JSON file contains user preferences. Invalid or unsupported files are left intact and reported in wallpaper.status under persistence_error; startup uses defaults until the user supplies a valid preference write.

Status reports scene readiness, effective pause state, pointer readiness, Bus counters, preferences and persistence errors. A property write being accepted does not prove that a frame has been displayed: rendering remains subject to the compositor's callbacks and output activity.

metrics contains process-lifetime host_updates, submitted_frames, failed_frames and simulation_steps counters, plus cumulative update_wall_ns, max_update_wall_ns and simulation_wall_ns timings. Frame counts count each output submission; simulation steps sum across outputs. Use counter differences over a measured interval for rates. Update timing covers app.update, including renderer waits; simulation timing covers the flock advance call. These are wall durations, not CPU measurements. Status observes completed host updates, so the update answering that request is not yet included. Counters reset when the wallpaper process restarts.

bus.queues.channels reports each bridge channel's current depth, bounded capacity (or null for an unbounded channel), and process-lifetime sampled_peak. bus.queues.latest_topics reports the current number of coalesced topics and its sampled peak. Samples are taken around service updates and on status requests; they can miss short bursts and are not atomic across channels. They exclude worker-local pending requests and socket buffers. bus.dropped_messages counts drops reported by the bridge, separately from intentional latest-topic replacement. Peaks and drop counts reset on restart.

scene.covered_outputs lists outputs whose logical area is completely excluded by window and panel bounds. metrics.suspended_outputs is the current number of output surfaces suspended after a successful clear frame, not a cumulative counter. Coverage can precede suspension while that frame is pending.

Rendering and input

Quoin's right-hand System page includes background controls for all nine preferences. Click a value to toggle it or cycle through common choices; the seed control advances to a new seed. Mix can set any value within the advertised ranges. Controls are disabled while a request is pending or wallpaper is unavailable, and hidden pages cannot activate them. Values come from service readback after saving. The existing Quoin Bus connection performs periodic reconciliation and bounded request timeouts through a one-shot host deadline, including while the panel is hidden.

The Background role has an empty input region, no keyboard interactivity and no reserved workspace area. Birds use a bounded fixed-step simulation and shared flat unlit mesh/material assets. Colour, density and speed changes do not grant the background input focus. Rate changes retain the pending frame callback, so they cannot force rendering through an occluded output.

When window and panel bounds cover an entire output, wallpaper hides its birds and submits one cleared frame before suspending that output's simulation and presentation. This follows the same geometric avoidance policy as ordinary windows; it does not assume their pixels are opaque. Overlapping and tiled rectangles count towards coverage, while narrow uncovered gaps remain active. Other outputs continue animating. Bus reconciliation remains awake, so moving or minimising a window resumes the exposed output without simulation catch-up. Reconfiguration or replacement requires a fresh clear frame. Explicit pause, disable and lock handling retain priority. Diagnostic capture keeps the required frames available until its readback completes.

Closing one output's background role retires only that surface, after the renderer has released its handles. Other outputs and Bus controls keep running. A revoked role is not recreated on the same Wayland output object; unplugging and reconnecting an output supplies a new identity and allows a new background, even when its connector name is reused. Restarting wallpaper also clears these process-local revocations.

The background host dispatches its main and render ECS schedules serially. Small scenes otherwise spend substantial CPU time coordinating worker threads between short systems. Asset work and explicitly parallel render preparation can still use Bevy task pools; drawing remains GPU-backed. The extraction schedule retains its deferred-command barrier before render preparation. Bird visibility and transforms are marked changed only when their values change, avoiding redundant propagation during stable visibility and control-only wakes.

Pointer interaction requires comp's pointer observation and a noded build that stamps the owning service on reserved topic deliveries. The consumer rejects unauthenticated, obsolete-instance and out-of-bounds samples; an expired pointer cannot continue repelling the flock. Window geometry is reconciled from authoritative snapshots, not inferred from application titles.

--capture /absolute/path.png explicitly captures this application's own first output after 20 rendered frames. It does not capture other applications. --version prints the app version.

Live Quoin control verification, daily session installation and broader desktop acceptance testing are still being completed. This manual describes the implemented client interfaces; it is not a claim that every deployment gate has passed.