From 8933f8e5450c5714a6185d74f54ef0960fa656a1 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Wed, 29 Apr 2026 07:21:27 -0700 Subject: [PATCH] Remove Hyprland shell script bindings --- dotfiles/config/hypr/hyprland.conf | 77 +---- dotfiles/config/hypr/hyprland.lua | 21 +- dotfiles/config/hypr/scripts/bring-window.sh | 40 --- dotfiles/config/hypr/scripts/cycle-layout.sh | 15 - .../hypr/scripts/find-empty-workspace.sh | 72 ----- .../config/hypr/scripts/focus-next-class.sh | 48 --- dotfiles/config/hypr/scripts/gather-class.sh | 30 -- dotfiles/config/hypr/scripts/go-to-window.sh | 33 -- .../config/hypr/scripts/minimize-active.sh | 49 --- .../config/hypr/scripts/minimized-cancel.sh | 39 --- .../config/hypr/scripts/minimized-mode.sh | 40 --- .../hypr/scripts/movewindow-follow-cursor.sh | 83 ----- dotfiles/config/hypr/scripts/raise-or-run.sh | 19 -- .../config/hypr/scripts/replace-window.sh | 43 --- .../hypr/scripts/shift-to-empty-on-screen.sh | 43 --- .../config/hypr/scripts/swap-workspaces.sh | 52 --- .../config/hypr/scripts/toggle-scratchpad.sh | 51 --- .../config/hypr/scripts/unminimize-last.sh | 86 ----- .../config/hypr/scripts/window-icon-map.sh | 66 ---- .../hypr/scripts/workspace-goto-empty.sh | 16 - .../hypr/scripts/workspace-move-to-empty.sh | 16 - .../config/hypr/scripts/workspace-scroll.sh | 42 --- nixos/flake.lock | 233 ++++++++----- nixos/hyprland.nix | 305 ++++++++++-------- .../hyprexpo-pr-612-workspace-numbers.patch | 228 ------------- .../patches/hyprexpo-pr-616-bring-mode.patch | 147 --------- 26 files changed, 345 insertions(+), 1549 deletions(-) delete mode 100755 dotfiles/config/hypr/scripts/bring-window.sh delete mode 100755 dotfiles/config/hypr/scripts/cycle-layout.sh delete mode 100755 dotfiles/config/hypr/scripts/find-empty-workspace.sh delete mode 100755 dotfiles/config/hypr/scripts/focus-next-class.sh delete mode 100755 dotfiles/config/hypr/scripts/gather-class.sh delete mode 100755 dotfiles/config/hypr/scripts/go-to-window.sh delete mode 100755 dotfiles/config/hypr/scripts/minimize-active.sh delete mode 100755 dotfiles/config/hypr/scripts/minimized-cancel.sh delete mode 100755 dotfiles/config/hypr/scripts/minimized-mode.sh delete mode 100755 dotfiles/config/hypr/scripts/movewindow-follow-cursor.sh delete mode 100755 dotfiles/config/hypr/scripts/raise-or-run.sh delete mode 100755 dotfiles/config/hypr/scripts/replace-window.sh delete mode 100755 dotfiles/config/hypr/scripts/shift-to-empty-on-screen.sh delete mode 100755 dotfiles/config/hypr/scripts/swap-workspaces.sh delete mode 100755 dotfiles/config/hypr/scripts/toggle-scratchpad.sh delete mode 100755 dotfiles/config/hypr/scripts/unminimize-last.sh delete mode 100755 dotfiles/config/hypr/scripts/window-icon-map.sh delete mode 100755 dotfiles/config/hypr/scripts/workspace-goto-empty.sh delete mode 100755 dotfiles/config/hypr/scripts/workspace-move-to-empty.sh delete mode 100755 dotfiles/config/hypr/scripts/workspace-scroll.sh delete mode 100644 nixos/patches/hyprexpo-pr-612-workspace-numbers.patch delete mode 100644 nixos/patches/hyprexpo-pr-616-bring-mode.patch diff --git a/dotfiles/config/hypr/hyprland.conf b/dotfiles/config/hypr/hyprland.conf index 5a7d6050..beaa4a24 100644 --- a/dotfiles/config/hypr/hyprland.conf +++ b/dotfiles/config/hypr/hyprland.conf @@ -26,8 +26,7 @@ $runMenu = rofi -show run # ENVIRONMENT VARIABLES # ============================================================================= env = XCURSOR_SIZE,24 -env = QT_QPA_PLATFORMTHEME,qt5ct -# Used by ~/.config/hypr/scripts/* to keep workspace IDs bounded. +env = QT_QPA_PLATFORMTHEME,qt6ct env = HYPR_MAX_WORKSPACE,9 # ============================================================================= @@ -260,8 +259,8 @@ bind = $mainMod SHIFT, Q, exit, bind = $mainMod, E, exec, emacsclient --eval '(emacs-everywhere)' bind = $mainMod, V, exec, wl-paste | xdotool type --file - -# Chrome/Browser (raise or spawn like XMonad's bindBringAndRaise) -bind = $modAlt, C, exec, ~/.config/hypr/scripts/raise-or-run.sh google-chrome google-chrome-stable +# Chrome/Browser +bind = $modAlt, C, exec, google-chrome-stable # ----------------------------------------------------------------------------- # SCRATCHPADS (managed by hyprscratch daemon with auto-dismiss) @@ -291,11 +290,11 @@ bind = $mainMod, S, hy3:movefocus, d bind = $mainMod, A, hy3:movefocus, l bind = $mainMod, D, hy3:movefocus, r -# Move windows (Mod + Shift + WASD) - hy3:movewindow with once=true for swapping -bind = $mainMod SHIFT, W, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh u once -bind = $mainMod SHIFT, S, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh d once -bind = $mainMod SHIFT, A, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh l once -bind = $mainMod SHIFT, D, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh r once +# Move windows (Mod + Shift + WASD) +bind = $mainMod SHIFT, W, hy3:movewindow, u +bind = $mainMod SHIFT, S, hy3:movewindow, d +bind = $mainMod SHIFT, A, hy3:movewindow, l +bind = $mainMod SHIFT, D, hy3:movewindow, r # Resize windows (Mod + Ctrl + WASD) binde = $mainMod CTRL, W, resizeactive, 0 -50 @@ -315,13 +314,6 @@ bind = $hyper SHIFT, S, movewindow, mon:d bind = $hyper SHIFT, A, movewindow, mon:l bind = $hyper SHIFT, D, movewindow, mon:r -# Shift to empty workspace on screen direction (Super + Ctrl + Shift + WASD) -# Like XMonad's shiftToEmptyOnScreen -bind = $mainMod CTRL SHIFT, W, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh u -bind = $mainMod CTRL SHIFT, S, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh d -bind = $mainMod CTRL SHIFT, A, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh l -bind = $mainMod CTRL SHIFT, D, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh r - # ----------------------------------------------------------------------------- # LAYOUT CONTROL (XMonad-like with hy3) # ----------------------------------------------------------------------------- @@ -373,31 +365,6 @@ bind = $mainMod, N, hy3:killactive # hy3:setswallow - set a window to swallow newly spawned windows bind = $mainMod CTRL, M, hy3:setswallow, toggle -# 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 # ----------------------------------------------------------------------------- @@ -448,38 +415,14 @@ bind = $mainMod CTRL, 9, focusworkspaceoncurrentmonitor, 9 # built-in per-monitor workspace history. bind = $mainMod, backslash, workspace, previous_per_monitor -# Swap current workspace with another (like XMonad's swapWithCurrent) -bind = $hyper, 5, exec, ~/.config/hypr/scripts/swap-workspaces.sh - -# Go to next empty workspace (like XMonad's moveTo Next emptyWS) -bind = $hyper, E, exec, ~/.config/hypr/scripts/workspace-goto-empty.sh - # Move to next screen (like XMonad's shiftToNextScreenX) bind = $mainMod, Z, focusmonitor, +1 bind = $mainMod SHIFT, Z, movewindow, mon:+1 -# Shift to empty workspace and view (like XMonad's shiftToEmptyAndView) -bind = $mainMod SHIFT, H, exec, ~/.config/hypr/scripts/workspace-move-to-empty.sh - # ----------------------------------------------------------------------------- # WINDOW MANAGEMENT # ----------------------------------------------------------------------------- -# Go to window (rofi window switcher with icons) -bind = $mainMod, G, exec, ~/.config/hypr/scripts/go-to-window.sh - -# Bring window (move to current workspace) -bind = $mainMod, B, exec, ~/.config/hypr/scripts/bring-window.sh - -# Replace window (swap focused with selected - like XMonad's myReplaceWindow) -bind = $mainMod SHIFT, B, exec, ~/.config/hypr/scripts/replace-window.sh - -# Gather windows of same class (like XMonad's gatherThisClass) -bind = $hyper, G, exec, ~/.config/hypr/scripts/gather-class.sh - -# Focus next window of different class (like XMonad's focusNextClass) -bind = $mainMod, apostrophe, exec, ~/.config/hypr/scripts/focus-next-class.sh - # ----------------------------------------------------------------------------- # MEDIA KEYS # ----------------------------------------------------------------------------- @@ -541,8 +484,8 @@ bindm = $mainMod, mouse:272, movewindow bindm = $mainMod, mouse:273, resizewindow # Scroll through workspaces -bind = $mainMod, mouse_down, exec, ~/.config/hypr/scripts/workspace-scroll.sh +1 -bind = $mainMod, mouse_up, exec, ~/.config/hypr/scripts/workspace-scroll.sh -1 +bind = $mainMod, mouse_down, workspace, e+1 +bind = $mainMod, mouse_up, workspace, e-1 # ============================================================================= # AUTOSTART diff --git a/dotfiles/config/hypr/hyprland.lua b/dotfiles/config/hypr/hyprland.lua index df1b84e1..34546ea2 100644 --- a/dotfiles/config/hypr/hyprland.lua +++ b/dotfiles/config/hypr/hyprland.lua @@ -12,6 +12,9 @@ local columns_layout = "nStack" local monocle_layout = "monocle" local minimized_workspace = "special:minimized" local current_layout = columns_layout +local enable_nstack = true +local enable_hyprexpo = true +local configure_nstack_plugin_from_lua = false local workspace_layouts = {} local minimized_windows = {} local window_picker_mode = nil @@ -101,7 +104,7 @@ local function hyprexpo(action) end local function apply_nstack_config() - if verify_config then + if verify_config or not enable_nstack or not configure_nstack_plugin_from_lua then return end @@ -126,7 +129,7 @@ local function apply_nstack_config() end local function apply_hyprexpo_config() - if verify_config then + if verify_config or not enable_hyprexpo then return end @@ -294,7 +297,7 @@ local function find_empty_workspace(target_monitor, exclude_id) end local function update_nstack_count() - if current_layout ~= columns_layout then + if not enable_nstack or current_layout ~= columns_layout then return end @@ -303,6 +306,7 @@ local function update_nstack_count() if count == 0 then return end + count = math.max(count, 2) hl.dsp.layout("setstackcount " .. tostring(count))() end @@ -1103,8 +1107,10 @@ local function toggle_scratchpad(name) end end -hl.plugin.load("/run/current-system/sw/lib/libhyprNStack.so") -if not verify_config then +if enable_nstack then + hl.plugin.load("/run/current-system/sw/lib/libhyprNStack.so") +end +if enable_hyprexpo and not verify_config then hl.plugin.load("/run/current-system/sw/lib/libhyprexpo.so") end @@ -1185,6 +1191,7 @@ hl.curve("smoothOut", { type = "bezier", points = { { 0.36, 1 }, { 0.3, 1 } } }) hl.curve("smoothInOut", { type = "bezier", points = { { 0.42, 0 }, { 0.58, 1 } } }) hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } }) +hl.animation({ leaf = "global", enabled = true, speed = 8, bezier = "default" }) hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" }) hl.animation({ leaf = "windowsIn", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" }) hl.animation({ leaf = "windowsOut", enabled = true, speed = 5, bezier = "smoothInOut", style = "gnomed" }) @@ -1226,8 +1233,6 @@ local function apply_rules() }) end -apply_rules() - bind(main_mod .. " + P", exec(menu)) bind(main_mod .. " + SHIFT + P", exec(run_menu)) bind(main_mod .. " + SHIFT + Return", exec(terminal)) @@ -1465,6 +1470,7 @@ bind(main_mod .. " + mouse:273", hl.dsp.window.resize()) hl.on("hyprland.start", function() apply_nstack_config() apply_hyprexpo_config() + apply_rules() hl.exec_cmd("sh -lc 'export IMALISON_SESSION_TYPE=wayland; dbus-update-activation-environment --systemd WAYLAND_DISPLAY DISPLAY XAUTHORITY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_TYPE IMALISON_SESSION_TYPE; systemctl --user start graphical-session.target hyprland-session.target'") hl.exec_cmd("hypridle") hl.exec_cmd("wl-paste --type text --watch cliphist store") @@ -1476,6 +1482,7 @@ end) hl.on("config.reloaded", apply_nstack_config) hl.on("config.reloaded", apply_hyprexpo_config) +hl.on("config.reloaded", apply_rules) hl.on("window.open", schedule_nstack_count_update) hl.on("window.destroy", schedule_nstack_count_update) diff --git a/dotfiles/config/hypr/scripts/bring-window.sh b/dotfiles/config/hypr/scripts/bring-window.sh deleted file mode 100755 index a0cf917a..00000000 --- a/dotfiles/config/hypr/scripts/bring-window.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -# Bring window to current workspace (like XMonad's bringWindow) -# Uses rofi with icons to select a window, then moves it here. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -source "$SCRIPT_DIR/window-icon-map.sh" - -CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id') - -# Get windows on OTHER workspaces as TSV -WINDOW_DATA=$(hyprctl clients -j | jq -r --argjson cws "$CURRENT_WS" ' - .[] | select(.workspace.id >= 0 and .workspace.id != $cws) - | [.address, .class, (.title | gsub("\t"; " ")), (.workspace.id | tostring)] - | @tsv') - -if [ -z "$WINDOW_DATA" ]; then - notify-send "Bring Window" "No windows on other workspaces" - exit 0 -fi - -addresses=() -TMPFILE=$(mktemp) -trap 'rm -f "$TMPFILE"' EXIT - -while IFS=$'\t' read -r address class title ws_id; do - icon=$(icon_for_class "$class") - addresses+=("$address") - printf '%-24s %s WS:%s\0icon\x1f%s\n' \ - "$class" "$title" "$ws_id" "$icon" -done <<< "$WINDOW_DATA" > "$TMPFILE" - -INDEX=$(rofi -dmenu -i -show-icons -p "Bring window" -format i < "$TMPFILE") || exit 0 - -if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then - ADDRESS="${addresses[$INDEX]}" - hyprctl dispatch movetoworkspace "$CURRENT_WS,address:$ADDRESS" - hyprctl dispatch focuswindow "address:$ADDRESS" -fi diff --git a/dotfiles/config/hypr/scripts/cycle-layout.sh b/dotfiles/config/hypr/scripts/cycle-layout.sh deleted file mode 100755 index b223fffe..00000000 --- a/dotfiles/config/hypr/scripts/cycle-layout.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -# Cycle between master and dwindle layouts -# Like XMonad's NextLayout - -set -euo pipefail - -CURRENT=$(hyprctl getoption general:layout -j | jq -r '.str') - -if [ "$CURRENT" = "master" ]; then - hyprctl keyword general:layout dwindle - notify-send "Layout" "Switched to Dwindle (binary tree)" -else - hyprctl keyword general:layout master - notify-send "Layout" "Switched to Master (XMonad-like)" -fi diff --git a/dotfiles/config/hypr/scripts/find-empty-workspace.sh b/dotfiles/config/hypr/scripts/find-empty-workspace.sh deleted file mode 100755 index dbd2745b..00000000 --- a/dotfiles/config/hypr/scripts/find-empty-workspace.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Print an "empty" workspace id within 1..$HYPR_MAX_WORKSPACE (default 9). -# -# Preference order (lowest id wins within each tier): -# 1. Workspace exists on the target monitor and has 0 windows -# 2. Workspace id does not exist at all (will be created on dispatch) -# 3. Workspace exists (elsewhere) and has 0 windows -# -# Usage: -# find-empty-workspace.sh [monitor] [exclude_id] - -max_ws="${HYPR_MAX_WORKSPACE:-9}" - -monitor="${1:-}" -exclude_id="${2:-}" - -if [[ -z "${monitor}" ]]; then - monitor="$(hyprctl activeworkspace -j | jq -r '.monitor' 2>/dev/null || true)" -fi - -if [[ -z "${monitor}" || "${monitor}" == "null" ]]; then - exit 1 -fi - -workspaces_json="$(hyprctl workspaces -j 2>/dev/null || echo '[]')" - -unused_candidate="" -elsewhere_empty_candidate="" - -for i in $(seq 1 "${max_ws}"); do - if [[ -n "${exclude_id}" && "${i}" == "${exclude_id}" ]]; then - continue - fi - - exists="$(jq -r --argjson id "${i}" '[.[] | select(.id == $id)] | length' <<<"${workspaces_json}")" - if [[ "${exists}" == "0" ]]; then - if [[ -z "${unused_candidate}" ]]; then - unused_candidate="${i}" - fi - continue - fi - - windows="$(jq -r --argjson id "${i}" '([.[] | select(.id == $id) | .windows] | .[0]) // 0' <<<"${workspaces_json}")" - if [[ "${windows}" != "0" ]]; then - continue - fi - - ws_monitor="$(jq -r --argjson id "${i}" '([.[] | select(.id == $id) | .monitor] | .[0]) // ""' <<<"${workspaces_json}")" - if [[ "${ws_monitor}" == "${monitor}" ]]; then - printf '%s\n' "${i}" - exit 0 - fi - - if [[ -z "${elsewhere_empty_candidate}" ]]; then - elsewhere_empty_candidate="${i}" - fi -done - -if [[ -n "${unused_candidate}" ]]; then - printf '%s\n' "${unused_candidate}" - exit 0 -fi - -if [[ -n "${elsewhere_empty_candidate}" ]]; then - printf '%s\n' "${elsewhere_empty_candidate}" - exit 0 -fi - -exit 1 - diff --git a/dotfiles/config/hypr/scripts/focus-next-class.sh b/dotfiles/config/hypr/scripts/focus-next-class.sh deleted file mode 100755 index 8d4a9534..00000000 --- a/dotfiles/config/hypr/scripts/focus-next-class.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -# Focus next window of a different class (like XMonad's focusNextClass) - -set -euo pipefail - -# Get focused window class -FOCUSED_CLASS=$(hyprctl activewindow -j | jq -r '.class') -FOCUSED_ADDR=$(hyprctl activewindow -j | jq -r '.address') - -if [ "$FOCUSED_CLASS" = "null" ] || [ -z "$FOCUSED_CLASS" ]; then - # No focused window, just focus any window - hyprctl dispatch cyclenext - exit 0 -fi - -# Get all unique classes -ALL_CLASSES=$(hyprctl clients -j | jq -r '[.[] | select(.workspace.id >= 0) | .class] | unique | .[]') - -# Get sorted list of classes -CLASSES_ARRAY=() -while IFS= read -r class; do - CLASSES_ARRAY+=("$class") -done <<< "$ALL_CLASSES" - -# Find current class index and get next class -CURRENT_INDEX=-1 -for i in "${!CLASSES_ARRAY[@]}"; do - if [ "${CLASSES_ARRAY[$i]}" = "$FOCUSED_CLASS" ]; then - CURRENT_INDEX=$i - break - fi -done - -if [ $CURRENT_INDEX -eq -1 ] || [ ${#CLASSES_ARRAY[@]} -le 1 ]; then - # Only one class or class not found - exit 0 -fi - -# Get next class (wrapping around) -NEXT_INDEX=$(( (CURRENT_INDEX + 1) % ${#CLASSES_ARRAY[@]} )) -NEXT_CLASS="${CLASSES_ARRAY[$NEXT_INDEX]}" - -# Find first window of next class -NEXT_WINDOW=$(hyprctl clients -j | jq -r ".[] | select(.class == \"$NEXT_CLASS\" and .workspace.id >= 0) | .address" | head -1) - -if [ -n "$NEXT_WINDOW" ]; then - hyprctl dispatch focuswindow "address:$NEXT_WINDOW" -fi diff --git a/dotfiles/config/hypr/scripts/gather-class.sh b/dotfiles/config/hypr/scripts/gather-class.sh deleted file mode 100755 index 927a95a0..00000000 --- a/dotfiles/config/hypr/scripts/gather-class.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -# Gather all windows of the same class as focused window (like XMonad's gatherThisClass) - -set -euo pipefail - -# Get focused window class -FOCUSED_CLASS=$(hyprctl activewindow -j | jq -r '.class') -CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id') - -if [ "$FOCUSED_CLASS" = "null" ] || [ -z "$FOCUSED_CLASS" ]; then - notify-send "Gather Class" "No focused window" - exit 0 -fi - -# Find all windows with same class on other workspaces -WINDOWS=$(hyprctl clients -j | jq -r ".[] | select(.class == \"$FOCUSED_CLASS\" and .workspace.id != $CURRENT_WS and .workspace.id >= 0) | .address") - -if [ -z "$WINDOWS" ]; then - notify-send "Gather Class" "No other windows of class '$FOCUSED_CLASS'" - exit 0 -fi - -# Move each window to current workspace -COUNT=0 -for ADDR in $WINDOWS; do - hyprctl dispatch movetoworkspace "$CURRENT_WS,address:$ADDR" - COUNT=$((COUNT + 1)) -done - -notify-send "Gather Class" "Gathered $COUNT windows of class '$FOCUSED_CLASS'" diff --git a/dotfiles/config/hypr/scripts/go-to-window.sh b/dotfiles/config/hypr/scripts/go-to-window.sh deleted file mode 100755 index a8b0c53b..00000000 --- a/dotfiles/config/hypr/scripts/go-to-window.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -# Go to a window selected via rofi (with icons from desktop entries). -# Replaces "rofi -show window" which doesn't work well on Wayland. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -source "$SCRIPT_DIR/window-icon-map.sh" - -# Get all windows on regular workspaces as TSV -WINDOW_DATA=$(hyprctl clients -j | jq -r ' - .[] | select(.workspace.id >= 0) - | [.address, .class, (.title | gsub("\t"; " ")), (.workspace.id | tostring)] - | @tsv') - -[ -n "$WINDOW_DATA" ] || exit 0 - -addresses=() -TMPFILE=$(mktemp) -trap 'rm -f "$TMPFILE"' EXIT - -while IFS=$'\t' read -r address class title ws_id; do - icon=$(icon_for_class "$class") - addresses+=("$address") - printf '%-24s %s WS:%s\0icon\x1f%s\n' \ - "$class" "$title" "$ws_id" "$icon" -done <<< "$WINDOW_DATA" > "$TMPFILE" - -INDEX=$(rofi -dmenu -i -show-icons -p "Go to window" -format i < "$TMPFILE") || exit 0 - -if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then - hyprctl dispatch focuswindow "address:${addresses[$INDEX]}" -fi diff --git a/dotfiles/config/hypr/scripts/minimize-active.sh b/dotfiles/config/hypr/scripts/minimize-active.sh deleted file mode 100755 index 6b8be9b7..00000000 --- a/dotfiles/config/hypr/scripts/minimize-active.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/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 deleted file mode 100755 index 5b7f07f4..00000000 --- a/dotfiles/config/hypr/scripts/minimized-cancel.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/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 deleted file mode 100755 index 11fd17fa..00000000 --- a/dotfiles/config/hypr/scripts/minimized-mode.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/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/movewindow-follow-cursor.sh b/dotfiles/config/hypr/scripts/movewindow-follow-cursor.sh deleted file mode 100755 index 0256e733..00000000 --- a/dotfiles/config/hypr/scripts/movewindow-follow-cursor.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env bash -# Move the active window in a direction and warp the cursor to keep its -# relative position inside the moved window. - -set -euo pipefail - -export PATH="/run/current-system/sw/bin:${PATH}" - -if [[ $# -lt 1 ]]; then - echo "usage: $0 [mode]" >&2 - exit 1 -fi - -dir="$1" -mode="${2:-}" - -if ! command -v hyprctl >/dev/null; then - exit 0 -fi - -move_window() { - if [[ -n "$mode" ]]; then - hyprctl dispatch hy3:movewindow "$dir, $mode" >/dev/null 2>&1 || true - else - hyprctl dispatch hy3:movewindow "$dir" >/dev/null 2>&1 || true - fi -} - -win_json="$(hyprctl -j activewindow 2>/dev/null || true)" -cur_json="$(hyprctl -j cursorpos 2>/dev/null || true)" - -if [[ -z "$win_json" || "$win_json" == "null" || -z "$cur_json" || "$cur_json" == "null" ]]; then - move_window - exit 0 -fi - -win_x="$(jq -er '.at[0]' <<<"$win_json" 2>/dev/null || true)" -win_y="$(jq -er '.at[1]' <<<"$win_json" 2>/dev/null || true)" -win_w="$(jq -er '.size[0]' <<<"$win_json" 2>/dev/null || true)" -win_h="$(jq -er '.size[1]' <<<"$win_json" 2>/dev/null || true)" -cur_x="$(jq -er '.x' <<<"$cur_json" 2>/dev/null || true)" -cur_y="$(jq -er '.y' <<<"$cur_json" 2>/dev/null || true)" - -if [[ ! "$win_x" =~ ^-?[0-9]+$ || ! "$win_y" =~ ^-?[0-9]+$ || ! "$win_w" =~ ^-?[0-9]+$ || ! "$win_h" =~ ^-?[0-9]+$ || ! "$cur_x" =~ ^-?[0-9]+$ || ! "$cur_y" =~ ^-?[0-9]+$ ]]; then - move_window - exit 0 -fi - -rel_x=$((cur_x - win_x)) -rel_y=$((cur_y - win_y)) - -move_window - -win_json="$(hyprctl -j activewindow 2>/dev/null || true)" -if [[ -z "$win_json" || "$win_json" == "null" ]]; then - exit 0 -fi - -win_x="$(jq -er '.at[0]' <<<"$win_json" 2>/dev/null || true)" -win_y="$(jq -er '.at[1]' <<<"$win_json" 2>/dev/null || true)" -win_w="$(jq -er '.size[0]' <<<"$win_json" 2>/dev/null || true)" -win_h="$(jq -er '.size[1]' <<<"$win_json" 2>/dev/null || true)" - -if [[ ! "$win_x" =~ ^-?[0-9]+$ || ! "$win_y" =~ ^-?[0-9]+$ || ! "$win_w" =~ ^-?[0-9]+$ || ! "$win_h" =~ ^-?[0-9]+$ ]]; then - exit 0 -fi - -if ((rel_x < 0)); then - rel_x=0 -elif ((rel_x > win_w)); then - rel_x=$win_w -fi - -if ((rel_y < 0)); then - rel_y=0 -elif ((rel_y > win_h)); then - rel_y=$win_h -fi - -new_x=$((win_x + rel_x)) -new_y=$((win_y + rel_y)) - -hyprctl dispatch movecursor "$new_x" "$new_y" >/dev/null 2>&1 || true diff --git a/dotfiles/config/hypr/scripts/raise-or-run.sh b/dotfiles/config/hypr/scripts/raise-or-run.sh deleted file mode 100755 index 8e20c7c3..00000000 --- a/dotfiles/config/hypr/scripts/raise-or-run.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -# Raise existing window or run command (like XMonad's raiseNextMaybe) -# Usage: raise-or-run.sh - -set -euo pipefail - -CLASS_PATTERN="$1" -COMMAND="$2" - -# Find windows matching the class pattern -MATCHING=$(hyprctl clients -j | jq -r ".[] | select(.class | test(\"$CLASS_PATTERN\"; \"i\")) | .address" | head -1) - -if [ -n "$MATCHING" ]; then - # Window exists, focus it - hyprctl dispatch focuswindow "address:$MATCHING" -else - # No matching window, run the command - exec $COMMAND -fi diff --git a/dotfiles/config/hypr/scripts/replace-window.sh b/dotfiles/config/hypr/scripts/replace-window.sh deleted file mode 100755 index e2ed4f4d..00000000 --- a/dotfiles/config/hypr/scripts/replace-window.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -# Replace focused window with selected window (like XMonad's myReplaceWindow) -# Swaps the positions of focused window and selected window - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -source "$SCRIPT_DIR/window-icon-map.sh" - -FOCUSED=$(hyprctl activewindow -j | jq -r '.address') - -if [ "$FOCUSED" = "null" ] || [ -z "$FOCUSED" ]; then - notify-send "Replace Window" "No focused window" - exit 0 -fi - -# Get all windows except focused as TSV -WINDOW_DATA=$(hyprctl clients -j | jq -r --arg focused "$FOCUSED" ' - .[] | select(.workspace.id >= 0 and .address != $focused) - | [.address, .class, (.title | gsub("\t"; " ")), (.workspace.id | tostring)] - | @tsv') - -if [ -z "$WINDOW_DATA" ]; then - notify-send "Replace Window" "No other windows available" - exit 0 -fi - -addresses=() -TMPFILE=$(mktemp) -trap 'rm -f "$TMPFILE"' EXIT - -while IFS=$'\t' read -r address class title ws_id; do - icon=$(icon_for_class "$class") - addresses+=("$address") - printf '%-24s %s WS:%s\0icon\x1f%s\n' \ - "$class" "$title" "$ws_id" "$icon" -done <<< "$WINDOW_DATA" > "$TMPFILE" - -INDEX=$(rofi -dmenu -i -show-icons -p "Replace with" -format i < "$TMPFILE") || exit 0 - -if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then - hyprctl dispatch hy3:movewindow "address:${addresses[$INDEX]}" -fi diff --git a/dotfiles/config/hypr/scripts/shift-to-empty-on-screen.sh b/dotfiles/config/hypr/scripts/shift-to-empty-on-screen.sh deleted file mode 100755 index 5bf26318..00000000 --- a/dotfiles/config/hypr/scripts/shift-to-empty-on-screen.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -# Shift window to empty workspace on screen in given direction -# Like XMonad's shiftToEmptyOnScreen -# Usage: shift-to-empty-on-screen.sh - -set -euo pipefail - -DIRECTION="$1" -max_ws="${HYPR_MAX_WORKSPACE:-9}" - -# Track the current monitor so we can return -ORIG_MONITOR=$(hyprctl activeworkspace -j | jq -r '.monitor') - -# Move focus to the screen in that direction -hyprctl dispatch focusmonitor "$DIRECTION" - -# Get the monitor we're now on (target monitor) -MONITOR=$(hyprctl activeworkspace -j | jq -r '.monitor') - -# If there is no monitor in that direction, bail -if [ "$MONITOR" = "$ORIG_MONITOR" ]; then - exit 0 -fi - -# Find an empty workspace within 1..$HYPR_MAX_WORKSPACE. -EMPTY_WS="$(~/.config/hypr/scripts/find-empty-workspace.sh "${MONITOR}" 2>/dev/null || true)" -if [[ -z "${EMPTY_WS}" ]]; then - # No empty workspace available within the cap; restore focus and bail. - hyprctl dispatch focusmonitor "$ORIG_MONITOR" - exit 0 -fi - -if (( EMPTY_WS < 1 || EMPTY_WS > max_ws )); then - hyprctl dispatch focusmonitor "$ORIG_MONITOR" - exit 0 -fi - -# Ensure the workspace exists on the target monitor -hyprctl dispatch workspace "$EMPTY_WS" - -# Go back to original monitor and move the window (without following) -hyprctl dispatch focusmonitor "$ORIG_MONITOR" -hyprctl dispatch movetoworkspacesilent "$EMPTY_WS" diff --git a/dotfiles/config/hypr/scripts/swap-workspaces.sh b/dotfiles/config/hypr/scripts/swap-workspaces.sh deleted file mode 100755 index 8b9dc21a..00000000 --- a/dotfiles/config/hypr/scripts/swap-workspaces.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash -# Swap the contents of the current workspace with another workspace. -# Intended to mirror XMonad's swapWithCurrent behavior. - -set -euo pipefail - -max_ws="${HYPR_MAX_WORKSPACE:-9}" - -CURRENT_WS="$(hyprctl activeworkspace -j | jq -r '.id')" -if [[ -z "${CURRENT_WS}" || "${CURRENT_WS}" == "null" ]]; then - exit 0 -fi - -TARGET_WS="${1:-}" - -if [[ -z "${TARGET_WS}" ]]; then - WS_LIST="$({ - seq 1 "${max_ws}" - hyprctl workspaces -j | jq -r '.[].id' 2>/dev/null || true - } | awk 'NF {print $1}' | awk '!seen[$0]++' | sort -n)" - - TARGET_WS="$(printf "%s\n" "${WS_LIST}" | rofi -dmenu -p "Swap with workspace")" -fi - -if [[ -z "${TARGET_WS}" || "${TARGET_WS}" == "null" ]]; then - exit 0 -fi - -if [[ "${TARGET_WS}" == "${CURRENT_WS}" ]]; then - exit 0 -fi - -if ! [[ "${TARGET_WS}" =~ ^-?[0-9]+$ ]]; then - notify-send "Swap Workspace" "Invalid workspace: ${TARGET_WS}" - exit 1 -fi - -if (( TARGET_WS < 1 || TARGET_WS > max_ws )); then - notify-send "Swap Workspace" "Workspace out of range (1-${max_ws}): ${TARGET_WS}" - exit 1 -fi - -WINDOWS_CURRENT="$(hyprctl clients -j | jq -r --arg ws "${CURRENT_WS}" '.[] | select((.workspace.id|tostring) == $ws) | .address')" -WINDOWS_TARGET="$(hyprctl clients -j | jq -r --arg ws "${TARGET_WS}" '.[] | select((.workspace.id|tostring) == $ws) | .address')" - -for ADDR in ${WINDOWS_CURRENT}; do - hyprctl dispatch movetoworkspace "${TARGET_WS},address:${ADDR}" -done - -for ADDR in ${WINDOWS_TARGET}; do - hyprctl dispatch movetoworkspace "${CURRENT_WS},address:${ADDR}" -done diff --git a/dotfiles/config/hypr/scripts/toggle-scratchpad.sh b/dotfiles/config/hypr/scripts/toggle-scratchpad.sh deleted file mode 100755 index 118c696c..00000000 --- a/dotfiles/config/hypr/scripts/toggle-scratchpad.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash -# Toggle a named Hyprland scratchpad, spawning it if needed. -# Usage: toggle-scratchpad.sh - -set -euo pipefail - -if [ "$#" -lt 4 ]; then - echo "usage: $0 " >&2 - exit 1 -fi - -NAME="$1" -shift -CLASS_REGEX="$1" -shift -TITLE_REGEX="$1" -shift -COMMAND=("$@") - -if [ "$CLASS_REGEX" = "-" ]; then - CLASS_REGEX="" -fi -if [ "$TITLE_REGEX" = "-" ]; then - TITLE_REGEX="" -fi - -if [ -z "$CLASS_REGEX" ] && [ -z "$TITLE_REGEX" ]; then - echo "toggle-scratchpad: provide a class or title regex" >&2 - exit 1 -fi - -MATCHING=$(hyprctl clients -j | jq -r --arg cre "$CLASS_REGEX" --arg tre "$TITLE_REGEX" ' - .[] - | select( - (($cre == "") or (.class | test($cre; "i"))) - and - (($tre == "") or (.title | test($tre; "i"))) - ) - | .address -') - -if [ -z "$MATCHING" ]; then - "${COMMAND[@]}" & -else - while IFS= read -r ADDR; do - [ -n "$ADDR" ] || continue - hyprctl dispatch movetoworkspacesilent "special:$NAME,address:$ADDR" - done <<< "$MATCHING" -fi - -hyprctl dispatch togglespecialworkspace "$NAME" diff --git a/dotfiles/config/hypr/scripts/unminimize-last.sh b/dotfiles/config/hypr/scripts/unminimize-last.sh deleted file mode 100755 index 4ad6b5b9..00000000 --- a/dotfiles/config/hypr/scripts/unminimize-last.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/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 - diff --git a/dotfiles/config/hypr/scripts/window-icon-map.sh b/dotfiles/config/hypr/scripts/window-icon-map.sh deleted file mode 100755 index 8c8714e1..00000000 --- a/dotfiles/config/hypr/scripts/window-icon-map.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bash -# Source this file to get icon_for_class function. -# Builds a mapping from window class → freedesktop icon name -# by scanning .desktop files for StartupWMClass and Icon fields. -# -# Usage: -# source "$(dirname "$0")/window-icon-map.sh" -# icon=$(icon_for_class "google-chrome") - -declare -A _WINDOW_ICON_MAP - -_build_window_icon_map() { - local IFS=':' - local -a search_dirs=() - local dir - - for dir in ${XDG_DATA_DIRS:-/run/current-system/sw/share:/usr/share:/usr/local/share}; do - [ -d "$dir/applications" ] && search_dirs+=("$dir/applications") - done - [ -d "$HOME/.local/share/applications" ] && search_dirs+=("$HOME/.local/share/applications") - [ ${#search_dirs[@]} -eq 0 ] && return - - # Expand globs per-directory so the pattern works correctly - local -a desktop_files=() - for dir in "${search_dirs[@]}"; do - desktop_files+=("$dir"/*.desktop) - done - [ ${#desktop_files[@]} -eq 0 ] && return - - # Single grep pass across all desktop files - local -A file_icons file_wmclass - local filepath line - while IFS=: read -r filepath line; do - case "$line" in - Icon=*) - [ -z "${file_icons[$filepath]:-}" ] && file_icons["$filepath"]="${line#Icon=}" - ;; - StartupWMClass=*) - [ -z "${file_wmclass[$filepath]:-}" ] && file_wmclass["$filepath"]="${line#StartupWMClass=}" - ;; - esac - done < <(grep -H '^Icon=\|^StartupWMClass=' "${desktop_files[@]}" 2>/dev/null) - - # Build class → icon map - local icon wm_class bn name - for filepath in "${!file_icons[@]}"; do - icon="${file_icons[$filepath]}" - [ -n "$icon" ] || continue - - wm_class="${file_wmclass[$filepath]:-}" - if [ -n "$wm_class" ]; then - _WINDOW_ICON_MAP["${wm_class,,}"]="$icon" - fi - - bn="${filepath##*/}" - name="${bn%.desktop}" - _WINDOW_ICON_MAP["${name,,}"]="$icon" - done -} - -_build_window_icon_map - -icon_for_class() { - local class_lower="${1,,}" - echo "${_WINDOW_ICON_MAP[$class_lower]:-$class_lower}" -} diff --git a/dotfiles/config/hypr/scripts/workspace-goto-empty.sh b/dotfiles/config/hypr/scripts/workspace-goto-empty.sh deleted file mode 100755 index df19301b..00000000 --- a/dotfiles/config/hypr/scripts/workspace-goto-empty.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cur_ws="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)" -monitor="$(hyprctl activeworkspace -j | jq -r '.monitor' 2>/dev/null || true)" - -ws="$( - ~/.config/hypr/scripts/find-empty-workspace.sh "${monitor}" "${cur_ws}" 2>/dev/null || true -)" - -if [[ -z "${ws}" ]]; then - exit 0 -fi - -hyprctl dispatch workspace "${ws}" >/dev/null 2>&1 || true - diff --git a/dotfiles/config/hypr/scripts/workspace-move-to-empty.sh b/dotfiles/config/hypr/scripts/workspace-move-to-empty.sh deleted file mode 100755 index fcae51bf..00000000 --- a/dotfiles/config/hypr/scripts/workspace-move-to-empty.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cur_ws="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)" -monitor="$(hyprctl activeworkspace -j | jq -r '.monitor' 2>/dev/null || true)" - -ws="$( - ~/.config/hypr/scripts/find-empty-workspace.sh "${monitor}" "${cur_ws}" 2>/dev/null || true -)" - -if [[ -z "${ws}" ]]; then - exit 0 -fi - -hyprctl dispatch movetoworkspace "${ws}" >/dev/null 2>&1 || true - diff --git a/dotfiles/config/hypr/scripts/workspace-scroll.sh b/dotfiles/config/hypr/scripts/workspace-scroll.sh deleted file mode 100755 index 11ddd2de..00000000 --- a/dotfiles/config/hypr/scripts/workspace-scroll.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -max_ws="${HYPR_MAX_WORKSPACE:-9}" -delta="${1:-}" - -case "${delta}" in - +1|-1) ;; - next) delta="+1" ;; - prev) delta="-1" ;; - *) - exit 2 - ;; -esac - -cur="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)" -if ! [[ "${cur}" =~ ^[0-9]+$ ]]; then - exit 0 -fi - -if (( cur < 1 )); then - cur=1 -elif (( cur > max_ws )); then - cur="${max_ws}" -fi - -if [[ "${delta}" == "+1" ]]; then - if (( cur >= max_ws )); then - nxt=1 - else - nxt=$((cur + 1)) - fi -else - if (( cur <= 1 )); then - nxt="${max_ws}" - else - nxt=$((cur - 1)) - fi -fi - -hyprctl dispatch workspace "${nxt}" >/dev/null 2>&1 || true - diff --git a/nixos/flake.lock b/nixos/flake.lock index 141d5f30..30c985b9 100644 --- a/nixos/flake.lock +++ b/nixos/flake.lock @@ -335,15 +335,15 @@ "flake-compat_3": { "flake": false, "locked": { - "lastModified": 1767039857, - "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", - "owner": "NixOS", + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", "repo": "flake-compat", - "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "type": "github" }, "original": { - "owner": "NixOS", + "owner": "edolstra", "repo": "flake-compat", "type": "github" } @@ -353,13 +353,13 @@ "locked": { "lastModified": 1767039857, "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", - "owner": "edolstra", + "owner": "NixOS", "repo": "flake-compat", "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { - "owner": "edolstra", + "owner": "NixOS", "repo": "flake-compat", "type": "github" } @@ -367,11 +367,11 @@ "flake-compat_5": { "flake": false, "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", "owner": "edolstra", "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { @@ -502,6 +502,24 @@ "type": "github" } }, + "flake-utils_2": { + "inputs": { + "systems": "systems_3" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "git-blame-rank": { "inputs": { "fenix": "fenix", @@ -667,6 +685,7 @@ "gitignore_3": { "inputs": { "nixpkgs": [ + "imalison-taffybar", "taffybar", "weeder-nix", "pre-commit-hooks", @@ -712,7 +731,7 @@ "hercules-ci-effects_2": { "inputs": { "flake-parts": "flake-parts_5", - "nixpkgs": "nixpkgs_6" + "nixpkgs": "nixpkgs_7" }, "locked": { "lastModified": 1701009247, @@ -780,11 +799,11 @@ ] }, "locked": { - "lastModified": 1777317717, - "narHash": "sha256-Rj4vx0RvEWtnpnizggWRtrGe092bXiGLLt0WijwYWtI=", + "lastModified": 1777471960, + "narHash": "sha256-X+xWT3VwjCRAxlBLZvBi5Lam4BpZk4k8ep7Fv13QNLw=", "owner": "colonelpanic8", "repo": "hyprNStack", - "rev": "94607cd53f2ddac88f6b26261393275e7dd590ef", + "rev": "d1b4307c0804ad22a17882bd5c5c2f5a7df2b3ea", "type": "github" }, "original": { @@ -1084,16 +1103,16 @@ ] }, "locked": { - "lastModified": 1767020608, - "narHash": "sha256-BSRT1Uu1ot4WfMfZc6KW0nwpmt2xl9wpUqmH/JoMTfk=", - "owner": "hyprwm", + "lastModified": 1777471981, + "narHash": "sha256-cd3pQg+vKv6vht4xzButsi/Kaw9P4d3itm46jYXyiDM=", + "owner": "colonelpanic8", "repo": "hyprland-plugins", - "rev": "d7b67e8f4ba8ebeee4ce899348fcee6291512169", + "rev": "725e354bbee982566068b5b90fec4fcd787c1036", "type": "github" }, "original": { - "owner": "hyprwm", - "ref": "v0.53.0", + "owner": "colonelpanic8", + "ref": "hyprexpo-v0.53.0-custom", "repo": "hyprland-plugins", "type": "github" } @@ -1115,11 +1134,11 @@ ] }, "locked": { - "lastModified": 1777413654, - "narHash": "sha256-lVGYGUWf9ynV5lR8QAygAfmYRkko1btIe26UcQ1bGXw=", + "lastModified": 1777471960, + "narHash": "sha256-Fr09SZhviCOxyuZiWku1X/rNXNvKrIqpsSdX1rQH+4o=", "owner": "colonelpanic8", "repo": "hyprland-plugins", - "rev": "ff36c04b270c26fcd53e623fc688e4eb41672897", + "rev": "457830c379b8e6763ba147b8c7f83986378b8576", "type": "github" }, "original": { @@ -1528,9 +1547,7 @@ "nixpkgs": [ "nixpkgs" ], - "taffybar": [ - "taffybar" - ], + "taffybar": "taffybar", "xmonad": [ "xmonad" ] @@ -1580,11 +1597,11 @@ ] }, "locked": { - "lastModified": 1777261868, - "narHash": "sha256-30E1RBr0FGrf1IdXi2OKua+vQ4sUvjwUq6lfC1qcBug=", + "lastModified": 1777434099, + "narHash": "sha256-GutKXyfGI7o89Dge4bP0yt0CQn1rqA6LyYDOH4GemdE=", "owner": "colonelpanic8", "repo": "keepbook", - "rev": "2e178e7ba864af0a880456dca241615dd75afd24", + "rev": "240fe454c26e7dbdd25a2fa4f0b436bf1c4f937b", "type": "github" }, "original": { @@ -1611,10 +1628,10 @@ }, "nix": { "inputs": { - "flake-compat": "flake-compat_3", + "flake-compat": "flake-compat_4", "flake-parts": "flake-parts", "git-hooks-nix": "git-hooks-nix", - "nixpkgs": "nixpkgs_4", + "nixpkgs": "nixpkgs_5", "nixpkgs-23-11": "nixpkgs-23-11", "nixpkgs-regression": "nixpkgs-regression" }, @@ -1671,7 +1688,7 @@ }, "nixos-wsl": { "inputs": { - "flake-compat": "flake-compat_4", + "flake-compat": "flake-compat_5", "nixpkgs": [ "nixpkgs" ] @@ -1805,6 +1822,22 @@ } }, "nixpkgs_4": { + "locked": { + "lastModified": 1776877367, + "narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0726a0ecb6d4e08f6adced58726b95db924cef57", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_5": { "locked": { "lastModified": 1771903837, "narHash": "sha256-jEA8WggGKtMFeNeCKq3NK8cLEjJmG6/RLUElYYbBZ0E=", @@ -1817,7 +1850,7 @@ "url": "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz" } }, - "nixpkgs_5": { + "nixpkgs_6": { "locked": { "lastModified": 1776877367, "narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=", @@ -1833,7 +1866,7 @@ "type": "github" } }, - "nixpkgs_6": { + "nixpkgs_7": { "locked": { "lastModified": 1697723726, "narHash": "sha256-SaTWPkI8a5xSHX/rrKzUe+/uVNy6zCGMXgoeMb7T9rg=", @@ -1849,7 +1882,7 @@ "type": "github" } }, - "nixpkgs_7": { + "nixpkgs_8": { "locked": { "lastModified": 1703255338, "narHash": "sha256-Z6wfYJQKmDN9xciTwU3cOiOk+NElxdZwy/FiHctCzjU=", @@ -1869,7 +1902,7 @@ "inputs": { "flake-parts": "flake-parts_4", "hercules-ci-effects": "hercules-ci-effects_2", - "nixpkgs": "nixpkgs_7", + "nixpkgs": "nixpkgs_8", "osx-kvm": "osx-kvm" }, "locked": { @@ -2055,9 +2088,10 @@ }, "pre-commit-hooks_3": { "inputs": { - "flake-compat": "flake-compat_5", + "flake-compat": "flake-compat_3", "gitignore": "gitignore_3", "nixpkgs": [ + "imalison-taffybar", "nixpkgs" ] }, @@ -2148,16 +2182,15 @@ "nixified-ai": "nixified-ai", "nixos-hardware": "nixos-hardware", "nixos-wsl": "nixos-wsl", - "nixpkgs": "nixpkgs_5", + "nixpkgs": "nixpkgs_6", "nixtheplanet": "nixtheplanet", "notifications-tray-icon": "notifications-tray-icon", "org-agenda-api": "org-agenda-api", "railbird-secrets": "railbird-secrets", - "systems": "systems_3", - "taffybar": "taffybar", + "systems": "systems_4", "vscode-server": "vscode-server", - "xmonad": "xmonad", - "xmonad-contrib": "xmonad-contrib" + "xmonad": "xmonad_2", + "xmonad-contrib": "xmonad-contrib_2" } }, "rust-analyzer-src": { @@ -2259,36 +2292,43 @@ "type": "github" } }, - "taffybar": { - "inputs": { - "flake-utils": [ - "flake-utils" - ], - "nixpkgs": [ - "nixpkgs" - ], - "weeder-nix": "weeder-nix", - "xmonad": [ - "xmonad" - ], - "xmonad-contrib": [ - "xmonad-contrib" - ] - }, + "systems_4": { "locked": { - "lastModified": 1777401169, - "narHash": "sha256-bciN/qFjXYm8ZIKXSc/OssUsLt9GoNs/cU9xT/pw7QY=", - "owner": "taffybar", - "repo": "taffybar", - "rev": "59e3c75990156dcd4353ad9fad5823303e751f0f", + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", "type": "github" }, "original": { - "owner": "taffybar", - "repo": "taffybar", + "owner": "nix-systems", + "repo": "default", "type": "github" } }, + "taffybar": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_4", + "weeder-nix": "weeder-nix", + "xmonad": "xmonad", + "xmonad-contrib": "xmonad-contrib" + }, + "locked": { + "lastModified": 1777452249, + "narHash": "sha256-Emhn9sIFRVyIlUULDuYjeFcYJld6EAD31TGasYwQsWg=", + "ref": "refs/heads/master", + "rev": "9a6463e68c7bc0a712e49d9ba6c6d1b764260cd7", + "revCount": 2295, + "type": "git", + "url": "file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar" + }, + "original": { + "type": "git", + "url": "file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar" + } + }, "vscode-server": { "inputs": { "flake-utils": [ @@ -2315,6 +2355,7 @@ "weeder-nix": { "inputs": { "nixpkgs": [ + "imalison-taffybar", "taffybar", "nixpkgs" ], @@ -2417,20 +2458,7 @@ } }, "xmonad": { - "inputs": { - "flake-utils": [ - "flake-utils" - ], - "git-ignore-nix": [ - "git-ignore-nix" - ], - "nixpkgs": [ - "nixpkgs" - ], - "unstable": [ - "nixpkgs" - ] - }, + "flake": false, "locked": { "lastModified": 1776502138, "narHash": "sha256-mSOpNU1iJvfFh5uwayA6aPxneFMduNW1kG1gV2tGE+c=", @@ -2441,11 +2469,29 @@ }, "original": { "owner": "xmonad", + "ref": "master", "repo": "xmonad", "type": "github" } }, "xmonad-contrib": { + "flake": false, + "locked": { + "lastModified": 1769258911, + "narHash": "sha256-YGEKXs4UmS5QOIELJTdCiMzTktuue+Bd3yFoIKSHuBU=", + "owner": "xmonad", + "repo": "xmonad-contrib", + "rev": "803bc3d12bdcc512ec06856c4f119d37de1ba338", + "type": "github" + }, + "original": { + "owner": "xmonad", + "ref": "master", + "repo": "xmonad-contrib", + "type": "github" + } + }, + "xmonad-contrib_2": { "inputs": { "flake-utils": [ "flake-utils" @@ -2474,6 +2520,35 @@ "repo": "xmonad-contrib", "type": "github" } + }, + "xmonad_2": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "git-ignore-nix": [ + "git-ignore-nix" + ], + "nixpkgs": [ + "nixpkgs" + ], + "unstable": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1776502138, + "narHash": "sha256-mSOpNU1iJvfFh5uwayA6aPxneFMduNW1kG1gV2tGE+c=", + "owner": "xmonad", + "repo": "xmonad", + "rev": "a618fb32662e44eb5d8276a3dc1925b0233e638b", + "type": "github" + }, + "original": { + "owner": "xmonad", + "repo": "xmonad", + "type": "github" + } } }, "root": "root", diff --git a/nixos/hyprland.nix b/nixos/hyprland.nix index e1686df5..7eef2975 100644 --- a/nixos/hyprland.nix +++ b/nixos/hyprland.nix @@ -1,4 +1,11 @@ -{ config, pkgs, lib, makeEnable, inputs, ... }: +{ + config, + pkgs, + lib, + makeEnable, + inputs, + ... +}: let cfg = config.myModules.hyprland; system = pkgs.stdenv.hostPlatform.system; @@ -11,17 +18,65 @@ let inputs.hyprNStack.packages.${system}.hyprNStack inputs.hyprland-plugins-lua.packages.${system}.hyprexpo ]; - hyprexpoPatched = inputs.hyprland-plugins.packages.${system}.hyprexpo.overrideAttrs (old: { - patches = (old.patches or [ ]) ++ [ - ./patches/hyprexpo-pr-612-workspace-numbers.patch - ./patches/hyprexpo-pr-616-bring-mode.patch - ]; - }); + hyprscratchSettings = { + daemon_options = "clean"; + global_options = ""; + global_rules = "float;size monitor_w*0.95 monitor_h*0.95;center"; + + htop = { + command = "alacritty --class htop-scratch --title htop -e htop"; + class = "htop-scratch"; + }; + + volume = { + command = "pavucontrol"; + class = "org.pulseaudio.pavucontrol"; + }; + + spotify = { + command = "spotify"; + class = "spotify"; + }; + + element = { + command = "element-desktop"; + class = "Element"; + }; + + slack = { + command = "slack"; + class = "Slack"; + }; + + transmission = { + command = "transmission-gtk"; + class = "transmission-gtk"; + }; + + dropdown = { + command = "ghostty --config-file=/home/imalison/.config/ghostty/dropdown"; + class = "com.mitchellh.ghostty.dropdown"; + options = "persist"; + rules = "float;size monitor_w monitor_h*0.5;move 0 60;noborder;noshadow;animation slide"; + }; + + gmail = { + command = "google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox"; + class = "google-chrome"; + title = "Gmail"; + }; + + messages = { + command = "google-chrome-stable --new-window https://messages.google.com/web/conversations"; + class = "google-chrome"; + title = "Messages"; + }; + }; enabledModule = makeEnable config "myModules.hyprland" true { myModules.taffybar.enable = true; # Needed for hyprlock authentication without PAM fallback warnings. - security.pam.services.hyprlock = {}; + security.pam.services.hyprlock = { }; # DDC/CI monitor control for keyboard-driven input switching. hardware.i2c = { @@ -39,146 +94,120 @@ let home-manager.sharedModules = [ inputs.hyprscratch.homeModules.default - ({ config, ... }: { - xdg.configFile."hypr" = { - force = true; - source = - if cfg.useLuaConfigBranch - then ../dotfiles/config/hypr - else config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/dotfiles/dotfiles/config/hypr"; - }; + ( + { config, lib, ... }: + let + hyprConfig = + name: + config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/dotfiles/dotfiles/config/hypr/${name}"; + in + { + services.kanshi = { + enable = true; + systemdTarget = "graphical-session.target"; + settings = [ + { + # USB-C connector names can move between DP-* ports across docks/reboots. + # Match the ultrawide by make/model and allow the serial field to vary. + profile.name = "ultrawide-usbc-desk"; + profile.outputs = [ + { + criteria = "eDP-1"; + status = "enable"; + mode = "2560x1600@240Hz"; + position = "0,0"; + scale = 1.0; + } + { + criteria = "Microstep MPG341CX OLED *"; + status = "enable"; + mode = "3440x1440@240Hz"; + position = "2560,0"; + scale = 1.0; + } + ]; + } + { + # When the laptop panel is unavailable (e.g. lid-closed docked use), + # still drive the ultrawide at its full refresh rate. + profile.name = "ultrawide-only"; + profile.outputs = [ + { + criteria = "Microstep MPG341CX OLED *"; + status = "enable"; + mode = "3440x1440@240Hz"; + position = "0,0"; + scale = 1.0; + } + ]; + } + ]; + }; - services.kanshi = { - enable = true; - systemdTarget = "graphical-session.target"; - settings = [ - { - # USB-C connector names can move between DP-* ports across docks/reboots. - # Match the ultrawide by make/model and allow the serial field to vary. - profile.name = "ultrawide-usbc-desk"; - profile.outputs = [ - { - criteria = "eDP-1"; - status = "enable"; - mode = "2560x1600@240Hz"; - position = "0,0"; - scale = 1.0; - } - { - criteria = "Microstep MPG341CX OLED *"; - status = "enable"; - mode = "3440x1440@240Hz"; - position = "2560,0"; - scale = 1.0; - } - ]; - } - { - # When the laptop panel is unavailable (e.g. lid-closed docked use), - # still drive the ultrawide at its full refresh rate. - profile.name = "ultrawide-only"; - profile.outputs = [ - { - criteria = "Microstep MPG341CX OLED *"; - status = "enable"; - mode = "3440x1440@240Hz"; - position = "0,0"; - scale = 1.0; - } - ]; - } - ]; - }; + programs.hyprscratch = { + enable = !cfg.useLuaConfigBranch; + settings = { }; + }; - programs.hyprscratch = { - enable = !cfg.useLuaConfigBranch; - settings = { - daemon_options = "clean"; - global_options = ""; - global_rules = "float;size monitor_w*0.95 monitor_h*0.95;center"; - - htop = { - command = "alacritty --class htop-scratch --title htop -e htop"; - class = "htop-scratch"; - }; - - volume = { - command = "pavucontrol"; - class = "org.pulseaudio.pavucontrol"; - }; - - spotify = { - command = "spotify"; - class = "spotify"; - }; - - element = { - command = "element-desktop"; - class = "Element"; - }; - - slack = { - command = "slack"; - class = "Slack"; - }; - - transmission = { - command = "transmission-gtk"; - class = "transmission-gtk"; - }; - - dropdown = { - command = "ghostty --config-file=/home/imalison/.config/ghostty/dropdown"; - class = "com.mitchellh.ghostty.dropdown"; - options = "persist"; - rules = "float;size monitor_w monitor_h*0.5;move 0 60;noborder;noshadow;animation slide"; - }; - - gmail = { - command = "google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox"; - class = "google-chrome"; - title = "Gmail"; - }; - - messages = { - command = "google-chrome-stable --new-window https://messages.google.com/web/conversations"; - class = "google-chrome"; - title = "Messages"; + xdg.configFile."hyprscratch/config.conf" = lib.mkIf (!cfg.useLuaConfigBranch) { + text = lib.hm.generators.toHyprconf { + attrs = hyprscratchSettings; }; }; - }; - }) + + xdg.configFile."hypr/hyprland.conf" = { + force = true; + source = hyprConfig "hyprland.conf"; + }; + + xdg.configFile."hypr/hyprland.lua" = lib.mkIf cfg.useLuaConfigBranch { + force = true; + source = hyprConfig "hyprland.lua"; + }; + + xdg.configFile."hypr/hypridle.conf".source = hyprConfig "hypridle.conf"; + + xdg.configFile."hypr/hyprlock.conf".source = hyprConfig "hyprlock.conf"; + + xdg.configFile."hypr/scripts".enable = false; + } + ) ]; # Hyprland-specific packages - environment.systemPackages = with pkgs; [ - # Hyprland utilities - hyprpaper # Wallpaper - hypridle # Idle daemon - hyprlock # Screen locker - hyprcursor # Cursor themes - wl-clipboard # Clipboard for Wayland - wtype # Wayland input typing - cliphist # Clipboard history - grim # Screenshot utility - slurp # Region selection - swappy # Screenshot annotation - nwg-displays # GUI monitor arrangement - mpv # Graphical screensaver payload - ddcutil # Monitor input switching over DDC/CI + environment.systemPackages = + with pkgs; + [ + # Hyprland utilities + hyprpaper # Wallpaper + hypridle # Idle daemon + hyprlock # Screen locker + hyprcursor # Cursor themes + wl-clipboard # Clipboard for Wayland + wtype # Wayland input typing + cliphist # Clipboard history + grim # Screenshot utility + slurp # Region selection + swappy # Screenshot annotation + nwg-displays # GUI monitor arrangement + mpvpaper # Layer-shell video screensaver payload + ddcutil # Monitor input switching over DDC/CI - # For scripts - jq - ] ++ luaPluginPackages ++ lib.optionals enableExternalPluginPackages [ - # External plugin packages are pinned to the stable 0.53 stack. - # Keep hy3 on the stable stack; the Lua branch uses hyprNStack and the - # forked Lua-compatible hyprexpo input instead. - inputs.hy3.packages.${system}.hy3 - hyprexpoPatched - ]; + # For scripts + jq + ] + ++ luaPluginPackages + ++ lib.optionals enableExternalPluginPackages [ + # External plugin packages are pinned to the stable 0.53 stack. + # Keep hy3 on the stable stack; the Lua branch uses hyprNStack and the + # forked Lua-compatible hyprexpo input instead. + inputs.hy3.packages.${system}.hy3 + inputs.hyprland-plugins.packages.${system}.hyprexpo + ]; }; in -enabledModule // { +enabledModule +// { options = lib.recursiveUpdate enabledModule.options { myModules.hyprland.useLuaConfigBranch = lib.mkOption { type = lib.types.bool; diff --git a/nixos/patches/hyprexpo-pr-612-workspace-numbers.patch b/nixos/patches/hyprexpo-pr-612-workspace-numbers.patch deleted file mode 100644 index 68d80f05..00000000 --- a/nixos/patches/hyprexpo-pr-612-workspace-numbers.patch +++ /dev/null @@ -1,228 +0,0 @@ -From aaefc0ff0bc4348de04f311ad0101da44c62ae94 Mon Sep 17 00:00:00 2001 -From: Ivan Malison -Date: Wed, 4 Feb 2026 00:54:52 -0800 -Subject: [PATCH 1/2] hyprexpo: optionally render workspace numbers - ---- - hyprexpo/README.md | 3 +- - hyprexpo/main.cpp | 2 + - hyprexpo/overview.cpp | 109 ++++++++++++++++++++++++++++++++++++++++++ - hyprexpo/overview.hpp | 4 ++ - 4 files changed, 117 insertions(+), 1 deletion(-) - -diff --git a/README.md b/README.md -index 97bd1d4..aac2e97 100644 ---- a/README.md -+++ b/README.md -@@ -28,6 +28,8 @@ gap_size | number | gap between desktops | `5` - bg_col | color | color in gaps (between desktops) | `rgb(000000)` - workspace_method | [center/first] [workspace] | position of the desktops | `center current` - skip_empty | boolean | whether the grid displays workspaces sequentially by id using selector "r" (`false`) or skips empty workspaces using selector "m" (`true`) | `false` -+show_workspace_numbers | boolean | show numeric labels for workspaces | `false` -+workspace_number_color | color | color of workspace number labels | `rgb(ffffff)` - gesture_distance | number | how far is the max for the gesture | `300` - - ### Keywords -@@ -57,4 +59,3 @@ off | hides the overview - disable | same as `off` - on | displays the overview - enable | same as `on` -- -diff --git a/main.cpp b/main.cpp -index 883fd82..ff9f380 100644 ---- a/main.cpp -+++ b/main.cpp -@@ -239,6 +239,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { - HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:bg_col", Hyprlang::INT{0xFF111111}); - HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method", Hyprlang::STRING{"center current"}); - HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty", Hyprlang::INT{0}); -+ HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:show_workspace_numbers", Hyprlang::INT{0}); -+ HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:workspace_number_color", Hyprlang::INT{0xFFFFFFFF}); - - HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gesture_distance", Hyprlang::INT{200}); - -diff --git a/overview.cpp b/overview.cpp -index 5721948..926a9f8 100644 ---- a/overview.cpp -+++ b/overview.cpp -@@ -1,5 +1,8 @@ - #include "overview.hpp" - #include -+#include -+#include -+#include - #define private public - #include - #include -@@ -15,6 +18,86 @@ - #undef private - #include "OverviewPassElement.hpp" - -+static Vector2D renderLabelTexture(SP out, const std::string& text, const CHyprColor& color, int fontSizePx) { -+ if (!out || text.empty() || fontSizePx <= 0) -+ return {}; -+ -+ auto measureSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); -+ auto measureCairo = cairo_create(measureSurface); -+ -+ PangoLayout* measureLayout = pango_cairo_create_layout(measureCairo); -+ pango_layout_set_text(measureLayout, text.c_str(), -1); -+ auto* fontDesc = pango_font_description_from_string("Sans Bold"); -+ pango_font_description_set_size(fontDesc, fontSizePx * PANGO_SCALE); -+ pango_layout_set_font_description(measureLayout, fontDesc); -+ pango_font_description_free(fontDesc); -+ -+ PangoRectangle inkRect, logicalRect; -+ pango_layout_get_extents(measureLayout, &inkRect, &logicalRect); -+ -+ const int textW = std::max(1, (int)std::ceil(logicalRect.width / (double)PANGO_SCALE)); -+ const int textH = std::max(1, (int)std::ceil(logicalRect.height / (double)PANGO_SCALE)); -+ -+ g_object_unref(measureLayout); -+ cairo_destroy(measureCairo); -+ cairo_surface_destroy(measureSurface); -+ -+ const int pad = std::max(4, (int)std::round(fontSizePx * 0.35)); -+ const int width = textW + pad * 2; -+ const int height = textH + pad * 2; -+ -+ auto surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); -+ auto cairo = cairo_create(surface); -+ -+ // Clear the pixmap -+ cairo_save(cairo); -+ cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); -+ cairo_paint(cairo); -+ cairo_restore(cairo); -+ -+ // Background for legibility -+ cairo_set_source_rgba(cairo, 0.0, 0.0, 0.0, 0.55); -+ cairo_rectangle(cairo, 0, 0, width, height); -+ cairo_fill(cairo); -+ -+ PangoLayout* layout = pango_cairo_create_layout(cairo); -+ pango_layout_set_text(layout, text.c_str(), -1); -+ fontDesc = pango_font_description_from_string("Sans Bold"); -+ pango_font_description_set_size(fontDesc, fontSizePx * PANGO_SCALE); -+ pango_layout_set_font_description(layout, fontDesc); -+ pango_font_description_free(fontDesc); -+ -+ pango_layout_get_extents(layout, &inkRect, &logicalRect); -+ const double xOffset = (width - logicalRect.width / (double)PANGO_SCALE) / 2.0; -+ const double yOffset = (height - logicalRect.height / (double)PANGO_SCALE) / 2.0; -+ -+ cairo_set_source_rgba(cairo, color.r, color.g, color.b, color.a); -+ cairo_move_to(cairo, xOffset, yOffset); -+ pango_cairo_show_layout(cairo, layout); -+ -+ g_object_unref(layout); -+ -+ cairo_surface_flush(surface); -+ -+ const auto DATA = cairo_image_surface_get_data(surface); -+ out->allocate(); -+ glBindTexture(GL_TEXTURE_2D, out->m_texID); -+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -+ -+#ifndef GLES2 -+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE); -+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); -+#endif -+ -+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); -+ -+ cairo_destroy(cairo); -+ cairo_surface_destroy(surface); -+ -+ return {width, height}; -+} -+ - static void damageMonitor(WP thisptr) { - g_pOverview->damage(); - } -@@ -34,11 +117,14 @@ COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn - static auto* const* PGAPS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gap_size")->getDataStaticPtr(); - static auto* const* PCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:bg_col")->getDataStaticPtr(); - static auto* const* PSKIP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty")->getDataStaticPtr(); -+ static auto* const* PSHOWNUM = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:show_workspace_numbers")->getDataStaticPtr(); -+ static auto* const* PNUMCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:workspace_number_color")->getDataStaticPtr(); - static auto const* PMETHOD = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method")->getDataStaticPtr(); - - SIDE_LENGTH = **PCOLUMNS; - GAP_WIDTH = **PGAPS; - BG_COLOR = **PCOL; -+ showWorkspaceNumbers = **PSHOWNUM; - - // process the method - bool methodCenter = true; -@@ -126,6 +212,17 @@ COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn - Vector2D tileRenderSize = (pMonitor->m_size - Vector2D{GAP_WIDTH * pMonitor->m_scale, GAP_WIDTH * pMonitor->m_scale} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; - CBox monbox{0, 0, tileSize.x * 2, tileSize.y * 2}; - -+ if (showWorkspaceNumbers) { -+ const CHyprColor numberColor = **PNUMCOL; -+ const int fontSizePx = std::max(12, (int)std::round(tileRenderSize.y * pMonitor->m_scale * 0.22)); -+ for (auto& image : images) { -+ if (image.workspaceID == WORKSPACE_INVALID) -+ continue; -+ image.labelTex = makeShared(); -+ image.labelSizePx = renderLabelTexture(image.labelTex, std::to_string(image.workspaceID), numberColor, fontSizePx); -+ } -+ } -+ - if (!ENABLE_LOWRES) - monbox = {{0, 0}, pMonitor->m_pixelSize}; - -@@ -452,6 +549,18 @@ void COverview::fullRender() { - texbox.round(); - CRegion damage{0, 0, INT16_MAX, INT16_MAX}; - g_pHyprOpenGL->renderTextureInternal(images[x + y * SIDE_LENGTH].fb.getTexture(), texbox, {.damage = &damage, .a = 1.0}); -+ -+ if (showWorkspaceNumbers) { -+ auto& image = images[x + y * SIDE_LENGTH]; -+ if (image.workspaceID != WORKSPACE_INVALID && image.labelTex && image.labelTex->m_texID != 0 && image.labelSizePx.x > 0 && image.labelSizePx.y > 0) { -+ const Vector2D labelSize = image.labelSizePx / pMonitor->m_scale; -+ const float margin = std::max(4.0, tileRenderSize.y * 0.05); -+ CBox labelBox = {x * tileRenderSize.x + x * GAPSIZE + margin, y * tileRenderSize.y + y * GAPSIZE + margin, labelSize.x, labelSize.y}; -+ labelBox.scale(pMonitor->m_scale).translate(pos->value()); -+ labelBox.round(); -+ g_pHyprOpenGL->renderTexture(image.labelTex, labelBox, {.a = 1.0}); -+ } -+ } - } - } - } -diff --git a/overview.hpp b/overview.hpp -index 4b02400..1f6bf3c 100644 ---- a/overview.hpp -+++ b/overview.hpp -@@ -8,6 +8,7 @@ - #include - #include - #include -+class CTexture; - - // saves on resources, but is a bit broken rn with blur. - // hyprland's fault, but cba to fix. -@@ -58,6 +59,8 @@ class COverview { - int64_t workspaceID = -1; - PHLWORKSPACE pWorkspace; - CBox box; -+ SP labelTex; -+ Vector2D labelSizePx; - }; - - Vector2D lastMousePosLocal = Vector2D{}; -@@ -81,6 +84,7 @@ class COverview { - - bool swipe = false; - bool swipeWasCommenced = false; -+ bool showWorkspaceNumbers = false; - - friend class COverviewPassElement; - }; --- -2.52.0 - - diff --git a/nixos/patches/hyprexpo-pr-616-bring-mode.patch b/nixos/patches/hyprexpo-pr-616-bring-mode.patch deleted file mode 100644 index 58760a2c..00000000 --- a/nixos/patches/hyprexpo-pr-616-bring-mode.patch +++ /dev/null @@ -1,147 +0,0 @@ -From edc05ce88f79ceda0cdcb9aa68ec371b1af323de Mon Sep 17 00:00:00 2001 -From: Ivan Malison -Date: Mon, 16 Feb 2026 21:50:16 -0800 -Subject: [PATCH 2/2] hyprexpo: add bring selection mode - ---- - hyprexpo/README.md | 1 + - hyprexpo/main.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++ - hyprexpo/overview.cpp | 12 +++++++++-- - hyprexpo/overview.hpp | 3 ++- - 4 files changed, 63 insertions(+), 3 deletions(-) - -diff --git a/README.md b/README.md -index aac2e97..084f02b 100644 ---- a/README.md -+++ b/README.md -@@ -55,6 +55,7 @@ Here are a list of options you can use: - | --- | --- | - toggle | displays if hidden, hide if displayed - select | selects the hovered desktop -+bring | brings a window from the hovered desktop to the current desktop - off | hides the overview - disable | same as `off` - on | displays the overview -diff --git a/main.cpp b/main.cpp -index ff9f380..78bac24 100644 ---- a/main.cpp -+++ b/main.cpp -@@ -65,6 +65,47 @@ static void hkAddDamageB(void* thisptr, const pixman_region32_t* rg) { - g_pOverview->onDamageReported(); - } - -+static PHLWINDOW windowToBringFromWorkspace(const PHLWORKSPACE& workspace) { -+ if (!workspace) -+ return nullptr; -+ -+ for (auto it = g_pCompositor->m_windows.rbegin(); it != g_pCompositor->m_windows.rend(); ++it) { -+ const auto& w = *it; -+ if (!w || w->m_workspace != workspace || !w->m_isMapped || w->isHidden()) -+ continue; -+ -+ return w; -+ } -+ -+ return nullptr; -+} -+ -+static SDispatchResult bringWindowFromWorkspace(int64_t sourceWorkspaceID) { -+ if (sourceWorkspaceID == WORKSPACE_INVALID) -+ return {.success = false, .error = "selected workspace is empty"}; -+ -+ const auto FOCUSSTATE = Desktop::focusState(); -+ const auto MONITOR = FOCUSSTATE->monitor(); -+ if (!MONITOR || !MONITOR->m_activeWorkspace) -+ return {.success = false, .error = "no active monitor/workspace"}; -+ -+ if (sourceWorkspaceID == MONITOR->activeWorkspaceID()) -+ return {}; -+ -+ const auto SOURCEWORKSPACE = g_pCompositor->getWorkspaceByID(sourceWorkspaceID); -+ if (!SOURCEWORKSPACE) -+ return {.success = false, .error = "selected workspace is not open"}; -+ -+ const auto WINDOW = windowToBringFromWorkspace(SOURCEWORKSPACE); -+ if (!WINDOW) -+ return {.success = false, .error = "selected workspace has no mapped windows"}; -+ -+ g_pCompositor->moveWindowToWorkspaceSafe(WINDOW, MONITOR->m_activeWorkspace); -+ FOCUSSTATE->fullWindowFocus(WINDOW); -+ g_pCompositor->warpCursorTo(WINDOW->middle()); -+ return {}; -+} -+ - static SDispatchResult onExpoDispatcher(std::string arg) { - - if (g_pOverview && g_pOverview->m_isSwiping) -@@ -77,6 +118,15 @@ static SDispatchResult onExpoDispatcher(std::string arg) { - } - return {}; - } -+ if (arg == "bring") { -+ if (g_pOverview) { -+ g_pOverview->selectHoveredWorkspace(); -+ const auto BRINGRESULT = bringWindowFromWorkspace(g_pOverview->selectedWorkspaceID()); -+ g_pOverview->close(false); -+ return BRINGRESULT; -+ } -+ return {}; -+ } - if (arg == "toggle") { - if (g_pOverview) - g_pOverview->close(); -diff --git a/overview.cpp b/overview.cpp -index 926a9f8..45ee982 100644 ---- a/overview.cpp -+++ b/overview.cpp -@@ -343,6 +343,14 @@ void COverview::selectHoveredWorkspace() { - closeOnID = x + y * SIDE_LENGTH; - } - -+int64_t COverview::selectedWorkspaceID() const { -+ const int ID = closeOnID == -1 ? openedID : closeOnID; -+ if (ID < 0 || ID >= (int)images.size()) -+ return WORKSPACE_INVALID; -+ -+ return images[ID].workspaceID; -+} -+ - void COverview::redrawID(int id, bool forcelowres) { - if (!pMonitor) - return; -@@ -451,7 +459,7 @@ void COverview::onDamageReported() { - g_pCompositor->scheduleFrameForMonitor(pMonitor.lock()); - } - --void COverview::close() { -+void COverview::close(bool switchToSelection) { - if (closing) - return; - -@@ -471,7 +479,7 @@ void COverview::close() { - - redrawAll(); - -- if (TILE.workspaceID != pMonitor->activeWorkspaceID()) { -+ if (switchToSelection && TILE.workspaceID != pMonitor->activeWorkspaceID()) { - pMonitor->setSpecialWorkspace(0); - - // If this tile's workspace was WORKSPACE_INVALID, move to the next -diff --git a/overview.hpp b/overview.hpp -index 1f6bf3c..ca59f32 100644 ---- a/overview.hpp -+++ b/overview.hpp -@@ -33,8 +33,9 @@ class COverview { - void onSwipeEnd(); - - // close without a selection -- void close(); -+ void close(bool switchToSelection = true); - void selectHoveredWorkspace(); -+ int64_t selectedWorkspaceID() const; - - bool blockOverviewRendering = false; - bool blockDamageReporting = false; --- -2.52.0 -