Customising it

Wallpapers

Edit on GitHub · last edited

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: 000 is 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, 1 2 3 toggle categories, S cycles the sort, Enter sets the highlighted one, w goes back to the picker, Esc closes.

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.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

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 --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.