hyprland: add rofi icons to window go/bring/replace pickers

Add desktop-entry icon lookup to the rofi window picker scripts,
matching the XMonad setup's icon support. A shared helper script
(window-icon-map.sh) builds a class→icon mapping from .desktop files
and each picker uses rofi's dmenu icon protocol (\0icon\x1f).

Also replaces the X11-only "rofi -show window" with a native
Hyprland window picker using hyprctl clients.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 17:08:12 -08:00
committed by Kat Huang
parent 87868495f8
commit 87b03b51f9
5 changed files with 149 additions and 30 deletions

View File

@@ -17,7 +17,7 @@ monitor=,preferred,auto,1
# ============================================================================= # =============================================================================
# PROGRAMS # PROGRAMS
# ============================================================================= # =============================================================================
$terminal = alacritty $terminal = ghostty
$fileManager = dolphin $fileManager = dolphin
$menu = rofi -show drun -show-icons $menu = rofi -show drun -show-icons
$runMenu = rofi -show run $runMenu = rofi -show run
@@ -284,7 +284,7 @@ bind = $modAlt, C, exec, ~/.config/hypr/scripts/raise-or-run.sh google-chrome go
# Toggle scratchpads # Toggle scratchpads
bind = $modAlt, E, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh element '^Element$' - element-desktop bind = $modAlt, E, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh element '^Element$' - element-desktop
bind = $modAlt, G, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh gmail '^google-chrome$' '.*@gmail.com.*Gmail.*' google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox bind = $modAlt, G, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh gmail '^google-chrome$' '.*@gmail.com.*Gmail.*' google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox
bind = $modAlt, H, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh htop '^htop-scratch$' - alacritty --class htop-scratch --title htop -e htop bind = $modAlt, H, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh htop '^htop-scratch$' - ghostty --class=htop-scratch --title=htop -e htop
bind = $modAlt, M, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh messages '^google-chrome$' '^Messages.*' google-chrome-stable --new-window https://messages.google.com/web/conversations bind = $modAlt, M, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh messages '^google-chrome$' '^Messages.*' google-chrome-stable --new-window https://messages.google.com/web/conversations
bind = $modAlt, K, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh slack '^Slack$' - slack bind = $modAlt, K, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh slack '^Slack$' - slack
bind = $modAlt, S, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh spotify '^spotify$' - spotify bind = $modAlt, S, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh spotify '^spotify$' - spotify
@@ -489,8 +489,8 @@ bind = $mainMod SHIFT, H, exec, ~/.config/hypr/scripts/workspace-move-to-empty.s
# WINDOW MANAGEMENT # WINDOW MANAGEMENT
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Go to window (rofi window switcher) # Go to window (rofi window switcher with icons)
bind = $mainMod, G, exec, rofi -show window -show-icons bind = $mainMod, G, exec, ~/.config/hypr/scripts/go-to-window.sh
# Bring window (move to current workspace) # Bring window (move to current workspace)
bind = $mainMod, B, exec, ~/.config/hypr/scripts/bring-window.sh bind = $mainMod, B, exec, ~/.config/hypr/scripts/bring-window.sh
@@ -576,7 +576,7 @@ exec-once = ~/.config/hypr/scripts/workspace-history.sh
# Scratchpad applications (spawn on demand via keybinds) # Scratchpad applications (spawn on demand via keybinds)
# exec-once = [workspace special:element silent] element-desktop # exec-once = [workspace special:element silent] element-desktop
# exec-once = [workspace special:gmail silent] google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox # exec-once = [workspace special:gmail silent] google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox
# exec-once = [workspace special:htop silent] alacritty --class htop-scratch --title htop -e htop # exec-once = [workspace special:htop silent] ghostty --class=htop-scratch --title=htop -e htop
# exec-once = [workspace special:messages silent] google-chrome-stable --new-window https://messages.google.com/web/conversations # exec-once = [workspace special:messages silent] google-chrome-stable --new-window https://messages.google.com/web/conversations
# exec-once = [workspace special:slack silent] slack # exec-once = [workspace special:slack silent] slack
# exec-once = [workspace special:spotify silent] spotify # exec-once = [workspace special:spotify silent] spotify

View File

