From c185247b6284d64c63ba6bbd88608288c31eb0d0 Mon Sep 17 00:00:00 2001 From: Henro Veijer Date: Fri, 27 Feb 2026 18:07:25 +0100 Subject: [PATCH] Working on smarter overflow indicator --- .../hypr/scripts/hyprscroll-overflow.sh | 118 +++++++++++++++--- .../assets/conf/desktop/waybar/config.jsonc | 7 +- .../assets/conf/desktop/waybar/style.css | 57 ++++++++- 3 files changed, 163 insertions(+), 19 deletions(-) diff --git a/henrovnix_ok/assets/conf/desktop/hypr/scripts/hyprscroll-overflow.sh b/henrovnix_ok/assets/conf/desktop/hypr/scripts/hyprscroll-overflow.sh index ed90dbaa3..34f884d78 100755 --- a/henrovnix_ok/assets/conf/desktop/hypr/scripts/hyprscroll-overflow.sh +++ b/henrovnix_ok/assets/conf/desktop/hypr/scripts/hyprscroll-overflow.sh @@ -1,34 +1,77 @@ #!/usr/bin/env bash set -euo pipefail +# Usage: +# Waybar exec: hyprscroll-overflow.sh +# Waybar on-click: hyprscroll-overflow.sh --pick +# +# Env: +# COLUMN_WIDTH=0.5 +# ICON="󰓒" +# DMENU_CMD="walker --dmenu" (or: "wofi --dmenu", "rofi -dmenu", etc.) + COLUMN_WIDTH="${COLUMN_WIDTH:-0.5}" ICON="${ICON:-󰓒}" +DMENU_CMD="${DMENU_CMD:-walker --dmenu}" + +json_escape() { + # minimal JSON string escape (quotes, backslashes, newlines, tabs, CR) + local s="${1:-}" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//$'\n'/\\n}" + s="${s//$'\r'/\\r}" + s="${s//$'\t'/\\t}" + printf '%s' "$s" +} fail_json() { - printf '{"text":"%s !","tooltip":"hyprscroll-overflow: %s","class":"error"}\n' \ - "$ICON" "${1//\"/\\\"}" + local msg="hyprscroll-overflow: ${1:-unknown error}" + printf '{"text":"%s !","tooltip":"%s","class":"error"}\n' \ + "$(json_escape "$ICON")" "$(json_escape "$msg")" exit 0 } -command -v hyprctl >/dev/null 2>&1 || fail_json "hyprctl not in PATH" -command -v jq >/dev/null 2>&1 || fail_json "jq not in PATH" +need() { command -v "$1" >/dev/null 2>&1 || fail_json "$1 not in PATH"; } +need hyprctl +need jq +need awk read -r focused_mon_id focused_ws_id < <( hyprctl -j monitors 2>/dev/null | jq -r ' .[] | select(.focused==true) | "\(.id) \(.activeWorkspace.id)" ' -) || fail_json "failed to read monitors" +) || fail_json "failed to read focused monitor/workspace" -[[ -n "${focused_mon_id:-}" && -n "${focused_ws_id:-}" ]] || fail_json "no focused monitor?" +[[ -n "${focused_mon_id:-}" && -n "${focused_ws_id:-}" ]] || fail_json "no focused monitor/workspace" + +# Collect windows (current ws + current monitor, mapped only) +clients_json="$(hyprctl -j clients 2>/dev/null)" || fail_json "failed to read clients" + +# Tooltip list (multiline) +# Include a stable selector: address (hex), plus class + title for humans. +tooltip_list="$( + jq -r --argjson ws "$focused_ws_id" --argjson mid "$focused_mon_id" ' + [ .[] + | select(.mapped == true) + | select(.workspace.id == $ws) + | select(.monitor == $mid) + | {address, class, title} + ] + | to_entries + | map("\(.key+1). [\(.value.class)] \(.value.title) (\(.value.address))") + | .[] + ' <<<"$clients_json" +)" || fail_json "failed to build tooltip list" win_count="$( - hyprctl -j clients 2>/dev/null | jq --argjson ws "$focused_ws_id" --argjson mid "$focused_mon_id" ' + jq -r --argjson ws "$focused_ws_id" --argjson mid "$focused_mon_id" ' [ .[] | select(.mapped == true) | select(.workspace.id == $ws) | select(.monitor == $mid) ] | length - ' + ' <<<"$clients_json" )" || fail_json "failed to count clients" max_visible="$(awk -v w="$COLUMN_WIDTH" 'BEGIN{ if (w<=0) {print 1} else {print int(1.0/w)} }')" \ @@ -37,13 +80,56 @@ max_visible="$(awk -v w="$COLUMN_WIDTH" 'BEGIN{ if (w<=0) {print 1} else {print overflow=$(( win_count - max_visible )) -layout="$(hyprctl getoption general:layout 2>/dev/null | awk '/str:/ {print $2; exit}')" -[[ -n "${layout:-}" ]] || layout="" +layout="$(hyprctl getoption general:layout 2>/dev/null | awk '/str:/ {print $2; exit}' || true)" +layout="${layout:-}" -if (( overflow > 0 )) && [[ "$layout" == "scrolling" ]]; then - printf '{"text":"%s +%d","tooltip":"%d windows; approx %d fit (column_width=%s)","class":"overflow"}\n' \ - "$ICON" "$overflow" "$win_count" "$max_visible" "$COLUMN_WIDTH" -else - # truly empty (and tooltip empty) + class for CSS - printf '{"text":"","tooltip":"","class":"hidden"}\n' +# Click action: pick a window and focus it +if [[ "${1:-}" == "--pick" ]]; then + # Build menu lines: address at end so we can parse it back reliably. + menu="$( + jq -r --argjson ws "$focused_ws_id" --argjson mid "$focused_mon_id" ' + [ .[] + | select(.mapped == true) + | select(.workspace.id == $ws) + | select(.monitor == $mid) + | {address, class, title} + ] + | map("[\(.class)] \(.title) \(.address)") + | .[] + ' <<<"$clients_json" + )" || exit 0 + + [[ -n "${menu:-}" ]] || exit 0 + + # shellcheck disable=SC2086 + choice="$(printf '%s\n' "$menu" | eval "$DMENU_CMD" || true)" + [[ -n "${choice:-}" ]] || exit 0 + + addr="$(awk '{print $NF}' <<<"$choice")" + [[ "$addr" =~ ^0x[0-9a-fA-F]+$ ]] || exit 0 + + # Focus brings it forward in normal tiling usage; for tricky stacking cases, + # you can also add: hyprctl dispatch bringactivetotop + hyprctl dispatch focuswindow "address:${addr}" >/dev/null 2>&1 || exit 0 + exit 0 fi + +# Status JSON for Waybar +# Make it ALWAYS visible so you can hover to see the list. +# Show +N only when overflow AND layout==scrolling, keeping your original intent. +text="$ICON" +cls="ok" +if (( overflow > 0 )) && [[ "$layout" == "scrolling" ]]; then + text="$ICON +$overflow" + cls="overflow" +fi + +tooltip="WS ${focused_ws_id} • ${win_count} window(s)\n" +tooltip+="Approx ${max_visible} fit (column_width=${COLUMN_WIDTH})\n" +tooltip+="------------------------------\n" +tooltip+="${tooltip_list:-"(no windows)"}" + +printf '{"text":"%s","tooltip":"%s","class":"%s"}\n' \ + "$(json_escape "$text")" \ + "$(json_escape "$tooltip")" \ + "$(json_escape "$cls")" diff --git a/henrovnix_ok/assets/conf/desktop/waybar/config.jsonc b/henrovnix_ok/assets/conf/desktop/waybar/config.jsonc index 4e23f51a5..69f824e31 100644 --- a/henrovnix_ok/assets/conf/desktop/waybar/config.jsonc +++ b/henrovnix_ok/assets/conf/desktop/waybar/config.jsonc @@ -41,8 +41,11 @@ "return-type": "json", "interval": 1, "format": "{text}", - "tooltip": "{tooltip}", - }, + "tooltip": true, + + // Click = choose a window and focus it + "on-click": "bash -lc 'COLUMN_WIDTH=0.5 DMENU_CMD=\"walker --dmenu\" \"$HOME/.config/hypr/scripts/hyprscroll-overflow.sh\" --pick'" + } "idle_inhibitor": { "format": "{icon}", diff --git a/henrovnix_ok/assets/conf/desktop/waybar/style.css b/henrovnix_ok/assets/conf/desktop/waybar/style.css index 65c11152b..a282654b5 100644 --- a/henrovnix_ok/assets/conf/desktop/waybar/style.css +++ b/henrovnix_ok/assets/conf/desktop/waybar/style.css @@ -34,7 +34,7 @@ window#waybar { min-width: 80px; background-color: transparent; color: @text; - border: 1px solid @inactive; + border: 2px solid @inactive; border-radius: 10px; } @@ -159,3 +159,58 @@ window#waybar { #custom-notifications.unread { color: @yellow; } + +/* ========================================================= + * Hyprscroll overflow indicator (custom/hyprscroll_overflow) + * States: .ok, .overflow, .error + * ========================================================= */ + +/* Default (no overflow): subtle pill, still hoverable for tooltip */ +#custom-hyprscroll_overflow.ok { + padding: 0px 1px; + min-width: 80px; + color: @subtext1; + border-radius: 10px; + + /* subtle outline so you know it's there */ + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(255, 255, 255, 0.03); +} + +/* Make it feel interactive (hover) */ +#custom-hyprscroll_overflow.ok:hover { + color: @text; + background-color: @surface1; + border: 1px solid rgba(255, 255, 255, 0.18); +} + +/* Overflow state: you already have this; keep it. + Optional: add hover tweak so it "pops" a bit. */ +#custom-hyprscroll_overflow.overflow:hover { + background: + linear-gradient(rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.1)) + padding-box, + linear-gradient(45deg, @blue, @green) border-box; +} + +/* Error state: clear but not screaming */ +#custom-hyprscroll_overflow.error { + padding: 0px 1px; + min-width: 80px; + color: @text; + border-radius: 10px; + + border: 1px solid rgba(255, 0, 0, 0.55); + background: rgba(255, 0, 0, 0.15); + font-weight: bold; +} + +/* Optional: if you keep .hidden in the script for any reason */ +#custom-hyprscroll_overflow.hidden { + padding: 0; + margin: 0; + min-width: 0; + border: 0; + background: transparent; + opacity: 0; +}