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:
@@ -17,7 +17,7 @@ monitor=,preferred,auto,1
|
||||
# =============================================================================
|
||||
# PROGRAMS
|
||||
# =============================================================================
|
||||
$terminal = alacritty
|
||||
$terminal = ghostty
|
||||
$fileManager = dolphin
|
||||
$menu = rofi -show drun -show-icons
|
||||
$runMenu = rofi -show run
|
||||
@@ -284,7 +284,7 @@ bind = $modAlt, C, exec, ~/.config/hypr/scripts/raise-or-run.sh google-chrome go
|
||||
# Toggle scratchpads
|
||||
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, 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, K, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh slack '^Slack$' - slack
|
||||
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
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Go to window (rofi window switcher)
|
||||
bind = $mainMod, G, exec, rofi -show window -show-icons
|
||||
# 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
|
||||
@@ -576,7 +576,7 @@ exec-once = ~/.config/hypr/scripts/workspace-history.sh
|
||||
# Scratchpad applications (spawn on demand via keybinds)
|
||||
# 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: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:slack silent] slack
|
||||
# exec-once = [workspace special:spotify silent] spotify
|
||||
|
||||
@@ -1,30 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
# 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
|
||||
|
||||
# Get current workspace
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/window-icon-map.sh"
|
||||
|
||||
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
|
||||
|
||||
# Get all windows and format for rofi
|
||||
WINDOWS=$(hyprctl clients -j | jq -r '.[] | select(.workspace.id >= 0 and .workspace.id != '"$CURRENT_WS"') | "\(.title) [\(.class)] - WS:\(.workspace.id) |\(.address)"')
|
||||
# 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 "$WINDOWS" ]; then
|
||||
if [ -z "$WINDOW_DATA" ]; then
|
||||
notify-send "Bring Window" "No windows on other workspaces"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Show rofi menu
|
||||
SELECTION=$(echo "$WINDOWS" | rofi -dmenu -i -p "Bring window" -format 's')
|
||||
addresses=()
|
||||
TMPFILE=$(mktemp)
|
||||
trap 'rm -f "$TMPFILE"' EXIT
|
||||
|
||||
if [ -n "$SELECTION" ]; then
|
||||
# Extract the window address (after the last |)
|
||||
ADDRESS=$(echo "$SELECTION" | sed 's/.*|//')
|
||||
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"
|
||||
|
||||
# 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"
|
||||
|
||||
# Focus the window
|
||||
hyprctl dispatch focuswindow "address:$ADDRESS"
|
||||
fi
|
||||
|
||||
33
dotfiles/config/hypr/scripts/go-to-window.sh
Executable file
33
dotfiles/config/hypr/scripts/go-to-window.sh
Executable 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
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Get current workspace and focused window
|
||||
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
|
||||
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
|
||||
@@ -13,21 +14,30 @@ if [ "$FOCUSED" = "null" ] || [ -z "$FOCUSED" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get all windows except focused
|
||||
WINDOWS=$(hyprctl clients -j | jq -r ".[] | select(.workspace.id >= 0 and .address != \"$FOCUSED\") | \"\(.title) [\(.class)] - WS:\(.workspace.id) |\(.address)\"")
|
||||
# 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 "$WINDOWS" ]; then
|
||||
if [ -z "$WINDOW_DATA" ]; then
|
||||
notify-send "Replace Window" "No other windows available"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Show rofi menu
|
||||
SELECTION=$(echo "$WINDOWS" | rofi -dmenu -i -p "Replace with" -format 's')
|
||||
addresses=()
|
||||
TMPFILE=$(mktemp)
|
||||
trap 'rm -f "$TMPFILE"' EXIT
|
||||
|
||||
if [ -n "$SELECTION" ]; then
|
||||
# Extract the window address
|
||||
ADDRESS=$(echo "$SELECTION" | sed 's/.*|//')
|
||||
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"
|
||||
|
||||
# Swap windows using hy3
|
||||
hyprctl dispatch hy3:movewindow "address:$ADDRESS"
|
||||
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
|
||||
|
||||
66
dotfiles/config/hypr/scripts/window-icon-map.sh
Executable file
66
dotfiles/config/hypr/scripts/window-icon-map.sh
Executable 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}"
|
||||
}
|
||||
Reference in New Issue
Block a user