batty-251/wallpaper-picker

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.

Versions

Files in the latest version

README

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

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

Using it

TabWhat it does
InstalledYour local wallpapers as tiles, with previews. Use puts one on your monitors.
WorkshopSearch the Steam Workshop by trending / most-subscribed / newest, filter by tag, install without leaving the app.
CompatibilityFrame-rate limit, edge clamp, pause rules, effects, start-at-login.
SettingsYour Steam Web API key and account, and what steamcmd can see.

Steam Workshop: what works, and what cannot

Browsing and downloading use two different mechanisms, because Steam does not offer one
that does both.

The registration page asks for a domain name even for a desktop app. Enter
localhost — 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.

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.

Setting up steamcmd (once)

# 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> +quit

Do 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, exiting

If 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/steamcmd

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

Compatibility settings

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 for
antialias, msaa, render scale or texture quality returns nothing outside vendored
libraries. Rather than show dead controls, this app offers only what the engine honours:

SettingNotes
Frame-rate limitPer monitor. The single most effective saving.
Edge clampclamp / border / repeat.
Pause behind full-screen appsUses winbar's window list on COSMIC.
Pause scopeAll monitors, or only the covered one.
Stop animating on batteryWith a "also below N%" threshold.
Never pause for these appsMatches app id or window title.
Particles / parallax / mouseBig savings on scene wallpapers.
Start at login + delayWrites 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.

Architecture

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 protocol

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

The JSON contract

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=true

The app window

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

Minimise to the tray

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.

Icon

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 as
steam_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 it

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

Development

# 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 never

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

Licence

MIT — see LICENSE.

PUBLIC REPOSITORY

Opening batty-251/wallpaper-picker…

Loading the public version history and verified file tree.