PUBLIC REPOSITORY
By Batty 251 (@batty-251) · Licence: MIT · 1 published version · 84 files · 5.4 MB of source · Last published 2026-09-30
A public project on CodeRook. Browse it at https://coderook.com/batty-251/wallpaper-picker.
Animated wallpapers for Linux, driven by linux-wallpaperengine,
with a Steam Workshop browser and Wallpaper Engine-style compatibility settings.
Ships as a desktop app (Electron + AppImage) with the tested Python core bundled inside.
![four tabs: Installed, Workshop, Compatibility, Settings]
./install-app.sh # builds the sidecar + AppImage, then installs
./install-app.sh --no-build # install whatever is already in release/Installs to ~/Applications/WallpaperPicker-<version>.AppImage with an application-menu
entry and a Desktop icon.
| Tab | What it does |
|---|---|
| Installed | Your local wallpapers as tiles, with previews. Use puts one on your monitors. |
| Workshop | Search the Steam Workshop by trending / most-subscribed / newest, filter by tag, install without leaving the app. |
| Compatibility | Frame-rate limit, edge clamp, pause rules, effects, start-at-login. |
| Settings | Your Steam Web API key and account, and what steamcmd can see. |
Browsing and downloading use two different mechanisms, because Steam does not offer one
that does both.
IPublishedFileService/QueryFiles). It needs a free key from <https://steamcommunity.com/dev/apikey>, pasted into Settings. The key is stored in your own config file (~/.config/wallpaper-picker/config.json) and is never part of the app.The registration page asks for a domain name even for a desktop app. Enterlocalhost — Steam does not validate it (the field is aimed at web developers, so its
help text is misleading for this use). You then confirm the key with the Steam mobile
app, exactly like the steamcmd login, and the account must not be a limited one.
steamcmd with your Steam account. Wallpaper Engine is not in Valve's anonymous-download set: an anonymous request answers ERROR! Download item <id> failed (No match). Items land in steamapps/workshop/content/431960/<id>, which is exactly where the engine looks for --bg <id>.So the app cannot download a wallpaper for you without a Steam login, and it will tell you
so rather than failing silently. If steamcmd is not installed, the Settings tab says so.
# 1. install Valve's steamcmd into the location the app looks in
mkdir -p ~/.local/share/wallpaper-picker/steamcmd
curl -sSL https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz \
| tar xz -C ~/.local/share/wallpaper-picker/steamcmd
# 2. let it update itself once
~/.local/share/wallpaper-picker/steamcmd/steamcmd.sh +login anonymous +quit
# 3. sign in once, so it caches your credentials
~/.local/share/wallpaper-picker/steamcmd/steamcmd.sh +login <your-steam-account> +quitDo not symlink steamcmd onto your PATH. steamcmd.sh finds its own linux32/ client
relative to its own path, so a symlink in ~/.local/bin looks for~/.local/bin/linux32/steamcmd and fails with:
Couldn't find steamcmd at /home/you/.local/bin/linux32/steamcmd, exitingIf you want the short steamcmd command, install a one-line wrapper instead (the app
skips anything on PATH that cannot actually run, so even a stale symlink cannot break it):
cat > ~/.local/bin/steamcmd <<'EOF'
#!/bin/sh
exec "$HOME/.local/share/wallpaper-picker/steamcmd/steamcmd.sh" "$@"
EOF
chmod +x ~/.local/bin/steamcmdStep 3 is interactive: enter your password and Steam Guard code once (if Steam Guard uses
the mobile app, you confirm the sign-in on your phone instead). steamcmd then caches the
credential and downloads work from the app. The app never asks for, stores or transmits
your Steam password; it only runs the steamcmd you have signed in.
wallpaper-picker login-status reports whether steamcmd is installed and signed in.
That signed-in check is a real probe: the app runs steamcmd +login <account> +quit and
reads whether steamcmd reports "Logging in using cached credentials". This is deliberate --
measured on this machine, steamcmd wrote no credential file of its own after a successful
login (it reused the existing Steam config), so inspecting its directory reported "not
signed in" on a machine that was in fact signed in, and blocked a working download. The
Settings tab shows the same result and offers the login command with a copy button.
Wallpaper Engine's Windows panel has quality, anti-aliasing and texture-resolution options.
The Linux engine has no such settings at all — grepping its source forantialias, msaa, render scale or texture quality returns nothing outside vendored
libraries. Rather than show dead controls, this app offers only what the engine honours:
| Setting | Notes |
|---|---|
| Frame-rate limit | Per monitor. The single most effective saving. |
| Edge clamp | clamp / border / repeat. |
| Pause behind full-screen apps | Uses winbar's window list on COSMIC. |
| Pause scope | All monitors, or only the covered one. |
| Stop animating on battery | With a "also below N%" threshold. |
| Never pause for these apps | Matches app id or window title. |
| Particles / parallax / mouse | Big savings on scene wallpapers. |
| Start at login + delay | Writes an XDG autostart entry with a portable sleep delay. |
Audio is deliberately absent: wallpapers are always silent, which is also the largest
CPU saving available.
src/wallpaper_picker/ the Python core (the single source of truth)
steam.py Steam Web API client + steamcmd manager
power.py battery detection
autostart.py login entry
config.py slots, options, per-wallpaper overrides
daemon.py owns the engine processes, pauses, restarts
cli.py JSON CLI — the app's only way in
src/main, src/renderer/ the Electron app (UI only, no wallpaper logic)
packaging/ PyInstaller build of the core
scripts/cdp-check.js verifies the UI through the DevTools protocolThe Electron app holds no wallpaper logic. It shells out to the Python core and reads
JSON, so the daemon, the CLI and the tests all exercise the same code.
Every command the app uses takes --json and prints exactly one JSON object on stdout;
prose never mixes in. Exit codes: 0 ok, 1 failure, 2 bad input, 3 needs setup.
wallpaper-picker state --json # everything the UI needs for its first frame
wallpaper-picker wallpapers --json # the library, with previews and properties
wallpaper-picker browse --json --sort popular --search dragon
wallpaper-picker install 3122339805 --json
wallpaper-picker options --json --set pause_on_battery=trueInstalled is your library as a grid with fuzzy search and sorting. Clicking a
wallpaper opens it in the side panel; nothing is applied from the grid itself. The
panel shows the preview, which monitors it is on, display settings, effects, and the
wallpaper's own properties.
Properties are grouped into categories that collapse, and those categories are the
wallpaper's own: Wallpaper Engine marks groups in project.json with <h3>/<h4>
separator entries carrying an order, and the app nests them exactly as the author
intended. "Show all 64" is therefore not needed — a wallpaper with 72 properties opens as
a handful of named, collapsible sections.
The controls in the side panel currently report state rather than change it (they are
dimmed), so the panel is honest about what it does. Apply and Clear are the real
buttons; Compatibility holds the settings that are wired up.
The minimise button hides the window to a tray icon and wallpapers keep running, because
the engine is a separate process. Left-click the tray icon to reopen it; right-click for
Re-apply / Stop / Quit.
A Wayland session has no systray protocol, so Electron's tray can silently do nothing. The
app checks whether an icon was really created: if there is no tray, minimising leaves the
window open instead of hiding the app where it cannot be reached. app:info reports the
same as tray, and the UI greys the button out when it is false.
The app uses Wallpaper Engine's official icon, not a lookalike. Steam extracts the real
icon from Wallpaper Engine's executable when the game is installed and keeps it assteam_icon_431960.png (431960 is Wallpaper Engine's Steam appid), so the genuine artwork
is already on disk — nothing is downloaded and nothing is redrawn.
packaging/install_wpe_icon.py reads that, scales it with alpha-weighted supersampling, and
writes 512/256/128/64/32/22 px PNGs into resources/:
python3 packaging/install_wpe_icon.py --check # which source would be used
python3 packaging/install_wpe_icon.py # install itIt falls back, in order, to Wallpaper Engine's own logo.png in the Steam librarycache
(cropping the emblem out of the wide wordmark), and then gives up and tells you to install
Wallpaper Engine — it will not silently substitute something else. packaging/make_icon.py
still exists as a hand-drawn stand-in for a machine with no Wallpaper Engine on it, but it
is no longer what ships.
The title bar shows the same artwork inline as a 32 px data URI: the renderer may not read
files and the page's CSP forbids extra sources, so inlining is both the simplest and the
safest way to get the real icon in there.
# tests (no display, no network, no Steam needed)
uv run --with pytest --with pytest-asyncio --with dbus-fast pytest tests -q
# the desktop UI against the source tree
npm install && npm start
# rebuild the bundle
python3 packaging/build_sidecar.py
npx electron-builder --publish neverThis machine cannot screenshot a Wayland window, so the UI is verified through the
DevTools protocol instead: node scripts/cdp-check.js <port> prints what the DOM actually
contains and any console errors. Start the app with --remote-debugging-port=<port>.
MIT — see LICENSE.
PUBLIC REPOSITORY