Working on smarter overflow indicator
This commit is contained in:
@@ -1,34 +1,77 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
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}"
|
COLUMN_WIDTH="${COLUMN_WIDTH:-0.5}"
|
||||||
ICON="${ICON:-}"
|
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() {
|
fail_json() {
|
||||||
printf '{"text":"%s !","tooltip":"hyprscroll-overflow: %s","class":"error"}\n' \
|
local msg="hyprscroll-overflow: ${1:-unknown error}"
|
||||||
"$ICON" "${1//\"/\\\"}"
|
printf '{"text":"%s !","tooltip":"%s","class":"error"}\n' \
|
||||||
|
"$(json_escape "$ICON")" "$(json_escape "$msg")"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
command -v hyprctl >/dev/null 2>&1 || fail_json "hyprctl not in PATH"
|
need() { command -v "$1" >/dev/null 2>&1 || fail_json "$1 not in PATH"; }
|
||||||
command -v jq >/dev/null 2>&1 || fail_json "jq not in PATH"
|
need hyprctl
|
||||||
|
need jq
|
||||||
|
need awk
|
||||||
|
|
||||||
read -r focused_mon_id focused_ws_id < <(
|
read -r focused_mon_id focused_ws_id < <(
|
||||||
hyprctl -j monitors 2>/dev/null | jq -r '
|
hyprctl -j monitors 2>/dev/null | jq -r '
|
||||||
.[] | select(.focused==true) | "\(.id) \(.activeWorkspace.id)"
|
.[] | 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="$(
|
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(.mapped == true)
|
||||||
| select(.workspace.id == $ws)
|
| select(.workspace.id == $ws)
|
||||||
| select(.monitor == $mid)
|
| select(.monitor == $mid)
|
||||||
] | length
|
] | length
|
||||||
'
|
' <<<"$clients_json"
|
||||||
)" || fail_json "failed to count clients"
|
)" || 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)} }')" \
|
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 ))
|
overflow=$(( win_count - max_visible ))
|
||||||
|
|
||||||
layout="$(hyprctl getoption general:layout 2>/dev/null | awk '/str:/ {print $2; exit}')"
|
layout="$(hyprctl getoption general:layout 2>/dev/null | awk '/str:/ {print $2; exit}' || true)"
|
||||||
[[ -n "${layout:-}" ]] || layout=""
|
layout="${layout:-}"
|
||||||
|
|
||||||
if (( overflow > 0 )) && [[ "$layout" == "scrolling" ]]; then
|
# Click action: pick a window and focus it
|
||||||
printf '{"text":"%s +%d","tooltip":"%d windows; approx %d fit (column_width=%s)","class":"overflow"}\n' \
|
if [[ "${1:-}" == "--pick" ]]; then
|
||||||
"$ICON" "$overflow" "$win_count" "$max_visible" "$COLUMN_WIDTH"
|
# Build menu lines: address at end so we can parse it back reliably.
|
||||||
else
|
menu="$(
|
||||||
# truly empty (and tooltip empty) + class for CSS
|
jq -r --argjson ws "$focused_ws_id" --argjson mid "$focused_mon_id" '
|
||||||
printf '{"text":"","tooltip":"","class":"hidden"}\n'
|
[ .[]
|
||||||
|
| 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
|
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")"
|
||||||
|
|||||||
@@ -41,8 +41,11 @@
|
|||||||
"return-type": "json",
|
"return-type": "json",
|
||||||
"interval": 1,
|
"interval": 1,
|
||||||
"format": "{text}",
|
"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": {
|
"idle_inhibitor": {
|
||||||
"format": "{icon}",
|
"format": "{icon}",
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ window#waybar {
|
|||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: @text;
|
color: @text;
|
||||||
border: 1px solid @inactive;
|
border: 2px solid @inactive;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,3 +159,58 @@ window#waybar {
|
|||||||
#custom-notifications.unread {
|
#custom-notifications.unread {
|
||||||
color: @yellow;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user