@@ -1,30 +1,40 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Bring window to current workspace (like XMonad's bringWindow) # Bring window to current workspace (like XMonad's bringWindow)
# Uses rofi to select a window and moves it to the current workspace # Uses rofi with icons to select a window, then moves it here.
set -euo pipefail set -euo pipefail
# Get current workspace SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/window-icon-map.sh"
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id') CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
# Get all windows and format for rofi # Get windows on OTHER workspaces as TSV
WINDOWS=$(hyprctl clients -j | jq -r '.[] | select(.workspace.id >= 0 and .workspace.id != '"$CURRENT_WS"') | "\(.title) [\(.class)] - WS:\(.workspace.id) |\(.address)"') 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 "$WINDOWS" ]; then if [ -z "$WINDOW_DATA" ]; then
notify-send "Bring Window" "No windows on other workspaces" notify-send "Bring Window" "No windows on other workspaces"
exit 0 exit 0
fi fi
# Show rofi menu addresses=()
SELECTION=$(echo "$WINDOWS" | rofi -dmenu -i -p "Bring window" -format 's') TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
if [ -n "$SELECTION" ]; then while IFS=$'\t' read -r address class title ws_id; do
# Extract the window address (after the last |) icon=$(icon_for_class "$class")
ADDRESS=$(echo "$SELECTION" | sed 's/.*|//') addresses+=("$address")
printf '%-20s %-40s WS:%s\0icon\x1f%s\n' \
"$class" "${title:0:40}" "$ws_id" "$icon"
done <<< "$WINDOW_DATA" > "$TMPFILE"
# Move window to current workspace 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 movetoworkspace "$CURRENT_WS,address:$ADDRESS"
# Focus the window
hyprctl dispatch focuswindow "address:$ADDRESS" hyprctl dispatch focuswindow "address:$ADDRESS"
fi fi

View File

@@ -0,0 +1,33 @@
#!/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 '%-20s %-40s WS:%s\0icon\x1f%s\n' \
"$class" "${title:0:40}" "$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

View File

@@ -4,8 +4,9 @@
set -euo pipefail set -euo pipefail
# Get current workspace and focused window SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id') source "$SCRIPT_DIR/window-icon-map.sh"
FOCUSED=$(hyprctl activewindow -j | jq -r '.address') FOCUSED=$(hyprctl activewindow -j | jq -r '.address')
if [ "$FOCUSED" = "null" ] || [ -z "$FOCUSED" ]; then if [ "$FOCUSED" = "null" ] || [ -z "$FOCUSED" ]; then
@@ -13,21 +14,30 @@ if [ "$FOCUSED" = "null" ] || [ -z "$FOCUSED" ]; then
exit 0 exit 0
fi fi
# Get all windows except focused # Get all windows except focused as TSV
WINDOWS=$(hyprctl clients -j | jq -r ".[] | select(.workspace.id >= 0 and .address != \"$FOCUSED\") | \"\(.title) [\(.class)] - WS:\(.workspace.id) |\(.address)\"") 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 "$WINDOWS" ]; then if [ -z "$WINDOW_DATA" ]; then
notify-send "Replace Window" "No other windows available" notify-send "Replace Window" "No other windows available"
exit 0 exit 0
fi fi
# Show rofi menu addresses=()
SELECTION=$(echo "$WINDOWS" | rofi -dmenu -i -p "Replace with" -format 's') TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
if [ -n "$SELECTION" ]; then while IFS=$'\t' read -r address class title ws_id; do
# Extract the window address icon=$(icon_for_class "$class")
ADDRESS=$(echo "$SELECTION" | sed 's/.*|//') addresses+=("$address")
printf '%-20s %-40s WS:%s\0icon\x1f%s\n' \
"$class" "${title:0:40}" "$ws_id" "$icon"
done <<< "$WINDOW_DATA" > "$TMPFILE"
# Swap windows using hy3 INDEX=$(rofi -dmenu -i -show-icons -p "Replace with" -format i < "$TMPFILE") || exit 0
hyprctl dispatch hy3:movewindow "address:$ADDRESS"
if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then
hyprctl dispatch hy3:movewindow "address:${addresses[$INDEX]}"
fi fi

View File

@@ -0,0 +1,66 @@
#!/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}"
}