Skip to content

Add multi-display wallpapers, portrait support, and a display-aware Wallhaven browser - #137

Closed
GitmanIII wants to merge 12 commits into
omacom:mainfrom
GitmanIII:add-multi-display-support
Closed

GitmanIII wants to merge 12 commits into
omacom:mainfrom
GitmanIII:add-multi-display-support

Conversation

@GitmanIII

@GitmanIII GitmanIII commented Oct 7, 2026 •

Copy link
Copy Markdown

Summary

Adds multi-monitor support to Aether: it reads the Hyprland layout, assigns a wallpaper per display (so a portrait monitor gets a native image instead of a crop), draws the palette from one display or an equal blend of several, and can find wallpapers for a display by aspect ratio and resolution. It ships its own per-display background service so it works with no external dependency, and falls back to the original single-wallpaper behaviour when that service isn't present.

Multiple displays

  • Reads hyprctl monitors all -j: name, make/model/serial, position, scale, rotation, native resolution. Rotated outputs are treated as portrait; monitors are labelled Monitor 1, 2, … in layout order.
  • Editor: the monitor layout overlays the wallpaper. Click a monitor to elect it — the existing tools (edit/crop, blur, eyedropper, change) act on it — click an empty monitor to pick a wallpaper for it, or × to clear. The hero and all its tools are preserved.
  • Palette source: Individual (extract from one monitor; selecting extracts immediately) or Blended (tick any subset; each weighted equally 1/N regardless of resolution).
  • Wallhaven: a "Choose for display…" picker sets that display's aspect ratio + resolution and turns each card's button into "Set for Monitor N", auto-advancing to the next unset monitor. A resolution match toggle switches between an exact resolution and "this or higher".
  • Blueprints store the per-display wallpapers and restore them on apply, so a saved theme carries "Monitor 1 = A, Monitor 2 = B".

The per-display background service

contrib/quickshell/background is a clone of omarchy.background that keeps the stock global background, reveal transition, theme payload, and desktop double-click, and adds:

background setForScreen
background clearForScreen
background assignments

An output uses its assignment when it has one and the global background otherwise, so with no assignments the behaviour is identical to stock. It is installed by the existing plugin installer (make install-omarchy-plugins) alongside the wallpaper/blueprint selectors. Missing assignment files fall back to the global background instead of a blank desktop.

Global actions (the desktop wallpaper selector and the theme switcher) clear per-display assignments, so the chosen wallpaper/theme applies to every monitor. Aether's own per-display assignments use stable paths and are not affected.

Fallback

When no per-screen service is present, the per-display UI is hidden and Aether behaves exactly as before. Aether also works with any other service that implements the same IPC (e.g. the Backdrop plugin).

Saving / applying

The primary editor action is now Apply (apply only); Apply ▾ → Save as new blueprint… (Ctrl+J) saves one, so the default flow no longer writes into Omarchy's theme directory. (The old "save as new folder" dialog is now unreachable and can be removed in a follow-up.)

Blueprint names are now case-insensitive, matching how Omarchy lowercases theme names: saving Cyber3 and cyber3 updates one blueprint instead of creating two, and deleting either removes all of its case variants. This also fixes pre-existing duplicate names that could not be deleted.

Testing

  • go test ./internal/... ./cli/... and go vet pass, with new tests for monitor parsing/orientation/logical size, display key matching, assignment decoding, equal-weight blending, hotplug event parsing, the wallhaven query params, blueprint displays, and case-insensitive blueprint names.
  • npm run check: 0 errors, 0 warnings.
  • npm test: 139 pass.
  • Manual on Omarchy (Hyprland, HDMI-A-2 4K@1.5× + DP-4 1440×2560 portrait): per-display assignment, portrait rendering, Individual/Blended, blueprint save/apply, Wallhaven filters, monitor hotplug.

Could this live in Omarchy itself?

Per-display rendering needs a per-screen background service, and Aether bundles one so the feature works today with no external dependency. But the interface is only three methods — setForScreen, clearForScreen, assignments — on the existing background IPC target. If Omarchy's own omarchy.background adopted them, this bundled plugin would become unnecessary and per-display would work out of the box. Aether already talks to that IPC, so it would keep working unchanged.

Read the monitor layout from Hyprland with `hyprctl monitors all -j` and
surface it in a new Displays tab. Each monitor reports its orientation
(portrait or landscape), scale, native resolution and position, drawn to
scale so rotation is visible at a glance.

Assign a wallpaper per display through a per-display background service
that keeps Omarchy's `background` IPC target and adds `setForScreen` and
`clearForScreen` (for example the Backdrop plugin). Aether matches
assignments by the serial-backed `make:model:serial` key and falls back to
the connector name. Without such a service the tab still reports the layout
and applies the single global wallpaper as before.

