Skip to content

Instantly share code, notes, and snippets.

@elranu
Created September 2, 2026 01:28
Show Gist options
  • Select an option

  • Save elranu/911bbbd43a185bc5b37aacf30f97f415 to your computer and use it in GitHub Desktop.

Select an option

Save elranu/911bbbd43a185bc5b37aacf30f97f415 to your computer and use it in GitHub Desktop.
Swap the active workspaces of two monitors in Hyprland (0.55+ Lua dispatcher API)

hypr-swap-monitor-workspaces

Swap the active workspaces of two monitors in Hyprland: whatever is on one screen right now moves to the other and vice versa, windows included.

Hyprland already ships the swap itself — hl.dsp.workspace.swap_monitors — but it takes two monitor names. This script works out which two, so a single keybind keeps working when a monitor is plugged in or unplugged.

Behaviour

  • Two monitors: direction is ignored. There is only one "other" screen, so both keys do the same thing.
  • Three or more: swaps with the immediate neighbour in that direction, ordered by actual position in the layout (by x, then y).
  • At the edge: does nothing, rather than wrapping around to the far end. Wrapping would be impossible to predict.

Requirements

Hyprland 0.55+ (the Lua dispatcher API) and jq.

Install

curl -o ~/.local/bin/hypr-swap-monitor-workspaces \
  https://gist.githubusercontent.com/elranu/911bbbd43a185bc5b37aacf30f97f415/raw/hypr-swap-monitor-workspaces
chmod +x ~/.local/bin/hypr-swap-monitor-workspaces

Bind it

In ~/.config/hypr/bindings.lua (Omarchy) or your Lua config:

hl.unbind("SUPER + LEFT")
hl.unbind("SUPER + RIGHT")
o.bind("SUPER + LEFT",  "Swap workspaces between monitors", "hypr-swap-monitor-workspaces left")
o.bind("SUPER + RIGHT", "Swap workspaces between monitors", "hypr-swap-monitor-workspaces right")

Note that SUPER + ←/→ is "Focus on left/right window" by default, so the hl.unbind calls are what free it. Pick another chord if you would rather keep that — SUPER + SHIFT + ALT + ←/→ is a good candidate on Omarchy, where it duplicates SUPER + ALT + ←/→.

Outside Omarchy the o.bind helper does not exist; use hl.bind directly:

hl.bind("SUPER + LEFT", hl.dsp.exec_cmd("hypr-swap-monitor-workspaces left"))

Why not just bind the dispatcher

hl.dsp.workspace.swap_monitors requires monitor1 and monitor2 by name, so binding it directly means hardcoding your current monitors. That breaks as soon as an output is renamed (DP-5 becoming DP-6 after a replug is common) or a display is disconnected.

#!/usr/bin/env bash
#
# Intercambia los workspaces ACTIVOS de dos monitores: lo que se ve ahora en uno
# pasa al otro y viceversa. Las ventanas viajan con su workspace.
#
# hypr-swap-monitor-workspaces left intercambia con el monitor de la izquierda
# hypr-swap-monitor-workspaces right idem a la derecha
#
# Con dos monitores la direccion no importa: solo hay un "otro", asi que
# cualquiera de las dos teclas hace lo mismo. Con tres o mas se usa el vecino
# inmediato en esa direccion, y en el borde no hace nada en vez de dar la vuelta,
# que seria imposible de predecir.
#
# El intercambio en si lo hace Hyprland (hl.dsp.workspace.swap_monitors); aca
# solo se resuelve QUE dos monitores recibe.
set -uo pipefail
dir="${1:-right}"
# Ordenados por posicion en el layout: primero por x, despues por y.
mapfile -t mons < <(hyprctl monitors -j | jq -r 'sort_by(.x, .y)[].name')
(( ${#mons[@]} < 2 )) && exit 0
focused=$(hyprctl monitors -j | jq -r '.[] | select(.focused) | .name')
[[ -z $focused ]] && focused="${mons[0]}"
if (( ${#mons[@]} == 2 )); then
# Un solo "otro" posible: la direccion es irrelevante.
other="${mons[0]}"
[[ $focused == "${other}" ]] && other="${mons[1]}"
else
idx=-1
for i in "${!mons[@]}"; do
[[ ${mons[$i]} == "$focused" ]] && idx=$i && break
done
(( idx < 0 )) && exit 0
case "$dir" in
left) (( idx == 0 )) && exit 0; other="${mons[$((idx - 1))]}" ;;
right) (( idx == ${#mons[@]} - 1 )) && exit 0; other="${mons[$((idx + 1))]}" ;;
*) exit 0 ;;
esac
fi
hyprctl eval "return hl.dispatch(hl.dsp.workspace.swap_monitors({ monitor1 = \"${focused}\", monitor2 = \"${other}\" }))" >/dev/null 2>&1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment