fix freeze eww

This commit is contained in:
2026-09-11 22:40:11 +02:00
parent 98ac3a8844
commit c8251c6f6d
12 changed files with 76 additions and 26 deletions
@@ -39,6 +39,18 @@ detach() {
systemd-run --user --collect --quiet "${unit[@]}" -- "$cmd" "$@"
}
# eww kills a click handler that has not returned within the widget's
# :timeout (200 ms) — less than the eww client calls most handlers make
# (~50 ms each), let alone bluetoothctl or a station list fetch. Re-run
# this script detached and return at once. Call it with the original
# arguments, before anything with side effects.
# detach_self "$@"
detach_self() {
[[ -n ${EWW_DETACHED-} ]] && return 0
EWW_DETACHED=1 setsid -f "$0" "$@"
exit 0
}
# Event multiplexer for listener scripts. Producers run in the background
# and write one line per event into a fifo; the main loop re-evaluates the
# state on every event, coalescing bursts into a single refresh.
@@ -5,6 +5,7 @@ active() { rfkill list wifi 2>/dev/null | grep -q "Soft blocked: yes"; }
case ${1:-} in
status) active && echo true || echo false ;;
*)
detach_self "$@"
if active; then
rfkill unblock all
state=false
@@ -3,6 +3,7 @@
case ${1:-} in
status) dunstctl is-paused ;;
*)
detach_self "$@"
dunstctl set-paused toggle
state=$(dunstctl is-paused)
eww update do-not-disturb="$state" 2>/dev/null
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
# Connect or disconnect the bluetooth device with MAC $1.
mac=$1
[[ -n $mac ]] || exit 64
detach_self "$@"
if bluetoothctl info "$mac" 2>/dev/null | grep -q "Connected: yes"; then
bluetoothctl disconnect "$mac"
else
@@ -4,6 +4,7 @@
# Needs the polkit rule from modules/nixos/system/network/wireguard.
name=$1
[[ -n $name ]] || exit 64
detach_self "$@"
if [[ -d /sys/class/net/$name ]]; then
systemctl stop "wireguard-$name.service"
else
@@ -8,6 +8,7 @@ active() { systemctl --user is-active --quiet "$unit" || pgrep -x wlsunset >/dev
case ${1:-} in
status) active && echo true || echo false ;;
*)
detach_self "$@"
if active; then
systemctl --user stop "$unit" 2>/dev/null
pkill -x wlsunset
@@ -11,6 +11,7 @@
# reply can never spawn a second daemon).
panel=$1
[[ -n $panel ]] || exit 64
detach_self "$@"
# One toggle at a time; the lock dies with this process. fd 9 is closed for
# every eww call so nothing can inherit it.
@@ -5,6 +5,7 @@ active() { [[ $(powerprofilesctl get 2>/dev/null) == power-saver ]]; }
case ${1:-} in
status) active && echo true || echo false ;;
*)
detach_self "$@"
if active; then
powerprofilesctl set balanced 2>/dev/null
state=false
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
# Toggle the full-screen power menu on the focused monitor. The open's exit
# status is ignored: it exceeds the client's 100 ms reply deadline.
detach_self "$@"
eww close popup 2>/dev/null
eww update active-panel= 2>/dev/null
screen=$(eww-bar focused)
@@ -216,10 +216,10 @@ status_loop() {
}
case ${1:-} in
start) start "$2" ;;
stop) stop ;;
toggle) if playing; then stop; else start "$(current_id)"; fi ;;
vol) set_volume "$2" ;;
mute) toggle_mute ;;
start) detach_self "$@"; start "$2" ;;
stop) detach_self "$@"; stop ;;
toggle) detach_self "$@"; if playing; then stop; else start "$(current_id)"; fi ;;
vol) detach_self "$@"; set_volume "$2" ;;
mute) detach_self "$@"; toggle_mute ;;
*) status_loop ;;
esac
@@ -8,6 +8,7 @@ active() { hyprctl getoption decoration:screen_shader 2>/dev/null | grep -qF "$s
case ${1:-} in
status) active && echo true || echo false ;;
*)
detach_self "$@"
if active; then
hyprctl eval 'hl.config({ decoration = { screen_shader = "" } })' >/dev/null
hyprctl eval 'hl.config({ render = { use_fp16 = 2 } })' >/dev/null
+49 -21
View File
@@ -56,6 +56,9 @@ log() { printf 'eww-bar: %s\n' "$*" >&2; }
ewwc() { timeout 5 eww --no-daemonize "$@" 9>&-; }
ping_daemon() { ewwc ping >/dev/null 2>&1; }
# Empty when the daemon did not answer within the client's 100 ms deadline —
# its main thread is busy (a popup takes seconds to build) — as well as when
# nothing is open. Only a non-empty listing is authoritative.
list_windows() { ewwc active-windows 2>/dev/null || true; }
bar_open() { list_windows | grep -qx 'bar: bar'; }
@@ -69,6 +72,28 @@ wait_for_window() {
return 1
}
# window_state NAME [SECONDS] → open | closed (listed without NAME) |
# busy (nothing listed within SECONDS: main thread stuck, or nothing open)
window_state() {
local i n=$((${2:-5} * 5)) out
for ((i = 0; i < n; i++)); do
out=$(list_windows)
if [[ -n $out ]]; then
if grep -qx "$1: $1" <<<"$out"; then echo open; else echo closed; fi
return 0
fi
sleep 0.2
done
echo busy
}
# The compositor's view: does the daemon own any layer surface? Tells a
# daemon that is merely not answering from one with nothing on screen.
daemon_has_layer() {
hyprctl -j layers 2>/dev/null \
| jq -e --argjson pid "$(main_pid)" '[.. | objects | select(.pid? == $pid)] | length > 0' >/dev/null 2>&1
}
# ---------------------------------------------------------------- daemon ---
# nixpkgs wraps eww (wrapGAppsHook), so the running process is `.eww-wrapped`;
@@ -126,13 +151,13 @@ wait_for_daemon() {
start_unit() {
systemctl --user reset-failed "$unit" >/dev/null 2>&1 || true
systemctl --user start "$unit"
systemctl --user start --no-block "$unit"
}
restart_unit() {
log "restarting $unit"
systemctl --user reset-failed "$unit" >/dev/null 2>&1 || true
systemctl --user restart "$unit"
systemctl --user restart --no-block "$unit"
}
ensure() {
@@ -194,7 +219,7 @@ close_popups() {
# open_bar [--force] [SPEC]
open_bar() {
local force=0 spec screen
local force=0 spec screen state
if [[ ${1:-} == --force ]]; then force=1; shift; fi
spec=${1:-$(saved_screen)}
spec=${spec:-$default_screen}
@@ -210,17 +235,18 @@ open_bar() {
# client deadline), so the result is verified instead.
close_popups
ewwc open bar --screen "$screen" >/dev/null 2>&1 || true
if ! wait_for_window bar 5; then
if [[ $screen != "$default_screen" ]]; then
log "cannot open the bar on '$screen', using screen $default_screen"
screen=$default_screen
ewwc open bar --screen "$screen" >/dev/null 2>&1 || true
fi
if ! wait_for_window bar 5; then
log "the bar does not come up, restarting $unit"
restart_unit
exit 1
fi
state=$(window_state bar 10)
# Only an answered "no bar" means the screen was refused; busy just needs time.
if [[ $state == closed && $screen != "$default_screen" ]]; then
log "cannot open the bar on '$screen', using screen $default_screen"
screen=$default_screen
ewwc open bar --screen "$screen" >/dev/null 2>&1 || true
state=$(window_state bar 10)
fi
if [[ $state != open ]]; then
log "the bar does not come up ($state), restarting $unit"
restart_unit
exit 1
fi
printf '%s\n' "$spec" >"$screen_file"
printf '%s\n' "$screen" >"$current_file"
@@ -301,14 +327,16 @@ check() {
strike "daemon does not answer"
return 0
fi
if wait_for_window bar 3; then
rm -f "$fail_file"
return 0
fi
# Either nothing is open or the main loop is frozen (empty reply). Try to
# open the bar: a frozen daemon still won't list it afterwards.
case $(window_state bar 10) in
open) rm -f "$fail_file"; return 0 ;;
# Nothing listed for 10 s while something is on screen: the main loop is
# frozen or still building a popup. Leave the windows alone; a second
# miss restarts the unit.
busy) if daemon_has_layer; then strike "UI unresponsive"; return 0; fi ;;
esac
# Listed without the bar, or nothing on screen at all: it really is gone.
open_bar || true
if wait_for_window bar 3; then
if wait_for_window bar 5; then
rm -f "$fail_file"
log "bar was missing, reopened"
else