Any display's wallpaper can be used as the color-extraction source, so a
theme can be generated from either monitor.

- internal/platform: parse hyprctl monitors, orientation, logical size
- internal/omarchy: display model, key matching, per-screen IPC integration
- internal/omarchy, internal/platform: unit tests for parsing and keys
- frontend: Displays tab, store, thumbnails and regression tests
- docs: docs/displays.md and README feature list
A per-display background service may resolve a display by its connector
name rather than the serial-backed key. Backdrop stores whatever key it is
given, but looks displays up serial-first then connector, so writing only
the serial key left a stale connector entry winning and the wallpaper never
changed.

Pass the display's full key list to the service and apply the wallpaper (or
clear it) for every candidate key. This keeps a display's state consistent
regardless of which key the service prefers.
Wallhaven can filter by aspect ratio and exact resolution. Add both to the
search params and the filter bar, and let a connected display fill them in:
picking a display maps its physical resolution to the nearest wallhaven
ratio (1440x2560 -> 9:16, 3440x1440 -> 21:9) and sets the exact resolution,
so portrait monitors can find native portrait wallpapers.

- internal/wallhaven: ratios/resolutions query params + test
- frontend: aspect helpers, wallhaven store state/persistence, filter UI
…refresh

- Monitors overlay the hero in the editor; clicking one elects it, which drives
  the existing wallpaper tools (edit, blur, eyedropper, change).
- Palette source panel: Individual (extract from one monitor) or Blended (mix
  any subset equally). Selecting a monitor extracts immediately.
- Wallhaven: choose a monitor to assign wallpapers to it and stay in the
  browser; the "set" state is session-only and auto-advances to the next monitor.
- "Use", "apply without changing palette", and the hero's change all update the
  elected monitor's assignment, keeping the hero and per-display state in sync.
- Equal-weight blending: every image contributes 1/N samples regardless of size.
- Hyprland monitor hotplug watcher refreshes the display list live.
- Remove the redundant Displays tab; the layout preview now lives in the editor.
The display picker, monitor overlay, palette-source panel, and per-monitor
assignment on Use / change-wallpaper all require a background service that
implements setForScreen. Gate them on the per-screen capability so Aether
falls back to the original single-wallpaper behavior when only the stock
renderer is present, instead of showing controls that error.
Add contrib/quickshell/background: an omarchy.background clone that keeps the
stock global background, reveal transition, theme payload, and desktop
double-click, and adds per-display assignments through
setForScreen/clearForScreen/assignments. An output uses its assignment when it
has one and the global background otherwise, so with no assignments the
behavior is identical to stock. Missing assignment files fall back to the
global background instead of a blank desktop.

The desktop global actions (wallpaper selector and theme switcher) clear
per-display assignments, so they reset every monitor to the chosen background.

Wire the service into the plugin installer so per-display works with no
external dependency.
- Wallhaven: a "=" / "≥" toggle next to the resolution dropdown switches
  between an exact resolution and "this resolution or higher". Wallhaven
  ignores `resolutions` when `atleast` is also sent, so only one is ever set.
- Editor: the Apply theme menu's "Save as new folder" becomes "Save as new
  blueprint", opening the blueprint dialog instead of writing an Omarchy theme
  folder. Ctrl+J and the command palette follow.
With the resolution shown to the left, '<= reads this resolution or
higher', matching the mathematical reading of (resolution) <= value.
Rewrite docs/displays.md for the shipped background service, the editor
monitor overlay, Individual/Blended palette sources, and per-display
blueprints. Note the service in the shell plugin docs and README.
…folder

"Apply theme" becomes "Apply blueprint": it names a blueprint, then applies
the theme, so the primary flow no longer writes into Omarchy's theme
directory. The Apply menu keeps a blueprint save without applying, and
Ctrl+Enter still applies only.
Apply applies the theme; the arrow menu's "Save as new blueprint…" saves
without applying, so applying a loaded blueprint no longer forces a save.
@bjarneo

bjarneo commented Oct 8, 2026

Copy link
Copy Markdown
Member

Hey, this is not what Aether is made for, Aether is made to create themes for Omarchy, not handle lifecycles like backgrounds etc. That is more the role of a settings application for Omarchy.

@bjarneo bjarneo closed this Oct 8, 2026
@bjarneo

bjarneo commented Oct 8, 2026

Copy link
Copy Markdown
Member

Thanks for the PR and your thoughts. Maybe you should look into building an app tweaking stuff like this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants