diff --git a/bin/sunshine-headless-primary.sh b/bin/sunshine-headless-primary.sh index 06efd7d..b7c06d7 100755 --- a/bin/sunshine-headless-primary.sh +++ b/bin/sunshine-headless-primary.sh @@ -113,18 +113,32 @@ apply() { log "DP-1 mirroring $head" fi - # 4. Rescue orphaned workspaces (monitorID == -1) onto the headless primary. + # 4. Relocate any workspace that lives on a MIRRORED output onto the headless + # primary. A mirror is excluded from the layout, so a workspace bound to it + # is trapped and unreachable — exactly what happens on swap-back when + # Hyprland brings DP-1 up normal, binds a workspace to it, then we mirror + # it. (tostring guards mirrorOf being null | "None" | a numeric id.) + local mon ws + for mon in $(hyprctl monitors all -j \ + | jq -r '.[] | select(((.mirrorOf // "None") | tostring | ascii_downcase) != "none") | .name'); do + [[ "$mon" == "$head" ]] && continue + for ws in $(hyprctl workspaces -j | jq -r --arg m "$mon" '.[] | select(.monitor==$m) | .id'); do + log "relocating workspace $ws off mirror $mon -> $head" + hyprctl dispatch moveworkspacetomonitor "$ws $head" >/dev/null 2>&1 || true + done + done + + # 5. Rescue orphaned workspaces (monitorID == -1) onto the headless primary. # Narrow by design — a healthy second monitor (DP-2) is left alone. - local ws for ws in $(hyprctl workspaces -j | jq -r '.[] | select(.monitorID == -1) | .id'); do log "rescuing orphaned workspace $ws -> $head" hyprctl dispatch moveworkspacetomonitor "$ws $head" >/dev/null 2>&1 || true done - # 5. Rescue any floating windows stranded off-screen by the topology change. + # 6. Rescue any floating windows stranded off-screen by the topology change. rescue_offscreen_windows - # 6. Keep Sunshine's output_name pointed at the live headless name. + # 7. Keep Sunshine's output_name pointed at the live headless name. if [[ -f "$CONF" ]] && grep -qF '# managed-by: omarchy-moonlight' "$CONF"; then local cur; cur="$(awk '/^output_name = / {print $3; exit}' "$CONF" 2>/dev/null || true)" if [[ "$cur" != "$head" ]]; then diff --git a/docs/HEADLESS-PRIMARY.md b/docs/HEADLESS-PRIMARY.md index 854be3d..815a41c 100644 --- a/docs/HEADLESS-PRIMARY.md +++ b/docs/HEADLESS-PRIMARY.md @@ -62,7 +62,12 @@ clients. Inherent to cloning an ultrawide. `hyprctl output remove` is unreliable for these, returns "output not found"). 2. Sizes it `5120x1440@60` at `0x0`. 3. If `DP-1` is present → `hyprctl keyword monitor "DP-1,…,mirror,HEADLESS-1"`. -4. Rescues any **orphaned** workspace (`monitorID == -1`) onto the headless +4. **Relocates any workspace stranded on a mirrored output** onto the primary. + On swap-back Hyprland brings `DP-1` up *normal*, binds a workspace to it, and + then we mirror it — leaving that workspace trapped on a layout-excluded + mirror (unreachable). This moves it off. (You can move a workspace *off* a + mirror but not *onto* one, which is why this only bites via hotplug.) +5. Rescues any **orphaned** workspace (`monitorID == -1`) onto the headless primary — deliberately narrow, so a healthy second monitor (DP-2) is left alone. 5. **Rescues off-screen floating windows** — recenters any floating, mapped