Omahub
← All plugins
E

Sticky Backgrounds

by Elija Sorensen

Remembers the wallpaper you last chose for each theme and restores it on theme switch

Security review

No obvious issues detected

Deterministic scan — not a security guarantee

None
Risk level
None
Analyzed commit
286c888
Scanned
1 month ago

No potentially dangerous behavior detected in the analyzed commit.

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

None
AI risk level
None
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
286c888
Reviewed
1 month ago

Manual review found no dangerous or destructive behavior: the plugin is a transparent wrapper around the stock background renderer, stores remembered wallpaper paths under ~/.local/state/omarchy/sticky-backgrounds, and only repairs a user-level symlink. The bash helper validates theme names to prevent path traversal, runs entirely as the user, and makes no network or privileged operations. This is consistent with the deterministic scan's 'none' result.

How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/WhiskeyTuesday/omarchy-sticky-backgrounds --enable
Appearance #Hyprland #quickshell

Sticky Backgrounds

An Omarchy shell plugin that remembers the wallpaper you last chose for each theme, and puts it back when you return to that theme.

Out of the box, switching away from a theme and back always lands on that theme's first wallpaper, whatever you had picked. This is basecamp/omarchy#7668.

Install

omarchy plugin add https://github.com/whiskeytuesday/omarchy-sticky-backgrounds.git --enable

Remove it with omarchy plugin disable whiskeytuesday.sticky-backgrounds, which restores the built-in renderer, or omarchy plugin remove whiskeytuesday.sticky-backgrounds.

Requirements and dependencies

Omarchy Quattro, which supplies everything this uses: omarchy-shell (Quickshell) and the stock omarchy.background plugin it wraps out of $OMARCHY_PATH. The helper script calls only bash, mkdir, printf, cat and ln, all from coreutils. No third-party dependencies, no network access, no privileged operations.

Enabling this plugin writes omarchy.background into disabledPlugins in ~/.config/omarchy/shell.json. That is Omarchy's own clonedFrom mechanism doing it, not this plugin editing your config behind your back, and omarchy plugin disable reverses it and restores the built-in renderer.

How it works

The plugin does not fork Omarchy's background renderer. Its manifest declares:

"omarchy": { "clonedFrom": "omarchy.background" }

which makes the shell disable the built-in background plugin on enable and restore it on disable, and route calls addressed to omarchy.background here. The plugin then loads the stock Background.qml out of $OMARCHY_PATH as a child object and forwards every IPC call to it, so all of the rendering — the reveal animation, the per-monitor Variants, the double-click handlers — stays upstream's and keeps working across an omarchy update.

Quickshell registers IPC targets first-come-first-served. The wrapper's handler is declarative and registers during its own completion, before the child is created in Component.onCompleted, so the wrapper's handler wins and the child's identical handler is inert. Expect one benign warning in the log:

QML IpcHandler at .../background/Background.qml[131:3]: Handler was registered
but will not be used because another handler is registered for target background

No flash

omarchy-theme-set writes the incoming theme into theme.name before it sends the themeTransition IPC call, and rewrites the background symlink after. Intercepting that call means the substitution happens before a single frame is drawn — the wrong wallpaper is never rendered, not merely covered up.

What gets recorded

Only deliberate selections. Every user-made choice — the background switcher, omarchy theme bg next, omarchy theme bg set — reaches the shell through omarchy-theme-bg-set, which sends the background set IPC call. That is the only writer of the symlink besides omarchy-theme-set itself, so it is the only place a selection needs recording. A theme you have never picked a wallpaper for keeps stock behavior.

State lives in ~/.local/state/omarchy/sticky-backgrounds/<theme-slug>, one path per file.

Resolution order

  1. The remembered path, if it still exists. This is the case essentially always: a theme's memory is only ever read back while that theme is staged, and current/theme/backgrounds/ is rebuilt with identical filenames on every switch, so the recorded path is alive exactly when it is consulted. It also covers user wallpapers under ~/.config/omarchy/backgrounds/<theme>/ and wallpapers chosen from outside any theme directory.
  2. The same basename inside that theme's background directories. A belt-and-braces fallback for a wallpaper that moved between directories but kept its name. It does not rescue a theme update that renames files, since the basename changes too.
  3. Nothing — Omarchy's own choice stands.

Known wart

omarchy-theme-set rewrites the background symlink to its pick as soon as the IPC call returns, so the plugin cannot win that write; it repairs the symlink on a 400 ms timer instead. The symlink has to end up correct because omarchy theme bg next derives its cycle position from it and the lock screen reads it for the blurred wallpaper. Losing the race is cosmetic and self-correcting — the rendered wallpaper is right either way, and the next selection rewrites the link — but it is a race, and it is the main reason this belongs upstream rather than in a plugin.

Testing

./test/run exercises the resolution rules directly, with no Quickshell and no live desktop:

$ ./test/run
resolution:
  ok  unknown theme resolves to nothing
  ok  existing path is returned verbatim
  ok  vanished staged path resolves to nothing while unstaged
  ok  vanished staged path re-resolves by basename once restaged
  ok  basename fallback prefers the user dir
  ok  external path survives
safety:
  ok  traversal theme name writes nothing
  ok  traversal theme name resolves to nothing
  ok  empty theme name resolves to nothing
filenames:
  ok  glob metacharacters in filename
  ok  spaces in path

Status — this plugin expects to be made obsolete

It is a deliberate stopgap. The correct fix is in omarchy-theme-set itself, where the wallpaper is chosen before any of this machinery runs, and basecamp/omarchy#7718 proposes exactly that. If that PR merges, uninstall this plugin.

Upstream's version is strictly better: it is flash-free by construction rather than by interception, and it needs no symlink repair because the symlink is written from the corrected choice. See the wart above.

The two do not corrupt each other in the meantime. State lives in ~/.local/state/omarchy/sticky-backgrounds/, deliberately not the theme-backgrounds/ directory that PR introduces. And if both were ever active at once they would agree rather than fight: omarchy-theme-bg-set resolves the path with realpath before sending it, that PR records it with readlink -f, so both remember the same file. choose_theme_background would then pass the remembered wallpaper as finalPath, this plugin would find its own record identical, and it would forward the call unchanged without substituting or repairing anything.

That is precisely why it should still be removed: it degrades to a pass-through that keeps the built-in renderer disabled for no benefit, and any upstream change to Background.qml's function signatures would then break forwarding for nothing gained.

License

MIT