Customising it
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 |
w |
Where more come from — opens the wallhaven browser below, and the header's [w] Wallhaven button is the same door |
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.
Where more come from: wallhaven.cc (Super+Ctrl+W)
The picker lists what is already on the disk. This is a grid of what is on
wallhaven.cc — the same window opens from Super+Ctrl+W, from w or
the [w] Wallhaven button in the picker, from the Wallhaven row at the
bottom of the picker's built-ins, and from synui-wallhaven at a prompt.
w goes back the other way too: the browser's header carries a Wallpapers
button on the same key, so one key flips between what is on the disk and what is
not. Whichever window you leave closes — both are full-screen and both want the
keyboard, and two of them at once is a picker nobody can drive.
One monitor: the one you are on. synui names the focused screen when it
opens the browser, because no Wayland protocol tells a layer-shell window where
focus is — a browser that is never told opens on every monitor at once. Started
from a prompt with no monitor to name, it opens on the first screen;
synui-wallhaven toggle DP-1 names one by hand.
- Category chips — general, anime, people. At least one stays on:
000is a query wallhaven answers with nothing, which on screen is indistinguishable from a network that did not come back. - Sorting — popular, latest, most viewed, random.
←→page,123toggle categories,Scycles the sort,Entersets the highlighted one,wgoes back to the picker,Esccloses.
Picking one downloads it into ~/Pictures/Wallpapers, which is the first
directory the Super+W picker scans, and then sets it through the same
wppick_set_path() every other pick goes through. So a wallpaper taken from
wallhaven is an ordinary local wallpaper from then on: it is in the picker's
list afterwards, it survives this being uninstalled, and there is one definition
of "make this the wallpaper" rather than two.
⛔ Off by default. This is the only part of the wallpaper picker that leaves your machine, and the second thing in the whole desktop that talks to the internet at all — the weather is the other. Nothing here resolves a name until you say so, and the window opens on that question the first time: it says what turning it on costs, carries the Turn on button, and asks wallhaven.cc for nothing at all until it is pressed. The same switch from a prompt:
bash synui-wallhaven on
⚠ Results are sfw and that is not a setting. wallhaven's other two purity
levels need an API key, which means pasting a credential into a wallpaper picker
and keeping it in a config file that a screenshot of the settings window would
leak. One filter, applied, is a smaller promise and a keepable one.
The command line
synui-wallhaven search --sort=toplist --categories=101 # general + people
synui-wallhaven search --rec # the table the window reads
synui-wallhaven get <id> # download only, prints the path
synui-wallhaven set <id> # …and make it the wallpaper
synui-wallhaven on | off | status
⚠ The browser is a quickshell surface, not compositor code, for the same
reason the welcome guide is. A grid of remote thumbnails means HTTP, JSON and
JPEG decoding, and the compositor's event loop is the one place on the machine
where a slow DNS lookup is a frozen desktop — weather.c and news.c each pay
for their network with a worker thread and a stop flag wired into libcurl's
progress callback. A QML Image loads an https URL by itself, asynchronously,
with its own cache.
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.stateoverridessynuirc, deliberately. The picker writes~/.config/synui/wallpaper.stateand synui applies it last, which is what makes a pick survive a restart — a packagedsynuircupdate can't quietly take the wallpaper back. Delete that file to hand control tosynuircagain.
Wallpaper Engine (Steam Workshop)
Steam's own Wallpaper Engine cannot do this, and never will. Under Proton it renders into the Windows
Progman/WorkerWdesktop 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
It is on the ISO as of 0.2.1 — installed already, nothing to build. On 0.2.0 and earlier it was not, and had to be built 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
--screenshotis 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.jsonand 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.isAutoestimate, 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.