Skip to content

Wallpapers

Velle Sinclair edited this page Jul 27, 2026 · 2 revisions

Wallpapers

synui draws the wallpaper itself — nothing is autostarted for it, and there is no swaybg in the session. Super+W opens the picker; Super+Shift+W reloads the current one.

Steam Workshop wallpapers (Wallpaper Engine) are supported through a separate, optional package — see Wallpaper Engine below.


The picker (Super+W)

Key Does
/ (or k / j) Move the highlight — applies live, so you preview by scrolling
Tab Cycle the scope: All monitors, or one screen at a time
m Cycle the scaling mode
r Rescan for images
Enter / Esc / q Close

The rows are, in order:

Row What it is
Synapse The bundled image (/usr/share/synui)
Matrix Animated kanji rain, rendered on the GPU (GLES2 only)
None Flat bg_color
…images Every PNG/JPEG found in ~/Pictures, ~/Wallpapers, /usr/share/backgrounds and friends
…Workshop Subscribed Wallpaper Engine wallpapers, when that package is installed

The image scan re-runs every time the panel opens, so something dropped into ~/Pictures shows up without a restart. There is no file chooser and no text entry on purpose: pointing the picker at the directories people already keep images in beats building a chooser or shelling out to a GTK dialog for one path.

Scaling modes

fill · fit · stretch · center · tile. The mode follows the scope, so one screen can fill while another fits.


Per-monitor wallpapers

Tab in the picker is the whole feature. With the scope on:

  • All monitors — the pick sets the global wallpaper and drops every per-monitor override, so it is also how you get back to one wallpaper everywhere.
  • a single screen — the pick becomes that monitor's override alone.

The same thing from the config file, naming connectors the way synctl outputs spells them:

wallpaper             = default        # default | matrix | none | /path/to/img
wallpaper_mode        = fill           # fill | fit | stretch | center | tile

wallpaper_output      = DP-1 matrix
wallpaper_output      = HDMI-A-1 ~/Pictures/ultrawide.jpg
wallpaper_output_mode = HDMI-A-1 fit

A monitor with no line of its own follows the global keys.

wallpaper.state overrides synuirc, deliberately. The picker writes ~/.config/synui/wallpaper.state and synui applies it last, which is what makes a pick survive a restart — a packaged synuirc update can't quietly take the wallpaper back. Delete that file to hand control to synuirc again.


Wallpaper Engine (Steam Workshop)

Steam's own Wallpaper Engine cannot do this, and never will. Under Proton it renders into the Windows Progman/WorkerW desktop window, which does not exist here, and Wine cannot reach synui's scene graph regardless. Its "Apply" is a silent no-op on Wayland. Don't chase it.

What works is linux-wallpaperengine, a native reimplementation that reads the same Workshop assets and paints a wlr-layer-shell surface. SynapseOS packages it together with a control script, synui-wpengine, which is what the Super+W picker drives.

Getting it

The package is not on the ISO — it pulls a ~1.3 GB CEF blob at build time and it is useless without Steam. Build it from the repo:

cd linux-wallpaperengine-pkg
makepkg -si

