# hyprmoncfg — Full Documentation
> Documentation for hyprmoncfg, a visual multi-monitor layout editor and automatic profile switcher for Hyprland.
---
# hyprmoncfg
Canonical URL: https://hyprmoncfg.dev/
Create multi-monitor layouts for Hyprland and switch them automatically on hotplug and lid events.
---
# What is hyprmoncfg?
Canonical URL: https://hyprmoncfg.dev/what-is-hyprmoncfg/
## The problem with Hyprland monitor configuration
Configuring monitors in Hyprland means writing `monitor=` lines by hand. A 4K display at 1.33333x scale is effectively 2880x1620 pixels, so the monitor next to it needs to start at x=2880. Vertically centering a 1080p panel against it means doing division in your head to get the y-offset right. You reload, you're off by 40 pixels, you edit, you reload again. There's no visual feedback until after you've committed to a config.
Then it gets worse. You unplug your laptop, go to a conference, plug into a projector, and you're back to editing config files backstage before your talk. You come home, dock the laptop, and the layout is wrong again.
Existing tools try to help but bring their own problems. Some need a Python runtime and GTK just to draw a layout. Others have no visual editor at all -- profiles are config files you write by hand. Some handle hotplug but break when connectors swap between boots. And none of them check whether Hyprland is actually reading the file they're writing to.
## What hyprmoncfg does differently
**It's terminal-first, not terminal-only.** The TUI has a real layout canvas with drag-and-drop, focused Display and Color controls, picker dialogs, and a workspace planner. It's not a glorified config editor -- it's a spatial tool that happens to run in your terminal.
**One apply engine, everywhere.** The TUI and the daemon use the exact same code path: write the generated monitor config atomically, reload Hyprland, re-read monitor state, verify. No "best effort" daemon behavior. No silent failures.
**Profiles follow your hardware, not your ports.** Each profile stores monitor identity by make, model, and serial -- not by connector name. `DP-1` and `DP-2` can swap all they want. Your layout holds.
**It verifies the generated config is loaded.** Legacy source chains are checked before writing. For Hyprland 0.55+ Lua configs, hyprmoncfg reloads Hyprland and asks the active Lua state to confirm that the generated monitor file actually ran. If it did not, hyprmoncfg rolls the file back.
**One runtime dependency: Hyprland.** Two compiled Go binaries. No Python, no GTK, no GObject introspection, no D-Bus. Install them and you're done. Since it's a TUI, it works over SSH too -- configure monitors on a headless machine from your laptop.
## How it compares
| | hyprmoncfg | Monique | HyprDynamicMonitors | HyprMon | nwg-displays | kanshi |
|---|---|---|---|---|---|---|
| GUI or TUI | TUI | GUI | TUI | TUI | GUI | CLI |
| Spatial layout editor | Yes | Yes | Partial | Yes | Yes | No |
| Drag-and-drop | Yes | Yes | No | Yes | Yes | No |
| Snapping | Yes | Not documented | No | Yes | Yes | No |
| Profiles | Yes | Yes | Yes | Yes | No | Yes |
| Auto-switching daemon | Yes | Yes | Yes | No (roadmap) | No | Yes |
| Workspace planning | Yes | Yes | No | No | Basic | No |
| Mirror support | Yes | Yes | Yes | Yes | Yes | No |
| Safe apply with revert | Yes | Yes | No | Partial (manual rollback) | No | No |
| Hyprland 0.55 Lua config | Yes | No | No | No | Yes | N/A |
| Include-chain verification | Yes | No | No | No | No | No |
| Additional runtime dependencies | None | Python + GTK4 + libadwaita | UPower, D-Bus | None | Python + GTK3 | None |
## Demo
## Screenshots
### Dark theme
### Light theme
---
# Getting Started
Canonical URL: https://hyprmoncfg.dev/getting-started/
## Install
### Arch Linux
```bash
yay -S hyprmoncfg-bin
```
```bash
yay -S hyprmoncfg-git
```
### Fedora COPR
```bash
sudo dnf copr enable paolino/hyprmoncfg
sudo dnf install hyprmoncfg
```
### Nix / NixOS
```bash
nix run nixpkgs#hyprmoncfg
nix profile install nixpkgs#hyprmoncfg
```
```nix
environment.systemPackages = with pkgs; [
hyprmoncfg
];
```
### Gentoo GURU
```bash
sudo eselect repository enable guru
sudo emaint sync -r guru
sudo emerge gui-apps/hyprmoncfg
```
### Void Linux
Unofficial [Blackhole-vl](https://github.com/Event-Horizon-VL/blackhole-vl):
```bash
printf 'repository=https://mirror.black-hole.dev/%s/\n' "$(uname -m)" | sudo tee /etc/xbps.d/00-repository-blackhole.conf
sudo xbps-install -S
sudo xbps-install -S hyprland hyprmoncfg
```
### Build from source
```bash
git clone https://github.com/crmne/hyprmoncfg.git
cd hyprmoncfg
go build -o bin/hyprmoncfg ./cmd/hyprmoncfg
go build -o bin/hyprmoncfgd ./cmd/hyprmoncfgd
```
### Install to `~/.local/bin`
```bash
install -Dm755 bin/hyprmoncfg ~/.local/bin/hyprmoncfg
install -Dm755 bin/hyprmoncfgd ~/.local/bin/hyprmoncfgd
```
## Configure Hyprland
hyprmoncfg needs:
- A running Hyprland session
- `hyprctl` in `PATH`
- A Hyprland config that includes hyprmoncfg's generated monitor file
For legacy hyprlang configs, most setups already have this in `~/.config/hypr/hyprland.conf`:
hyprmoncfg writes a file it creates and owns, `~/.config/hypr/hyprmoncfg-monitors.lua` (or `.conf` on legacy configs), and adds one line at the end of your root Hyprland config to load it:
```lua
dofile(os.getenv("HOME") .. "/.config/hypr/hyprmoncfg-monitors.lua")
```
Loading last is what makes an applied layout final: any monitor rule Hyprland reads afterwards would override it. You do not need to add the line yourself, and nothing you wrote is replaced -- a `monitors.conf` or `monitors.lua` of your own is left alone. `hyprmoncfg doctor` reports the current state if you want to check.
If your Hyprland config is managed by a dotfile tool such as chezmoi or stow, keep that line in your source copy. Otherwise your dotfile tool and hyprmoncfg will keep undoing each other.
Hyprland does not read the generated file automatically, so on apply hyprmoncfg reloads Hyprland and asks the active Lua state to confirm that the file actually ran. If it did not, hyprmoncfg restores the previous file rather than leaving you with a layout that silently did nothing.
If your config files live somewhere other than the defaults:
```bash
hyprmoncfg --monitors-conf /path/to/monitors.conf --hypr-config /path/to/hyprland.conf
hyprmoncfg --monitors-conf /path/to/monitors.lua --hypr-config /path/to/hyprland.lua
```
## Create your first profile
Launch the TUI:
```bash
hyprmoncfg
```

{: .screenshot }
You land on the layout tab. The left side shows your connected monitors as rectangles arranged the way Hyprland currently sees them. The right side keeps hardware information above focused **Display** and **Color** controls for the selected monitor.
Drag monitors on the canvas to rearrange them. Click **Display** or **Color** in the pane border, then select a field to change resolution, scale, position, or color behavior. When the layout looks right:
1. Press `s` to save
2. Type a name like `desk` or `home-office`
3. Press `Enter`
That's it. Your monitor layout is now a named profile.
## Apply a saved profile
```bash
hyprmoncfg apply desk
```
Your monitors rearrange immediately, and a 10-second countdown starts. If the layout looks right, press any key to confirm. If something looks wrong, just wait -- it reverts automatically. This is the same safety mechanism you see on TVs and projectors when you change the resolution.
For scripts and automation, skip the countdown:
```bash
hyprmoncfg apply desk --confirm-timeout 0
```
## Enable automatic switching
The daemon watches for monitor changes and applies the best matching profile automatically. Set it up once and forget about it.
Clean Up Before You Enable The Daemon
hyprmoncfgd scores every profile in ~/.config/hyprmoncfg/profiles/. Old experiments, duplicate layouts, and half-finished saves are part of matching until you delete them.
Before you turn on automatic switching, make sure your profile library reflects real setups you actually want auto-applied:
- Save one profile for each real desk, dock, projector, or travel setup you use
- Delete throwaway profiles you created while experimenting
- Re-save the profile you actually use instead of keeping old variants around
On laptops, you do not need a separate closed-lid profile. Save the profile for the monitors you attach at that desk. When the lid is closed and an external monitor is connected, hyprmoncfg forces the internal laptop panel off for the apply and moves workspaces away from it.
AUR, Fedora COPR, Nixpkgs, and Gentoo GURU:
```bash
systemctl --user daemon-reload
systemctl --user enable --now hyprmoncfgd
```
Void Linux with Blackhole-vl, in legacy `hyprland.conf`:
```text
exec-once = hyprmoncfgd
```
Void Linux with Blackhole-vl, in `hyprland.lua`:
```lua
hl.on("hyprland.start", function()
hl.exec_cmd("hyprmoncfgd")
end)
```
Manual install:
```bash
mkdir -p ~/.config/systemd/user
cp packaging/systemd/hyprmoncfgd.local.service ~/.config/systemd/user/hyprmoncfgd.service
systemctl --user daemon-reload
systemctl --user enable --now hyprmoncfgd
```
Now when you plug in a monitor, unplug one, dock your laptop, or close the lid, the daemon finds the profile that best matches your current hardware and applies it. No interaction needed. The packaged systemd service works with both config formats because the daemon detects the active Hyprland config and writes the matching generated file through the same apply engine as the TUI.
If the daemon ever applies a layout you didn't expect, the most common cause is stale or duplicate profiles in `~/.config/hyprmoncfg/profiles/`. The daemon scores every profile it finds, not just the ones you remember saving. Delete old experiments, keep one profile per real setup, and the matching becomes predictable. See [Daemon Behavior](/daemon/) for the full scoring breakdown.
## Add profiles to your dotfiles
Profiles are stored in `~/.config/hyprmoncfg/profiles/`. Each profile has a canonical JSON file plus generated `.conf` and `.lua` sidecars, so you can keep the layouts as plain Hyprland snippets even if you stop using hyprmoncfg. Add the whole config directory to your dotfile manager and your layouts roam across every machine.
With [chezmoi](https://www.chezmoi.io/):
```bash
chezmoi add ~/.config/hyprmoncfg
```
Your desk at home, your laptop bag setup, your conference projector layout -- all versioned, all portable. The daemon on each machine picks the right profile based on what's actually plugged in.
You never commit the generated `~/.config/hypr/hyprmoncfg-monitors.{conf,lua}`. You commit your profiles. hyprmoncfg writes it for you. Do commit the one include line it adds to your Hyprland config, so your dotfile tool and hyprmoncfg stop undoing each other.
## Next steps
- [TUI Walkthrough](/tui/) -- learn the full editor interface
- [Daemon Behavior](/daemon/) -- understand how auto-switching works
- [Dotfiles Integration](/dotfiles/) -- set up profile portability
- [Command Reference](/commands/) -- every flag and subcommand
---
# TUI Walkthrough
Canonical URL: https://hyprmoncfg.dev/tui/
## Layout editor
When you launch `hyprmoncfg`, you land on the layout tab. This is where you arrange your monitors and tune their settings. The screen is split into two panes:
- **Left**: a canvas showing your monitors as draggable rectangles, positioned the way Hyprland currently sees them
- **Right**: monitor information above switchable **Display** and **Color** controls -- resolution, scale, position, transform, VRR, color management, and more
Drag monitors on the canvas to reposition them. The information and controls update in real time. When you need pixel-perfect placement, use the `Position X` and `Position Y` fields in **Display** instead of dragging.
While the TUI is open, it also refreshes live monitor state in the background. Plugging or unplugging a monitor, docking, undocking, or changing lid state reloads the editor so the canvas matches the current hardware.
The canvas only draws displays that are on and not mirroring another one. Anything it leaves out is named along the top edge, so a monitor you turned off or set to mirror never disappears without a trace.

{: .screenshot }
### Main controls
| Key | Action |
|-----|--------|
| `1` `2` `3` | Switch tabs (layout, profiles, workspaces) |
| `a` | Apply current draft or selected profile |
| `s` | Save current draft as a named profile |
| `r` | Reset from live Hyprland state |
| `?` | Show every key for the tab you are on |
| `q` | Quit |
### Canvas controls
| Input | Action |
|-------|--------|
| Mouse drag | Move the selected monitor |
| Arrow keys | Move by 100px |
| `Shift` + arrows | Move by 10px |
| `Ctrl` + arrows | Move by 1px |
| `Alt` + arrows | Snap beside the nearest enabled monitor |
| `0` | Move the selected monitor to 0,0, where Hyprland's own `position = auto` starts |
| `[` `]` | Select the previous or next monitor |
| `Tab` `Shift+Tab` | Move between the canvas, **Display**, and **Color** |
Dragging snaps to nearby edges on release. `Alt` + arrows place the selected monitor flush left, right, above, or below the nearest enabled monitor and center it on the other axis. Regular keyboard movement remains freeform.
### Display and Color controls
Press `Enter` on any **Display** or **Color** field to edit it:
- **Mode** opens a scrollable picker with every supported resolution and refresh rate
- **Scale**, **Position X**, **Position Y** accept typed numeric values
- **Rotation**, **VRR** cycle through their options with Enter or scroll
- **Mirror** lets you mirror the selected monitor to any other connected display. For a crisp image, set the mirrored monitor's Mode to match the source resolution. If the resolutions don't match, Hyprland upscales the image, which looks blurry
## Save dialog
Press `s` from the layout tab. You'll see a text input and the list of existing profiles.
- Type to filter existing profiles
- Arrow keys to select one (overwrites after confirmation)
- Type a new name and press `Enter` to create a fresh profile

{: .screenshot }
## Profiles
The second tab lists every saved profile and how well it fits the displays that are plugged in right now.
- **Match** is the profile's score against the connected hardware, the same score the daemon uses to pick a profile automatically. A dash means the profile has no display in common with what is connected
- **active** marks the profile your screens are already showing
- **best** marks the highest scoring profile -- the one the daemon would apply on the next hotplug
Selecting a profile fills the right side: its details on top, its monitor arrangement below. The details spell out the score as the arithmetic that produced it, so a surprising number is never a mystery, and they list what the canvas cannot draw -- displays the profile keeps off and displays that mirror another one. On the canvas, a display the profile expects but cannot find is outlined and labelled `not connected`.

{: .screenshot }
| Key | Action |
|-----|--------|
| `↑` `↓` | Select a profile |
| `Enter` | Load the profile into the layout editor |
| `a` | Apply the profile |
| `e` | Edit the profile's exec command |
| `d` | Delete the profile |
| `s` | Save the current draft |
## Workspace planner
The third tab lets you distribute workspaces across monitors. Pick one of three strategies:
| Strategy | What it does | When to use it |
|----------|-------------|----------------|
| `sequential` | Groups workspaces in chunks (e.g., 1-3 on monitor A, 4-6 on monitor B) | You think of each monitor as having "its own" workspaces |
| `interleave` | Round-robins workspaces across monitors (1 on A, 2 on B, 3 on A, ...) | You want next/previous workspace to alternate screens |
| `manual` | Shows every workspace as an assignment you can move between monitors | You need full control over exactly which workspace lives where |
You can also configure:
- **Workspace rules on/off** -- disable them entirely if you manage workspaces yourself
- **Max workspaces** -- how many workspaces to generate rules for
- **Group size** (sequential only) -- how many consecutive workspaces to assign to each monitor before moving to the next. With 2 monitors and a group size of 3, monitor A gets 1-3, monitor B gets 4-6, and so on
- **Monitor order** -- which monitor gets the first batch of generated workspaces
- **Workspace → display** (manual only) -- select a workspace and press `←` or `→` to assign it to a different monitor
There is no fixed workspace or group-size limit. Select **Max workspaces** or **Group size** and press `Enter` to type an exact count; `←` and `→` still make one-step adjustments.
Long manual lists stay navigable: the mouse wheel scrolls three rows at a time, `Page Up` and `Page Down` move by a visible page, and `Home` and `End` jump to the first or last row. Only the visible assignment rows are rendered.
Switching from a generated strategy to `manual` starts with the plan already on screen, so you can adjust only the exceptional workspaces instead of rebuilding the whole layout. In manual mode, changing **Max workspaces** adds or removes numbered workspace assignments.
The right side previews the result twice: **Workspace Plan** lists which workspaces each monitor owns, and **Monitor Layout** paints those same workspaces onto the monitors themselves. Both update as you change the strategy, so you can see where workspace 1 lands before you save.
The workspace plan is stored inside each profile. When the daemon applies a profile, it applies workspace rules too -- layout and workspace assignment in one shot.
## Laptop lids
Internal laptop panels are marked as internal displays in the layout view. The TUI also shows the current lid state when it is available.
Profiles are still profiles for the attached monitor setup, not separate open-lid and closed-lid variants. When the lid is closed and an external monitor is connected, applying a profile forces internal laptop-panel outputs off for that apply and moves workspace rules away from the forced-off panel.
---
# Daemon Behavior
Canonical URL: https://hyprmoncfg.dev/daemon/
## Why a daemon
You save profiles with the TUI. But who applies them when you're not looking?
That's what `hyprmoncfgd` does. It runs in the background, watches for monitor hotplug and lid events, and applies the best matching profile automatically. Plug in a monitor, close the lid, undock your laptop, connect to a projector -- the daemon handles it.
On Omarchy releases that start `omarchy-hyprland-monitor-watch`, the daemon takes exclusive ownership of monitor state. It stops the watcher's exact transient user scope, keeps later copies suppressed, and starts the watcher again when `hyprmoncfgd` exits while Hyprland is still running. This prevents Omarchy's clamshell reconciliation from overwriting an active hyprmoncfg profile.
Static Configs On Omarchy
A generated monitor config cannot provide runtime process ownership by itself. If you use a generated monitors.lua without running hyprmoncfgd, Omarchy’s monitor watcher remains active and may reconcile the laptop panel using Omarchy’s own settings. Disable that watcher separately or run the daemon.
This is especially useful if you move between setups regularly. A conference projector, a coworking space monitor, your desk at home -- each one has different resolution, position, and scale requirements. Save a profile once, and the daemon takes care of it from then on.
## Setup
AUR, Fedora COPR, Nixpkgs, and Gentoo GURU:
```bash
systemctl --user enable --now hyprmoncfgd
```
Void Linux with Blackhole-vl:
```text
exec-once = hyprmoncfgd
```
Manual install:
```bash
mkdir -p ~/.config/systemd/user
cp packaging/systemd/hyprmoncfgd.local.service ~/.config/systemd/user/hyprmoncfgd.service
systemctl --user daemon-reload
systemctl --user enable --now hyprmoncfgd
```
That's it. The daemon is running. The rest of this page explains how it decides which profile to apply and how to troubleshoot it.
## How it works
When the daemon detects a monitor or lid-state change, it runs through these steps:
1. Read the current monitor set from Hyprland
2. Score every saved profile against the connected hardware (see [Profile matching](#profile-matching) below for how scoring works)
3. Pick the highest-scoring profile
4. If the lid is closed and an external monitor is connected, force internal laptop-panel outputs off for this apply
5. Write the active generated monitor file atomically (temp file + rename, so a crash mid-write can't corrupt your config)
6. Tell Hyprland to reload
7. Re-read monitor state and verify the result matches what was intended
If the winning profile is the same one that's already applied, the daemon skips re-applying it. You won't see unnecessary reloads.
When every enabled display is DPMS-off, the daemon treats monitor add/remove events as part of display sleep rather than physical hotplug. It keeps the current profile in place, waits for the displays to wake, and then waits for two seconds without another monitor event before matching once. A real dock or undock that happened while the machine slept is still applied after the monitor set stabilizes.
Suspend gets the same respect. The daemon listens for logind's sleep signal, and a lid close that suspends the machine is treated as suspend, not clamshell: the pending switch is discarded instead of being carried across the nap, because by the time it would run, the lid is open again and acting on the stale close would turn the panel off in your face. On resume the daemon re-reads the physical lid switch, tells Hyprland to wake every display -- so both screens light up from the lid opening, not from your first keypress -- and re-matches once the monitor set settles. Opening the lid wakes the displays the same way. As a last line of defense, every apply re-reads the lid switch first, so a stale cached lid state can never decide what happens to your panel.
The daemon uses the **same apply engine** as the TUI. There is no separate "best effort" code path. If the TUI can apply a profile correctly, so can the daemon.
When the daemon is running, it is also the canonical writer. The TUI, CLI, and integrations connect to `$XDG_RUNTIME_DIR/hyprmoncfgd.sock` and ask the daemon to preview, confirm, revert, save, or delete through the same versioned IPC protocol. If the daemon is not running, the TUI and CLI acquire the writer lock and use the core engine directly.
Interactive profile changes are deliberate overrides. After you confirm one, the daemon keeps it in place until the monitor set or lid state changes. Automatic matching then resumes.
## Profile matching
Profiles are matched by hardware identity (make, model, serial) -- not connector name. This means your layout survives when monitors swap between `DP-1` and `DP-2` across reboots. Each profile is scored against the currently connected monitors:
| Condition | Points |
|---|---|
| Monitor enabled in profile and connected | +100 |
| Monitor disabled in profile but connected | +50 |
| Connected monitor not in the profile | −20 |
| Monitor enabled in profile but not connected | −30 |
| Monitor disabled in profile and not connected | −10 |
Highest score wins. Ties break alphabetically by profile name. A profile that mentions a monitor which is not plugged in pays for it either way, so the profile that describes exactly the connected displays beats a larger profile that happens to include them. The profiles tab shows every score, along with this breakdown for the selected profile.
On laptops, the daemon also reads lid state. UPower is optional, but recommended: with UPower available, lid changes arrive as D-Bus events and the daemon can react immediately. Without UPower, the daemon falls back to polling `/proc/acpi/button/lid/*/state` at `--lid-poll-interval`, which defaults to `1s` and is not available on every system. If neither source exists, lid-aware switching is disabled and monitor hotplug still works.
Lid state is not a separate profile type. Save the profile for the monitor setup you actually have attached. When the lid is closed and an external monitor is connected, hyprmoncfg treats internal laptop-panel outputs like `eDP-1`, `LVDS-1`, or `DSI-1` as forced off for that apply. Saved profiles are not rewritten. If workspace rules target the forced-off internal panel, those workspaces are moved to the first enabled external output in the selected profile.
Every Profile Is A Candidate
The daemon does not know which profiles are “real” and which were temporary experiments. It scores every JSON file in your profiles directory. An old throwaway profile with a high enough score will win over the one you actually want.
If you want reliable auto-switching:
- Save profiles for every real monitor setup you want the daemon to handle
- Keep one profile per setup -- don't accumulate near-duplicates
- Delete experimental profiles when you're done experimenting
- If two profiles tie, the one whose name comes first alphabetically wins
- When auto-switching picks the wrong profile, start by listing the files in `~/.config/hyprmoncfg/profiles/` -- a forgotten profile is almost always the answer
## Run manually
For testing or one-off use:
```bash
hyprmoncfgd
```
### Useful flags
```bash
hyprmoncfgd --debounce 1500ms # wait longer before applying after a plug event
hyprmoncfgd --wake-settle 2s # quiet period after displays wake
hyprmoncfgd --poll-interval 5s # how often to run fallback monitor checks
hyprmoncfgd --lid-poll-interval 1s # how often to run fallback lid checks
hyprmoncfgd --profile desk # always apply this specific profile
hyprmoncfgd --quiet # suppress log output
```
## Forcing a specific profile
Use `--profile ` to bypass automatic matching entirely. The daemon applies this one profile every time, regardless of what's connected. This is useful when you know exactly which setup you're on and want to eliminate any chance of a wrong match.
Stop the running daemon first, then start it with the flag:
```bash
systemctl --user stop hyprmoncfgd
hyprmoncfgd --profile conference-projector
```
The daemon owns a per-session writer lock. A second daemon or direct writer exits instead of fighting over the generated monitor config.
## Logs
```bash
journalctl --user -u hyprmoncfgd -f
```
The log shows every step: which profiles were scored, what each one scored, which one won, what generated monitor config was written, and whether verification passed. This is the first place to look when you want to understand why the daemon picked a particular profile.
Separate Matching From Applying
If you’re not sure whether the daemon picked the wrong profile or failed to apply the right one, test the profile directly with hyprmoncfg apply <name>. If the layout looks correct, the problem is matching, not applying – check the logs and your profile directory.
Filing A Matching Bug
If you think the daemon selected the wrong profile, open an issue and include all profiles from ~/.config/hyprmoncfg/profiles/, not just the one you expected to win. Matching depends on the full candidate set.
---
# Omarchy Quattro Panel
Canonical URL: https://hyprmoncfg.dev/omarchy/
## Displays, handled
The [hyprmoncfg: Multi-Monitor Manager for Omarchy](https://github.com/crmne/omarchy-hyprmoncfg) plugin lets you create multi-monitor layouts for Hyprland in a visual editor and switch them automatically on hotplug and lid events. Open the panel to see the layout that is live right now, the profile that matched it, and whether hyprmoncfg is managing your displays.

If you find the panel useful, I'd appreciate a [star on GitHub](https://github.com/crmne/omarchy-hyprmoncfg). It helps get the word out.
Turn management on and hyprmoncfg switches profiles automatically on monitor hotplug and laptop lid events. Turn it off and display ownership goes cleanly back to Omarchy. **Layout and settings** opens the full spatial editor in Omarchy's centered TUI window.
## Install the panel
Get it from the [Omarchy Plugins marketplace](https://omarchyplugins.com/plugin.html?id=crmne.hyprmoncfg), or install it directly:
```bash
omarchy plugin add https://github.com/crmne/omarchy-hyprmoncfg.git --enable
```
If hyprmoncfg is already installed, the panel connects to its daemon over the local IPC socket and updates as monitor state changes.
If it is missing, open the panel and choose **Install hyprmoncfg**. The panel uses Omarchy's normal presented package flow to install the stable AUR package, enables and starts `hyprmoncfgd.service`, and then opens the layout editor. Arrange your monitors, press `s`, and save your first profile. The panel refreshes as soon as it is ready.
## What the panel controls
- **Managed by hyprmoncfg** enables or disables the user daemon
- **Layout and settings** opens the spatial TUI
- **Profile** shows the active hardware-aware profile and whether it was selected automatically
The panel is a desktop surface for the same daemon and IPC protocol used by the TUI and CLI. It does not maintain another copy of monitor state or write Hyprland configuration on its own.
## When Omarchy still moves your displays
Omarchy manages monitors too, and two managers can disagree. Three things keep hyprmoncfg in charge, all of them automatic:
- hyprmoncfg writes its own `~/.config/hypr/hyprmoncfg-monitors.lua` and adds one line at the end of `hyprland.lua` to load it. Loading last means nothing before it, including Omarchy's clamshell toggle, can override the layout you applied. Your `monitors.lua` is left exactly as Omarchy shipped it
- the daemon stops Omarchy's monitor watcher while it owns your displays
- every apply records the internal panel's scale where Omarchy's clamshell script looks for it, so a lid or wake event brings the panel back at your scale rather than its default
Omarchy's clamshell script also drives Hyprland directly with `hyprctl`, which no load order can outrank. The daemon covers that by putting the whole profile back when something outside hyprmoncfg moves the displays, including a profile you picked by hand.
Check the load order at any time:
```bash
hyprmoncfg doctor
```
If your Hyprland config is managed by a dotfile tool such as chezmoi or stow, keep the line `hyprmoncfg doctor` prints in your source copy. Otherwise your dotfile tool and hyprmoncfg will keep re-adding and removing it.
## Remove it
```bash
omarchy plugin remove crmne.hyprmoncfg
```
Removing the panel leaves hyprmoncfg and your saved profiles installed. Turn off **Managed by hyprmoncfg** first if you also want Omarchy to resume display management.
---
# Dotfiles Integration
Canonical URL: https://hyprmoncfg.dev/dotfiles/
## The idea
You have a desktop, a laptop, maybe a Raspberry Pi. Each one connects to different monitors. But your preferred layouts -- the positions, scales, workspace assignments -- those are yours. They should travel with you.
hyprmoncfg stores profiles in `~/.config/hyprmoncfg/profiles/`. Each profile has a canonical JSON file plus generated `.conf` and `.lua` sidecars. Add this directory to your dotfile manager and every machine gets your full profile library.
The daemon on each machine looks at what's actually plugged in and picks the right profile. Your desktop applies "desk". Your laptop at a conference applies "projector". Your Raspberry Pi applies "tv". Same dotfiles, different hardware, correct layout every time.
## What to commit (and what not to)
**Commit**: `~/.config/hyprmoncfg/` -- your profile library.
**Don't commit**: the generated `~/.config/hypr/hyprmoncfg-monitors.conf` or `~/.config/hypr/hyprmoncfg-monitors.lua` -- hyprmoncfg rewrites it from the selected JSON profile and current monitor state. Committing it causes conflicts between machines with different monitors.
**Do commit**: the one line hyprmoncfg adds at the end of your Hyprland config to load that file. It names no machine-specific path, so it works everywhere, and committing it stops your dotfile tool and hyprmoncfg from taking turns undoing each other.
## chezmoi
Add the config directory:
```bash
chezmoi add ~/.config/hyprmoncfg
```
That's it. Your profiles are now versioned and will be deployed to every machine where you run `chezmoi apply`.
When you save a new profile or update an existing one, tell chezmoi to pick up the changes:
```bash
chezmoi re-add ~/.config/hyprmoncfg
```
(`re-add` updates files that chezmoi already tracks. You only need `add` for the initial setup.)
## Other dotfile managers
The same principle applies to any dotfile manager. The directory to track is:
```
~/.config/hyprmoncfg/
```
With GNU Stow, symlink the directory from your dotfiles repo. With yadm, just `yadm add ~/.config/hyprmoncfg`. With bare git repos, add the path to your tracking.
## A practical example
Say you speak at conferences. At home, you have a desk with an external monitor. On the road, you plug into whatever projector the venue provides.
1. At home, open the TUI, arrange your desk layout, save as `desk`
2. At a conference, plug into the projector, open the TUI, arrange, save as `conference-1080p`
3. At another venue with a 4K projector, save as `conference-4k`
4. Run `chezmoi re-add ~/.config/hyprmoncfg` after each new profile
Now the daemon handles it. Arrive at the venue, plug in, and the right profile is applied before you've even opened your slides. When you get home, dock the laptop, and your desk layout comes back instantly.
No manual config editing. No remembering which `monitor=` lines go where. Just plug in and go.
---
# Virtual Desktops
Canonical URL: https://hyprmoncfg.dev/virtual-desktops/
## The problem with Hyprland workspaces
You have three monitors. You press `SUPER+2` and exactly one of them changes, because in Hyprland a workspace belongs to a single monitor.
That gets in the way when you want scenes. Work is a browser, an editor, and logs across all three screens. Music is something else entirely. You want one keypress to swap the whole set, not three keypresses to swap them one at a time. GNOME and KDE work that way; Hyprland does not.
hyprmoncfg follows Hyprland here. Its workspace planner decides which workspaces live on which display, and stops there -- it will not pretend the compositor has a concept it does not have.
## Use hyprland-virtual-desktops
[hyprland-virtual-desktops](https://github.com/levnikmyskin/hyprland-virtual-desktops) adds scenes properly. It is a Hyprland plugin, a real `.so` loaded into the compositor, so it changes how workspaces work rather than driving them from outside.
```bash
hyprctl dispatch vdesk 2 # every monitor moves together
hyprctl dispatch movetodesk 3 # send a window to another desk
hyprctl dispatch nextdesk
```
## Turn the workspace planner off
hyprmoncfg's planner writes `workspace_rule` entries pinning workspace 1 to this monitor and workspace 2 to that one. virtual-desktops assigns workspaces to monitors itself, differently per desk. Leave both on and whichever ran last wins.
So turn the planner off in the TUI's Workspaces tab, on every profile you use with the plugin. hyprmoncfg then writes no workspace rules at all, and the split is clean: hyprmoncfg puts the monitors where they belong, virtual-desktops decides what appears on them.
You lose nothing. Spreading workspaces across displays is what the plugin does, and it does it per desk rather than once.
## What happens when you dock
hyprmoncfg matches the docked profile, applies the mode, scale, rotation and position for each display, reloads Hyprland, and stops. It does not touch workspaces.
virtual-desktops takes it from there. It notices the monitor set changed and restores which workspace each monitor was showing, using `plugin:virtual-desktops:rememberlayout`. Set that to `monitors` rather than `size` -- it keys on monitor identity, which is what hyprmoncfg matches profiles on, so the two agree about what counts as the same setup.
If the plugin needs a nudge afterwards, every profile has an **Exec** field that runs once the layout is applied and Hyprland has reloaded. Point it at a script:
```bash
#!/usr/bin/env bash
hyprctl dispatch vdeskreset
hyprctl dispatch vdesk 1
```
The command runs directly rather than through a shell, so a `&&` or a pipeline belongs in the script, not in the field.
## A practical example
1. Open the TUI, arrange your docked layout, save it as `desk`
2. Undock, arrange the laptop on its own, save that as `mobile`
3. Turn the workspace planner off on both, in the Workspaces tab
4. Set `plugin:virtual-desktops:rememberlayout = monitors` in your Hyprland config
Dock the laptop and hyprmoncfg puts the monitors back where they belong. Press `SUPER+2` and all of them move together.
---
# Commands and Flags
Canonical URL: https://hyprmoncfg.dev/commands/
## `hyprmoncfg`
Running `hyprmoncfg` with no arguments opens the TUI.
### Commands
| Command | Description |
|---------|-------------|
| `hyprmoncfg` | Open the TUI |
| `hyprmoncfg tui` | Open the TUI (explicit) |
| `hyprmoncfg monitors` | List connected monitors with hardware details |
| `hyprmoncfg profiles` | List saved profiles |
| `hyprmoncfg status` | Show the active profile, daemon state, and connected displays |
| `hyprmoncfg status --json` | Print the stable status schema as JSON |
| `hyprmoncfg save ` | Save current monitor state as a named profile |
| `hyprmoncfg apply ` | Apply a saved profile |
| `hyprmoncfg delete ` | Delete a saved profile |
| `hyprmoncfg doctor` | Check that Hyprland loads hyprmoncfg's monitor config last |
| `hyprmoncfg doctor --fix` | Add or move that include to the end of the Hyprland config |
| `hyprmoncfg version` | Print build metadata |
### Common flags
| Flag | Description |
|------|-------------|
| `--config-dir ` | Override the profile storage directory (default: `~/.config/hyprmoncfg`) |
| `--monitors-conf ` | Override `HYPRMONCFG_MONITORS_CONF` and the default generated monitor config path |
| `--hypr-config ` | Override `HYPRLAND_CONFIG` and the default root config path (`.conf` or `.lua`) |
### Environment variables
Both `hyprmoncfg` and `hyprmoncfgd` use these variables when the corresponding flag is not provided:
| Variable | Description |
|----------|-------------|
| `HYPRMONCFG_MONITORS_CONF` | Generated monitor config path to write and reload |
| `HYPRLAND_CONFIG` | Hyprland root config path used for include verification |
### Apply flags
| Flag | Description |
|------|-------------|
| `--confirm-timeout ` | Seconds to wait for confirmation before reverting (default: 10) |
| `--confirm-timeout 0` | Disable the revert timer entirely |
## `hyprmoncfgd`
The daemon. Runs in the foreground by default.
### Commands
| Command | Description |
|---------|-------------|
| `hyprmoncfgd` | Start the daemon |
| `hyprmoncfgd version` | Print build metadata |
### Daemon flags
| Flag | Description |
|------|-------------|
| `--config-dir ` | Override the profile storage directory |
| `--monitors-conf ` | Override `HYPRMONCFG_MONITORS_CONF` and the default generated monitor config path |
| `--hypr-config ` | Override `HYPRLAND_CONFIG` and the default root config path (`.conf` or `.lua`) |
| `--profile ` | Force a specific profile instead of auto-matching |
| `--debounce ` | Delay before applying after a monitor or lid event (default: 1200ms) |
| `--wake-settle ` | Quiet period after displays wake before reconciling monitor changes (default: 2s) |
| `--poll-interval ` | Polling frequency for monitor fallback checks (default: 5s) |
| `--lid-poll-interval ` | Polling frequency for lid-state fallback checks (default: 1s) |
| `--quiet` | Suppress log output |
## Exit behavior
- CLI commands exit non-zero on Hyprland query failures, invalid layouts, missing profiles, or generated-config verification failures.
- Legacy include-chain failures are reported before writing. For Lua configs, `apply` reloads Hyprland and asks the active Lua state to confirm that the generated monitor file actually ran. It restores the previous file if it did not.
- The daemon exits cleanly on `SIGINT` or `SIGTERM`.
- Only one monitor writer can run per user session. If the daemon owns the writer lock, the TUI and mutating CLI commands use its IPC socket automatically.
---
# Configuration Files
Canonical URL: https://hyprmoncfg.dev/configuration-files/
## Profile storage
Canonical profile JSON files live in:
```
~/.config/hyprmoncfg/profiles/*.json
```
Each profile has a canonical JSON file. The filename is a simplified version of the profile name -- spaces become hyphens, special characters are dropped. For example, a profile named "Home Office" becomes `home-office.json`.
hyprmoncfg also writes generated `home-office.conf` and `home-office.lua` sidecars next to the JSON file. These are fallback exports for people who want to stop using hyprmoncfg but keep the saved layouts as Hyprland config snippets. The JSON remains the source of truth used by hyprmoncfg and `hyprmoncfgd`.
Every Profile File Is A Match Candidate
hyprmoncfgd scans every *.json file in this directory. Old backups, temporary experiments, and duplicate layouts are not ignored just because you forgot about them.
Override the storage directory with `--config-dir`:
```bash
hyprmoncfg --config-dir /path/to/profiles
hyprmoncfgd --config-dir /path/to/profiles
```
### What's in a profile
Each profile stores:
- **Monitor outputs**: hardware identity (make, model, serial), resolution, refresh rate, scale, position, transform, VRR mode
- **Workspace settings**: strategy, max workspaces, group size, monitor order, explicit rules
Monitors are identified by hardware key (`make|model|serial`), not connector name. This means your profiles survive connector swaps between boots.
## Profile hygiene
If you want predictable daemon behavior, keep this directory curated:
- Create profiles for every real monitor scenario you expect auto-switching to cover
- Keep one profile per real monitor setup you actually want auto-applied
- Delete stale profiles instead of renaming them and leaving them in place
- Store backups somewhere else if you do not want them considered during matching
- Re-save a profile after major hardware changes instead of accumulating near-duplicates
## Hyprland targets
hyprmoncfg writes a file it creates and owns, so it never has to overwrite a config you or your distribution wrote. Default legacy apply target:
```
~/.config/hypr/hyprmoncfg-monitors.conf
```
Default legacy root config used for source verification:
```
~/.config/hypr/hyprland.conf
```
On Hyprland 0.55+, if `~/.config/hypr/hyprland.lua` exists, hyprmoncfg switches to Lua mode and uses:
```
~/.config/hypr/hyprmoncfg-monitors.lua
~/.config/hypr/hyprland.lua
```
If Hyprland 0.55+ is still using `hyprland.conf`, hyprmoncfg stays in legacy mode. When `HYPRLAND_CONFIG` is set, hyprmoncfg uses it as the root config path. An explicit `--hypr-config` takes precedence, and paths ending in `.conf` or `.lua` force the matching format.
Override either path:
```bash
hyprmoncfg --monitors-conf /path/to/monitors.conf --hypr-config /path/to/hyprland.conf
hyprmoncfgd --monitors-conf /path/to/monitors.conf --hypr-config /path/to/hyprland.conf
hyprmoncfg --monitors-conf /path/to/monitors.lua --hypr-config /path/to/hyprland.lua
hyprmoncfgd --monitors-conf /path/to/monitors.lua --hypr-config /path/to/hyprland.lua
```
To configure both binaries once, set the equivalent environment variables:
```bash
export HYPRMONCFG_MONITORS_CONF=/path/to/monitors.conf
export HYPRLAND_CONFIG=/path/to/hyprland.conf
```
Explicit flags take precedence. A systemd-managed daemon must receive these variables through the user manager's environment; restart `hyprmoncfgd` after changing them.
## The include-chain check
hyprmoncfg confirms that Hyprland loads the generated monitor file. This catches a surprisingly common problem: a tool writes a config file that Hyprland never reads, so nothing happens and you're left wondering why.
hyprmoncfg adds the include itself, at the end of your root config, and moves it back to the end if something is appended after it. Loading last is what makes the applied layout final: any monitor rule read afterwards would override it. `hyprmoncfg doctor` reports the current state and `--fix` settles it on demand.
Saved profiles are authoritative for the connected monitor set. When a connected output is absent from the selected profile, the generated file writes an explicit disabled rule for it. This overrides an earlier wildcard such as Omarchy's preferred/automatic monitor default instead of letting that default leave an unwanted display enabled.
The Lua include resolves its own path at load time:
```lua
dofile((os.getenv("XDG_CONFIG_HOME") or (os.getenv("HOME") .. "/.config")) .. "/hypr/hyprmoncfg-monitors.lua")
```
so the same config works under a different user, a different home, or a dotfile repo shared between machines. Legacy configs get `source = ~/.config/hypr/hyprmoncfg-monitors.conf`. If your Hyprland config is managed by a dotfile tool, keep that line in your source copy so the two do not keep undoing each other.
For Lua configs, hyprmoncfg reloads Hyprland and asks the active Lua state to confirm that the generated monitor file actually ran. If it did not, hyprmoncfg restores the previous file (or removes a newly created one) and reloads again. This works with `require`, `pcall`, `dofile`, custom `package.path` values, and computed include paths because Hyprland loads the real config itself.
If your files live elsewhere, point hyprmoncfg at them with `--monitors-conf` and `--hypr-config`; it will add an include for whatever target you name.
Upgrading from a version that wrote `monitors.conf` or `monitors.lua` needs nothing from you. On the next apply, hyprmoncfg writes its own file, adds the include, and replaces the body of the file it used to generate with a note saying where the rules moved. A `monitors.conf` or `monitors.lua` that hyprmoncfg did not generate is never touched.
## What gets written
When you apply a profile (via TUI, CLI, or daemon), hyprmoncfg writes the active generated monitor file. Legacy configs get `hyprmoncfg-monitors.conf` with either `monitorv2 { }` blocks (Hyprland 0.50+) or legacy `monitor = ` lines, depending on your Hyprland version. Lua configs get `hyprmoncfg-monitors.lua` with `hl.monitor({ ... })` and `hl.workspace_rule({ ... })` calls.
hyprmoncfg marks generated files with `Generated by hyprmoncfg` on the first line. It fully manages and rewrites a missing target or a target carrying that marker.
An existing file without the marker is treated as user-owned. The TUI and interactive CLI ask before replacing it; the daemon and non-interactive CLI refuse to replace it. To preserve a hand-written `monitors.conf` or `monitors.lua`, select a separate generated target such as `hyprmoncfg-monitors.conf` or `hyprmoncfg-monitors.lua` with `--monitors-conf` (or `HYPRMONCFG_MONITORS_CONF`) and include that file from the Hyprland root config.
Per-profile `*.conf` and `*.lua` files in `~/.config/hyprmoncfg/profiles/` are different: they are exported copies of each saved profile. Applying a profile still regenerates the active target from JSON and the current monitor state so lid handling, duplicate monitor resolution, Hyprland version detection, verification, and rollback keep working.
Do not put unrelated Hyprland settings in that file. Keep blocks like `render`, `cursor`, `misc`, and `env` in other sourced config files.
The file is written atomically (temp file + rename) to prevent partial writes from corrupting your config. Interactive applies keep an in-memory snapshot and restore it on rejection, timeout, interruption, or quit unless you explicitly keep the new configuration.
## Portability
Profile JSON files are portable across machines. The daemon uses hardware identity matching to score profiles, so a profile saved on your desktop will work on your laptop if the same monitors are connected. The generated `.conf` and `.lua` sidecars are useful fallback snippets, but JSON is the portable profile format hyprmoncfg reads.
Add `~/.config/hyprmoncfg` to your dotfile manager to share profiles across all your machines. See the [dotfiles guide](/dotfiles/) for details.
---
# IPC Protocol
Canonical URL: https://hyprmoncfg.dev/ipc/
## Overview
`hyprmoncfgd` exposes a newline-delimited JSON protocol on:
```text
$XDG_RUNTIME_DIR/hyprmoncfgd.sock
```
The socket is mode `0600`. Protocol version 1 is designed for the bundled TUI and CLI as well as local desktop integrations such as an Omarchy bar panel.
When the socket exists, the daemon is the canonical writer. Clients should not edit profiles or the active generated monitor file alongside it. The bundled TUI and CLI fall back to the same core engine only when no daemon owns the session writer lock.
## Envelopes
Every message is one JSON object followed by a newline. A request looks like:
```json
{"type":"request","protocol_version":1,"id":"1","method":"status"}
```
The matching response repeats the client-generated ID:
```json
{"type":"response","protocol_version":1,"id":"1","result":{}}
```
Errors replace `result` with an object containing a stable `code`, a human-readable `message`, and optional `data`.
## Methods
| Method | Parameters | Result |
|---|---|---|
| `status` | none | Status document |
| `subscribe` | none | Current status document, followed by `status` events |
| `editor_state` | none | Editable live profile, supported modes, and source-profile metadata |
| `edit_profile` | full `profile` and one `edit` operation | Updated profile draft |
| `preview` | `profile_name` or a full `profile`; optional `timeout_seconds` and `save_on_commit` | Transaction |
| `confirm` | `transaction_id` | none |
| `commit` | `transaction_id`; `save` boolean | none |
| `revert` | `transaction_id` | none |
| `save` | full `profile` | none |
| `delete` | `name` | none |
| `set_profile_auto` | `enabled` boolean | none |
A transaction contains an opaque `id`, the effective profile, and an RFC 3339 `deadline`.
## Safe preview lifecycle
Only one preview can be active at a time. It belongs to the connection that created it. If that connection disappears—for example, because changing the monitor layout rebuilt a screen-bound panel—the preview stays armed until its original deadline. Its transaction metadata remains available as `daemon.preview` in status, and a replacement client can reclaim it by sending the same transaction ID to `confirm`, `commit`, or `revert`.
A preview is reverted when any of these happens:
- the client calls `revert`
- the deadline expires
- the daemon shuts down
After `confirm`, the selected profile becomes a session-scoped override for the current connected monitor set. The daemon still owns monitor management, but automatic best-match selection is paused until the hardware set changes, the daemon restarts, or `set_profile_auto` is called with `enabled: true`. A late `confirm` or `revert` returns the `transaction_unavailable` error code.
`commit` completes the same safe preview transaction and can atomically save its effective profile before making the layout permanent. If saving fails, the transaction stays armed and still reverts at its deadline. Compact editors should use `save_on_commit: true` when creating a draft preview (or `commit` with `save: true`) for “keep and save” rather than racing separate `confirm` and `save` requests. Keeping that intent in the daemon lets a replacement panel finish the transaction correctly after reconnecting.
## Status events
After `subscribe`, the daemon pushes a fresh status document whenever monitor or profile state changes:
```json
{"type":"event","protocol_version":1,"event":"status","data":{}}
```
The document schema is versioned independently with `schema_version`. `daemon.profile_override` names the session-scoped manual profile for the current monitor set; when it is absent, profile choice is automatic. While an interactive change is awaiting confirmation, `daemon.preview` contains its `transaction_id`, `profile_name`, `deadline`, full effective `profile`, and optional `save_on_commit` intent. The profile lets a screen-bound integration reconstruct the target layout after the monitor change replaces its process or panel. Each profile summary includes `connected_outputs`, `connected_enabled_outputs`, `exact_display_match`, `match_score`, and a `match_reasons` breakdown with the count and score contribution for each connection state. `exact_display_match` is true when the profile accounts for every connected display and has no saved display missing; a connected display deliberately kept off still counts as part of that setup. Integrations should only offer a profile for manual selection when `connected_enabled_outputs` is greater than zero. This guarantees that applying the profile leaves at least one currently connected output enabled. The two connection counts let a browser distinguish all available saved outputs from the subset the profile enables.
Monitor summaries include the connector, make, model, active mode, physical and logical dimensions, position, scale, transform, internal/focused flags, and enabled state. Integrations can therefore render the same monitor identity and layout information as the TUI without querying Hyprland separately. `hyprmoncfg status --json` prints the same shape without requiring clients to speak the socket protocol.
`editor_state` is intentionally fetched on demand rather than included in every status event. Its `profile` is the live display and workspace state in the same shape accepted by `preview` and `save`; `profiles` contains the complete saved profiles for profile browsers. `workspace_plan` contains the resolved workspace assignments for the editable live profile, while `profile_workspace_plans` maps every saved profile name to its resolved assignments so read-only browsers can preview the same plan without reimplementing workspace strategies. `displays` adds supported modes, focus, DPMS, active workspace, internal/external type, and physical panel size for each connected output. `source_profile` is present only when the live state exactly matches one saved profile. Settings that Hyprland cannot report back reliably, such as ICC and HDR luminance overrides, are preserved from that profile or from the best hardware match so an editor does not erase advanced configuration it never displayed.
`edit_profile` applies one typed edit to a client-owned draft. Display mode, scale, VRR, transform, mirroring, position, enablement, color/HDR/ICC settings, and workspace settings are normalized and validated by the same profile geometry used by the bundled TUI. Resize edits reflow displays beyond the old right and bottom edges; dragged position edits can request edge snapping with `snap_distance`, and an overlapping drop is placed on the nearest clear outside edge. The method is stateless and does not touch the live displays until the returned profile is sent to `preview`.
---
# About hyprmoncfg
Canonical URL: https://hyprmoncfg.dev/about/
hyprmoncfg lets you visually create multi-monitor layouts for Hyprland, save each setup as a profile, and switch profiles automatically on hotplug and lid events. It ships as two compiled Go binaries with no graphical toolkit or runtime dependency beyond Hyprland.
The canonical source repository is [crmne/hyprmoncfg](https://github.com/crmne/hyprmoncfg). Packages are published in the AUR as [hyprmoncfg](https://aur.archlinux.org/packages/hyprmoncfg) and [hyprmoncfg-git](https://aur.archlinux.org/packages/hyprmoncfg-git), [Fedora COPR](https://copr.fedorainfracloud.org/coprs/paolino/hyprmoncfg/), [Nixpkgs](https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/hy/hyprmoncfg), [Gentoo GURU](https://github.com/gentoo/guru/tree/master/gui-apps/hyprmoncfg), and unofficially for Void Linux through [Blackhole-vl](https://github.com/Event-Horizon-VL/blackhole-vl).
hyprmoncfg is created by [Carmine Paolino](https://paolino.me/).