From dece70564dff5c777f09b4d3745ca48f94391b17 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Sat, 7 Feb 2026 00:26:29 -0800 Subject: [PATCH] hypr: improve minimize/unminimize workflow --- dotfiles/config/hypr/hyprland.conf | 30 +++++-- .../config/hypr/scripts/minimize-active.sh | 49 +++++++++++ .../config/hypr/scripts/minimized-cancel.sh | 39 +++++++++ .../config/hypr/scripts/minimized-mode.sh | 40 +++++++++ .../config/hypr/scripts/unminimize-last.sh | 86 +++++++++++++++++++ 5 files changed, 239 insertions(+), 5 deletions(-) create mode 100755 dotfiles/config/hypr/scripts/minimize-active.sh create mode 100755 dotfiles/config/hypr/scripts/minimized-cancel.sh create mode 100755 dotfiles/config/hypr/scripts/minimized-mode.sh create mode 100755 dotfiles/config/hypr/scripts/unminimize-last.sh diff --git a/dotfiles/config/hypr/hyprland.conf b/dotfiles/config/hypr/hyprland.conf index faaa2280..4f78ac77 100644 --- a/dotfiles/config/hypr/hyprland.conf +++ b/dotfiles/config/hypr/hyprland.conf @@ -396,12 +396,32 @@ bind = $mainMod SHIFT, equal, hy3:equalize, workspace bind = $mainMod, N, hy3:killactive # hy3:setswallow - set a window to swallow newly spawned windows -bind = $mainMod, M, hy3:setswallow, toggle +bind = $mainMod CTRL, M, hy3:setswallow, toggle -# Minimize to special workspace (like XMonad's minimizeWindow) -bind = $mainMod SHIFT, M, movetoworkspace, special:minimized -# Restore last minimized -bind = $modAlt, Return, togglespecialworkspace, minimized +# Minimize/unminimize (via special workspace) +bind = $mainMod, M, exec, ~/.config/hypr/scripts/minimize-active.sh minimized +bind = $mainMod SHIFT, M, exec, ~/.config/hypr/scripts/unminimize-last.sh minimized + +# Minimized "picker" mode: +# Open the minimized special workspace, focus a window, press Enter to restore it. +bind = $modAlt, Return, exec, ~/.config/hypr/scripts/minimized-mode.sh minimized + +submap = minimized +bind = , Return, exec, ~/.config/hypr/scripts/unminimize-last.sh minimized; hyprctl dispatch submap reset +bind = , Escape, exec, ~/.config/hypr/scripts/minimized-cancel.sh minimized +bind = $modAlt, Return, exec, ~/.config/hypr/scripts/minimized-cancel.sh minimized + +# Optional: basic focus navigation inside the picker. +bind = , H, movefocus, l +bind = , J, movefocus, d +bind = , K, movefocus, u +bind = , L, movefocus, r +bind = , left, movefocus, l +bind = , down, movefocus, d +bind = , up, movefocus, u +bind = , right, movefocus, r + +submap = reset # ----------------------------------------------------------------------------- # WORKSPACE CONTROL diff --git a/dotfiles/config/hypr/scripts/minimize-active.sh b/dotfiles/config/hypr/scripts/minimize-active.sh new file mode 100755 index 00000000..6b8be9b7 --- /dev/null +++ b/dotfiles/config/hypr/scripts/minimize-active.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Minimize the active window by moving it to a special workspace without +# toggling that special workspace open. +# +# Usage: minimize-active.sh +# Example: minimize-active.sh minimized + +set -euo pipefail + +NAME="${1:-minimized}" +NAME="${NAME#special:}" + +if ! command -v hyprctl >/dev/null 2>&1; then + exit 0 +fi +if ! command -v jq >/dev/null 2>&1; then + # We could parse plain output, but jq should exist in this setup; if it + # doesn't, fail soft. + exit 0 +fi + +ACTIVE_JSON="$(hyprctl -j activewindow 2>/dev/null || true)" +ADDR="$(printf '%s' "$ACTIVE_JSON" | jq -r '.address // empty')" +if [ -z "$ADDR" ] || [ "$ADDR" = "null" ]; then + exit 0 +fi + +# If the minimized special workspace is currently visible, closing it after the +# move keeps the window hidden (what "minimize" usually means). +MONITOR_ID="$(printf '%s' "$ACTIVE_JSON" | jq -r '.monitor // empty')" +SPECIAL_OPEN="$( + hyprctl -j monitors 2>/dev/null \ + | jq -r --arg n "special:$NAME" --argjson mid "${MONITOR_ID:-0}" ' + .[] + | select(.id == $mid) + | (.specialWorkspace.name // "") + | select(. == $n) + ' \ + | head -n 1 \ + || true +)" + +hyprctl dispatch movetoworkspacesilent "special:${NAME},address:${ADDR}" >/dev/null 2>&1 || true + +if [ -n "$SPECIAL_OPEN" ]; then + hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true +fi + +exit 0 diff --git a/dotfiles/config/hypr/scripts/minimized-cancel.sh b/dotfiles/config/hypr/scripts/minimized-cancel.sh new file mode 100755 index 00000000..5b7f07f4 --- /dev/null +++ b/dotfiles/config/hypr/scripts/minimized-cancel.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Exit minimized picker mode: +# - Hide the minimized special workspace on the active monitor (if visible) +# - Reset the submap +# +# Usage: minimized-cancel.sh + +set -euo pipefail + +NAME="${1:-minimized}" +NAME="${NAME#special:}" +SPECIAL_WS="special:${NAME}" + +if ! command -v hyprctl >/dev/null 2>&1; then + exit 0 +fi +if ! command -v jq >/dev/null 2>&1; then + exit 0 +fi + +MONITOR_ID="$(hyprctl -j activeworkspace 2>/dev/null | jq -r '.monitorID // empty' || true)" +if [ -z "$MONITOR_ID" ] || [ "$MONITOR_ID" = "null" ]; then + MONITOR_ID=0 +fi + +OPEN="$( + hyprctl -j monitors 2>/dev/null \ + | jq -r --argjson mid "$MONITOR_ID" '.[] | select(.id == $mid) | (.specialWorkspace.name // "")' \ + | head -n 1 \ + || true +)" + +if [ "$OPEN" = "$SPECIAL_WS" ]; then + hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true +fi + +hyprctl dispatch submap reset >/dev/null 2>&1 || true +exit 0 + diff --git a/dotfiles/config/hypr/scripts/minimized-mode.sh b/dotfiles/config/hypr/scripts/minimized-mode.sh new file mode 100755 index 00000000..11fd17fa --- /dev/null +++ b/dotfiles/config/hypr/scripts/minimized-mode.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Enter a "picker" mode for minimized windows: +# - Ensure the minimized special workspace is visible on the active monitor +# - Switch Hyprland into a submap so Enter restores and Escape cancels +# +# Usage: minimized-mode.sh + +set -euo pipefail + +NAME="${1:-minimized}" +NAME="${NAME#special:}" +SPECIAL_WS="special:${NAME}" + +if ! command -v hyprctl >/dev/null 2>&1; then + exit 0 +fi +if ! command -v jq >/dev/null 2>&1; then + exit 0 +fi + +MONITOR_ID="$(hyprctl -j activeworkspace 2>/dev/null | jq -r '.monitorID // empty' || true)" +if [ -z "$MONITOR_ID" ] || [ "$MONITOR_ID" = "null" ]; then + MONITOR_ID=0 +fi + +OPEN="$( + hyprctl -j monitors 2>/dev/null \ + | jq -r --argjson mid "$MONITOR_ID" '.[] | select(.id == $mid) | (.specialWorkspace.name // "")' \ + | head -n 1 \ + || true +)" + +# Ensure it's visible (but don't toggle it off if already open). +if [ "$OPEN" != "$SPECIAL_WS" ]; then + hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true +fi + +hyprctl dispatch submap minimized >/dev/null 2>&1 || true +exit 0 + diff --git a/dotfiles/config/hypr/scripts/unminimize-last.sh b/dotfiles/config/hypr/scripts/unminimize-last.sh new file mode 100755 index 00000000..4ad6b5b9 --- /dev/null +++ b/dotfiles/config/hypr/scripts/unminimize-last.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# Restore a minimized window by moving it out of a special workspace. +# +# Usage: unminimize-last.sh +# Example: unminimize-last.sh minimized + +set -euo pipefail + +NAME="${1:-minimized}" +NAME="${NAME#special:}" +SPECIAL_WS="special:${NAME}" + +if ! command -v hyprctl >/dev/null 2>&1; then + exit 0 +fi +if ! command -v jq >/dev/null 2>&1; then + exit 0 +fi + +ACTIVE_JSON="$(hyprctl -j activewindow 2>/dev/null || true)" +ACTIVE_ADDR="$(printf '%s' "$ACTIVE_JSON" | jq -r '.address // empty')" +ACTIVE_WS="$(printf '%s' "$ACTIVE_JSON" | jq -r '.workspace.name // empty')" +MONITOR_ID="$(printf '%s' "$ACTIVE_JSON" | jq -r '.monitor // empty')" + +# Destination is the normal active workspace for the active monitor. +DEST_WS="$( + hyprctl -j monitors 2>/dev/null \ + | jq -r --argjson mid "${MONITOR_ID:-0}" '.[] | select(.id == $mid) | .activeWorkspace.name' \ + | head -n 1 \ + || true +)" +if [ -z "$DEST_WS" ] || [ "$DEST_WS" = "null" ]; then + DEST_WS="$(hyprctl -j activeworkspace 2>/dev/null | jq -r '.name // empty' || true)" +fi +if [ -z "$DEST_WS" ] || [ "$DEST_WS" = "null" ]; then + exit 0 +fi + +# If we're focused on a minimized window already, restore that one. +ADDR="" +if [ "$ACTIVE_WS" = "$SPECIAL_WS" ] && [ -n "$ACTIVE_ADDR" ] && [ "$ACTIVE_ADDR" != "null" ]; then + ADDR="$ACTIVE_ADDR" +else + # Otherwise, restore the "most recent" minimized window we can find. + # focusHistoryID tends to have 0 as most recent; pick the smallest value. + ADDR="$( + hyprctl -j clients 2>/dev/null \ + | jq -r --arg sw "$SPECIAL_WS" ' + [ .[] + | select(.workspace.name == $sw) + | { addr: .address, fh: (.focusHistoryID // 999999999) } + ] + | sort_by(.fh) + | (.[0].addr // empty) + ' \ + | head -n 1 \ + || true + )" +fi + +if [ -z "$ADDR" ] || [ "$ADDR" = "null" ]; then + exit 0 +fi + +hyprctl dispatch movetoworkspacesilent "${DEST_WS},address:${ADDR}" >/dev/null 2>&1 || true +hyprctl dispatch focuswindow "address:${ADDR}" >/dev/null 2>&1 || true + +# If the minimized special workspace is currently visible, close it so we don't +# leave things in a special state after a restore. +SPECIAL_OPEN="$( + hyprctl -j monitors 2>/dev/null \ + | jq -r --arg n "$SPECIAL_WS" --argjson mid "${MONITOR_ID:-0}" ' + .[] + | select(.id == $mid) + | (.specialWorkspace.name // "") + | select(. == $n) + ' \ + | head -n 1 \ + || true +)" +if [ -n "$SPECIAL_OPEN" ]; then + hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true +fi + +exit 0 +