It needs, at runtime:

  • Steam, with Wallpaper Engine installed (the engine reads Wallpaper Engine's own assets/ tree, which is not redistributable, so it stays where Steam put it), and
  • at least one subscribed wallpaper in ~/.local/share/Steam/steamapps/workshop/content/431960.

With the package installed, subscribed wallpapers appear in the Super+W picker alongside the built-ins, and everything below is optional.

synui-wpengine

Command What it does
synui-wpengine list List subscribed Workshop wallpapers — id, type, title
synui-wpengine set <id> [output|all] Apply a wallpaper and persist it (default: every output)
synui-wpengine off [output|all] Stop the engine on one screen, or everywhere, and hand the background back to synui
synui-wpengine restore Re-apply the saved state — the autostart and post-suspend path
synui-wpengine status What is running, and what is saved
synui-wpengine list                    # 1234567890   scene   Some Wallpaper
synui-wpengine set 1234567890          # every monitor
synui-wpengine set 1234567890 DP-1     # just that one
synui-wpengine off DP-1                # synui's own wallpaper comes back there
synui-wpengine status

One engine process paints every screen named in the state file; a monitor with no entry keeps synui's own wallpaper, because the engine only creates layer surfaces for the ones it was asked for. That is also why off <output> is a restart without that line rather than a signal to a running process.

File
~/.config/synui/wpengine.state One <output> <workshop-id> per line — the saved choice
$XDG_RUNTIME_DIR/synui-wpengine.log The engine's stdout/stderr, truncated on every start
$XDG_RUNTIME_DIR/synui-wpengine.pid · .lock Pid tracking and the serialisation lock
Variable For
SYNUI_WPENGINE_BIN Point at an uninstalled build tree instead of the packaged binary
SYNUI_WPENGINE_FPS Engine frame rate (default 30)
STEAM_ROOT Non-default Steam library root

Why a layer-shell client can cover the wallpaper at all

synui_main.c creates layer_tree[BACKGROUND] after wallpaper_init(), so the background layer sits above wallpaper_tree and any layer-shell client covers what wallpaper.c drew. synui advertises everything the engine needs: layer-shell v4, xdg-output, foreign-toplevel. (synuirc already documented this for swaybg, which is what it was verified against before any of this was built.)

What renders and what doesn't

  • Scene and video wallpapers render correctly, full-screen, across a multi-monitor layout — including rotated outputs.
  • Web wallpapers render black. CEF initialises — the packaging bugs that stopped it are fixed — but the texture comes back empty. It is upstream, in the CEF→GL path: the engine's own --screenshot is black too.
  • Some Workshop entries are presets or editor asset packs, not wallpapers. They live in the same folder and can never render; the picker reads project.json and skips them rather than offering a row that does nothing.
  • The occasional scene mis-frames itself (half scale, one quadrant). That is that wallpaper's own camera.projection.isAuto estimate, not a general break.

The engine cannot survive losing its surfaces

After a suspend/resume, or a monitor going away and coming back, every screen would fall back to synui's static wallpaper with linux-wallpaperengine still running, at 0% CPU, blocked in poll() forever.

That is not fixable from synui's side. In the engine, handleGlobalRemoved is a literal // todo: outputs no-op, onLayerClose() frees the viewport's EGL surface and layer surface and erases it, and the only caller of setupLS() runs once at init — so a wl_output that appears later gets a viewport but never a layer surface. Once it loses them it has none for the rest of its life.

So synui re-runs synui-wpengine restore (pkgrel 196) from two triggers — logind's PrepareForSleep(false), and an output arriving after one was lost — coalesced into a single 2.5 s timer. The delay is not cosmetic: restore drops any state line naming an output synctl outputs can't see, so firing it mid-reprobe would persist a partial layout.

If you ever see it anyway, the manual cure is one command:

synui-wpengine restore

Three traps worth knowing

pgrep -x linux-wallpaperengine never matches. /proc/PID/comm is capped at 15 characters, so it reads linux-wallpaper. synui-wpengine keeps a pidfile and only falls back to the truncated name.

--silent is not silent to PipeWire. It mutes the engine's own mixer; SDL still opens a playback stream that sits at state: running, which held synui's idle inhibitor forever and killed dim/blank/lock on every boot. Fixed on synui's side, in the media inhibitor (pkgrel 195), not here.

An flock belongs to the open file description, not to the fd. The engine is a daemon, and it inherited the script's lock fd — so every later pick sat out the full 30-second timeout and then proceeded unserialised anyway, which is the exact race the lock exists to prevent. The launch closes fd 9 explicitly (9>&-).

Two more, both benign: Failed to initialize GLEW: No GLX display (it falls back to EGL), and NVIDIA needing __GL_THREADED_OPTIMIZATIONS=0, which the script sets for you.


See also: The Desktop, Window Effects, Keybindings, Commands, Troubleshooting.

Clone this wiki locally