feat(hyprland): add runtime monitor left/right swap bind

`$mod CTRL o` reflows the two connected monitors adjacent in reversed
order (gap-free regardless of differing widths) and re-inits the
layer-shell clients, which Hyprland doesn't reliably re-notify on a
runtime output move: waybar rebuilds on SIGUSR2, wallpaper is re-pushed.
`monitor` default becomes `mkDefault` so a host can pin its own layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 10:38:16 +02:00
parent e4a8b07117
commit 5c4045f4a6
+47 -1
View File
@@ -79,6 +79,47 @@ let
2) ${pkgs.systemd}/bin/systemctl poweroff ;;
esac
'';
# Swap the physical left/right placement of a two-monitor setup at runtime.
# Reflows the pair adjacent from x=0 (left one at origin, right one butted
# against its logical width) after reversing their order — so it stays
# gap-free regardless of differing widths, unlike a naive x-origin exchange
# (equal widths only). Machine-agnostic: reads whichever two connectors are
# live from `hyprctl -j`, no connector names. Ephemeral (hyprctl keyword) —
# the per-host default layout is restored on the next reload/relogin. No-op
# unless exactly two monitors are connected.
#
# Hyprland doesn't reliably re-notify layer-shell clients when an output is
# reconfigured at runtime (a known limitation — same class as the mirror/unplug
# cases where waybar needs a restart). Observed here: after the move, waybar and
# the wallpaper keep drawing at the pre-swap offsets, so the moved monitor's
# bar/bg land off-screen. Re-init both against the new geometry: waybar rebuilds
# its bars on SIGUSR2, and awww only paints outputs present when `awww img` last
# ran, so re-push the wallpaper symlink. Both are idempotent — harmless when a
# surface happened not to lag (which surface stales seems to race).
swapMonitors = pkgs.writeShellScript "hypr-swap-monitors" ''
set -eu
hc=${config.wayland.windowManager.hyprland.finalPackage}/bin/hyprctl
jq=${pkgs.jq}/bin/jq
data=$("$hc" monitors -j)
n=$(printf '%s' "$data" | "$jq" length)
if [ "$n" -ne 2 ]; then
${pkgs.libnotify}/bin/notify-send "Monitor swap" "need exactly 2 monitors ($n connected)"
exit 0
fi
# sort by current x [left, right]; the new left is the old right.
cmd=$(printf '%s' "$data" | "$jq" -r '
sort_by(.x)
| .[1] as $l | .[0] as $r
| (($l.width / $l.scale) | floor) as $off
| "keyword monitor \($l.name),preferred,0x0,\($l.scale)"
+ " ; keyword monitor \($r.name),preferred,\($off)x0,\($r.scale)"')
"$hc" --batch "$cmd"
# re-anchor the layer-shell clients onto the new geometry (see comment above)
${pkgs.procps}/bin/pkill -USR2 -x .waybar-wrapped || true
bg="$HOME/.local/share/bg"
[ -e "$bg" ] && ${pkgs.awww}/bin/awww img "$bg" || true
'';
in
{
imports = [ ./gpg.nix ./media.nix ./texlive.nix ./librewolf.nix ./mc.nix ./mime.nix ./hyprland-workspaces.nix ];
@@ -479,7 +520,10 @@ in
configType = "hyprlang";
settings = {
monitor = ",preferred,auto,1";
# Fallback for hosts that don't pin a layout; a two-monitor host overrides
# this with explicit positions (see the private inventory). mkDefault so
# that override is a plain value, not a mkForce fight.
monitor = lib.mkDefault ",preferred,auto,1";
# The qt module sets QT_QPA_PLATFORMTHEME=gtk3 (→ Qt apps read the GTK
# Adwaita-dark theme), but only into hm-session-vars.sh + the systemd user
@@ -582,6 +626,8 @@ in
# With two monitors the relative +1 just toggles between them.
"$mod, o, focusmonitor, +1"
"$mod SHIFT, o, movecurrentworkspacetomonitor, +1"
# Swap the two monitors' left/right placement (see swapMonitors above).
"$mod CTRL, o, exec, ${swapMonitors}"
] ++ (
# Dynamic definitions for viewing workspaces 1 to 9 and moving windows between them.
# References physical key codes for robustness with the SHIFT key, e.g. `code:10` instead of `1`.