Where your displays are, and what is drawn on them — one window for both.
Drag your outputs into place and displaywright applies it live with hyprctl,
then writes it back to Omarchy's Lua monitors.lua. Give each display its own
wallpaper and an omarchy-shell plugin draws it, replacing the built-in
background renderer that only ever showed one picture on every screen.
Both halves share one view of your desk, so a display you select or move on one page is the same display on the other.
omarchy plugin add https://github.com/BlackKingBarOrg/displaywright-shell-plugin.git --enableNothing changes yet. The renderer draws on top of Omarchy's own, taking over a display only once you give that display a picture — so the theme background, the SUPER + CTRL + SPACE switcher and the palette that follows your theme all keep working, and a display you never touch is never touched.
Once it is in, double-click a display's background and Omarchy's own
picture picker opens for that display alone. That is the plugin on its own, with
no window and no config file to edit; the file it writes,
~/.config/displaywright/wallpapers.json, is documented in
plugin/README.md for the fits, colours, video and span
that the picker does not cover.
Picture library, a live preview of every fit, and the display arrangement editor:
omarchy install app Displaywright displaywright-gitOr from a checkout, where make install symlinks displaywright onto your
PATH and adds the launcher entry — nothing is compiled:
git clone https://github.com/BlackKingBarOrg/displaywright
cd displaywright && make installThen run displaywright, or search Displaywright in the app menu
(SUPER + ALT + SPACE). It writes the same file the renderer reads, and
make plugin installs the renderer for you if you skipped the command above.
Coming from wallwright or hyprlayout, which this merges: displaywright migrate moves your wallpapers, profiles and renderer across. It never
overwrites anything, and running it twice does nothing.
- Drag to arrange. Tiles snap to their neighbours' edges and centre lines, with no overlaps or gaps on release. Arrow keys nudge 10 logical pixels, Shift makes it 100.
- Per-display settings. Enable, resolution, refresh rate, scale (with an Auto that reads the panel's real DPI from its EDID), rotation, VRR, mirroring, exact X/Y.
- Try before you keep. Apply takes effect immediately, then asks to keep or revert with a 15-second countdown that defaults to revert — a display that goes black cannot lock you out.
- Careful writes. You get a diff before
monitors.luais touched, the old file is backed up, and only displaywright's own managed block is rewritten. - Profiles. Save arrangements and recognise them again by output fingerprint.
- Warnings for overlaps, displays the pointer cannot reach across a gap, and scales that produce a fractional logical size.
Unlike nwg-displays, it speaks Hyprland 0.56's Lua dialect on both ends —
hl.monitor({ ... }) written to disk, hyprctl eval to apply — falling back to
hyprlang keyword on older builds.
- A picture per display, or one spanned across all of them, cut from your real layout and honest about how much falls in the gaps.
- Every fit Windows has, behaving the way Windows' do — including on a scaled display, where most tools get Center and Tile wrong.
- A preview that is not a guess. It runs the same arithmetic the renderer does, so you see what Center will do before committing.
- Displays you have not touched are left to Omarchy's own renderer, so a fresh install looks and behaves exactly like stock Omarchy.
- One folder. Everything you pick is copied into
~/Pictures/Displaywright, so a wallpaper survives you emptying~/Downloads. - Flat colours and video as well as pictures. No Apply button here — a wallpaper is visible the moment it lands.
- Double-clicking a display opens Omarchy's own picker for that display, with or without this window running.
The fit table and the wallpapers.json format are documented in
plugin/README.md.
displaywright # the window
displaywright outputs # the displays Hyprland reports
displaywright layout status|dump|lua|diff|save
displaywright layout builtin on|off|toggle
displaywright layout profiles
displaywright layout profile-save|profile-apply|profile-delete <name>
displaywright wallpaper status
displaywright wallpaper set DP-1 ~/a.jpg [--fit tile] [--backdrop '#101820'] [--no-copy]
displaywright wallpaper set span ~/wide.jpg
displaywright wallpaper color DP-1 '#101820'
displaywright wallpaper clear [DP-1]
displaywright renderer status|install|uninstall
displaywright migrateWorth a keybinding in ~/.config/hypr/bindings.lua:
o.bind("SUPER + P", "Displays and wallpapers", { launch = "displaywright" })
o.bind("SUPER + SHIFT + P", "Dock layout", "displaywright layout profile-apply dock")| Key | Action |
|---|---|
| Arrows / Shift+Arrows | Move the selected display 10 / 100 logical pixels |
| Tab | Cycle the selection on the canvas |
| Ctrl+Return | Apply the arrangement |
| Ctrl+S | Save to monitors.lua |
| Ctrl+R | Re-read displays from Hyprland |
| Ctrl+Z | Discard arrangement changes |
| Ctrl+Q | Quit |
Apply is a trial: it does not survive hyprctl reload. Ctrl+S, or the
Also write… tick in the confirmation, is what makes it stick. Both emit the
same Lua, so the diff you preview is what runs.
Switch the built-in display off while docked and it comes back on its own once no external display is left — whether or not this app is running.
That is not something displaywright invents. Writing disabled = true for a
laptop panel into monitors.lua is a trap: nothing removes it, so the next time
you undock you get a black machine. Omarchy already ships the pieces that avoid
it — internal-monitor-disable.lua, plus the service and hotplug watcher that
delete it — and displaywright writes into those instead, leaving a normal
enabled rule in monitors.lua for Omarchy to restore the panel from.
On a Hyprland without Omarchy it falls back to disabled = true and the sidebar
says so: nothing will switch the panel back on for you.
- Hyprland (tested on 0.56; older hyprlang builds work through the fallback)
- Python 3.11+ and PyGObject with GTK 4 and libadwaita
- For wallpapers: Omarchy 4.x with
omarchy-shell;qt6-multimedia-ffmpegfor video
On Arch/Omarchy: sudo pacman -S --needed python-gobject gtk4 libadwaita
The Displays half works on any Hyprland. Only the renderer needs Omarchy.
- Disconnected outputs do not appear in the UI, because
hyprctl monitors alldoes not report them. Their rules inmonitors.luaare left untouched. - HDR, ICC and colour-management fields of
HL.MonitorSpecare not exposed yet; lines you wrote for them are left alone. - Video wallpapers type-check clean but have not been run on hardware. Treat them as untested.
make test runs the suite; make lint type-checks the QML. Layout, publishing
and the renderer contract are in CONTRIBUTING.md.
MIT — see LICENSE.

