vpn eww and eww crash

This commit is contained in:
2026-09-10 21:02:27 +02:00
parent 7bb27f9373
commit a53f120b63
14 changed files with 246 additions and 51 deletions
+98 -34
View File
@@ -1,9 +1,17 @@
# eww-bar — lifecycle manager for the eww daemon and the bar window.
#
# The daemon is owned by the user unit eww.service. Every window open goes
# through `ensure` first: a plain `eww open` against a dead or stale socket
# silently forks a *second* daemon that takes over the socket path and orphans
# the first daemon's surfaces ("a bar/popup I can't close").
# The daemon is owned by the user unit eww.service. Two eww facts shape this
# script:
#
# * A client waits only 100 ms for the daemon's reply. Any slower command
# (opening the popup, reload) fails on the client side, and a plain
# `eww open` then forks a SECOND daemon that takes over the socket path and
# orphans the first one's surfaces ("two bars", "a popup I can't close").
# Every client call here passes --no-daemonize, which turns that into a
# harmless error; outcomes are verified with `active-windows`, never with
# the exit status.
# * `eww ping` is answered by the IPC thread; `eww active-windows` by the GTK
# main loop. Only the latter proves the UI is alive.
#
# SCREEN is a GDK monitor index (0), a connector name (DP-1) or
# desc:<prefix of the description shown by `hyprctl monitors`>. Descriptions
@@ -23,7 +31,7 @@ usage: eww-bar COMMAND [ARGS]
check health check run by eww-watchdog.timer
clean ExecStartPre: kill stray daemons, wipe stale sockets
focused print the name of the focused Hyprland monitor
status show unit state, open windows and the bar's screen
status show unit state, socket owner, open windows and screen
USAGE
}
@@ -42,6 +50,25 @@ mkdir -p "$state_dir"
log() { printf 'eww-bar: %s\n' "$*" >&2; }
# ---------------------------------------------------------------- client ---
# fd 9 (the lock) is closed for every client call so nothing can inherit it.
ewwc() { timeout 5 eww --no-daemonize "$@" 9>&-; }
ping_daemon() { ewwc ping >/dev/null 2>&1; }
list_windows() { ewwc active-windows 2>/dev/null || true; }
bar_open() { list_windows | grep -qx 'bar: bar'; }
# wait_for_window NAME [SECONDS]
wait_for_window() {
local i n=$((${2:-5} * 5))
for ((i = 0; i < n; i++)); do
list_windows | grep -qx "$1: $1" && return 0
sleep 0.2
done
return 1
}
# ---------------------------------------------------------------- daemon ---
# nixpkgs wraps eww (wrapGAppsHook), so the running process is `.eww-wrapped`;
@@ -60,18 +87,33 @@ unit_running() {
*) return 1 ;;
esac
}
main_pid() { systemctl --user show -p MainPID --value "$unit" 2>/dev/null || echo 0; }
# `eww ping` is answered by the IPC thread and only proves the socket is alive.
ping_daemon() { timeout 3 eww ping >/dev/null 2>&1 9>&-; }
# pids listening on an eww socket path. After a rival rebinds the path, ss
# lists BOTH sockets under the same name (the old one is merely unlinked), so
# every pid matters, not just the first.
socket_owners() {
ss -xlp 2>/dev/null | grep -F "$runtime_dir/eww-server_" \
| sed -n 's/.*pid=\([0-9]*\).*/\1/p' | sort -u | tr '\n' ' '
}
# `active-windows` is answered from the GTK main loop, so it also detects a
# frozen UI: the reply is then empty instead of listing the windows.
# fd 9 is closed for every client call so a daemonizing client (which can
# only happen when the socket is dead) can never inherit our lock.
list_windows() { timeout 5 eww active-windows 2>/dev/null 9>&- || true; }
bar_open() { list_windows | grep -qx 'bar: bar'; }
# The socket file is gone while the daemon lives on: a rival that crashed at
# startup unlinked it, and no client can reach the daemon any more.
socket_missing() { ! ls "$runtime_dir"/eww-server_* >/dev/null 2>&1; }
daemon_age() { ps -o etimes= -p "$(main_pid)" 2>/dev/null | tr -d " " || echo 0; }
healthy() { unit_running && ping_daemon; }
# pids of daemons other than the service's main process listening on the socket
rival_pids() {
local main pid
main=$(main_pid)
for pid in $(socket_owners); do
[[ $pid != "$main" ]] && printf '%s ' "$pid"
done
return 0
}
rival_daemon() { [[ -n $(rival_pids) ]]; }
healthy() { unit_running && ping_daemon && ! rival_daemon; }
wait_for_daemon() {
local i
@@ -98,10 +140,13 @@ ensure() {
(
flock -x 9
healthy && exit 0
# The unit is (re)starting — possibly running us from ExecStartPost —
# so give the daemon time to bind before declaring it dead.
if unit_running && wait_for_daemon; then exit 0; fi
log "daemon unreachable, restarting $unit"
if rival_daemon; then
log "a second eww daemon (pid $(rival_pids)) owns the socket"
elif unit_running && wait_for_daemon && ! rival_daemon; then
# The unit was (re)starting — possibly running us from ExecStartPost.
exit 0
fi
log "daemon unhealthy, restarting $unit"
systemctl --user stop "$unit" >/dev/null 2>&1 || true
kill_stray_daemons
start_unit
@@ -142,9 +187,9 @@ current_screen() { cat "$current_file" 2>/dev/null || true; }
close_popups() {
local w
for w in popup powermenu; do
timeout 3 eww close "$w" >/dev/null 2>&1 9>&- || true
list_windows | grep -qx "$w: $w" && { ewwc close "$w" >/dev/null 2>&1 || true; }
done
timeout 3 eww update active-panel= >/dev/null 2>&1 9>&- || true
ewwc update active-panel= >/dev/null 2>&1 || true
}
# open_bar [--force] [SPEC]
@@ -161,17 +206,21 @@ open_bar() {
exit 0
fi
# Popups may sit on an output that just vanished; `eww open` itself
# replaces an already-open bar.
# replaces an already-open bar. The exit status is meaningless (100 ms
# client deadline), so the result is verified instead.
close_popups
if ! timeout 10 eww open bar --screen "$screen" >/dev/null 9>&-; then
if [[ $screen == "$default_screen" ]]; then
log "eww open failed, restarting $unit"
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
log "cannot open the bar on '$screen', using screen $default_screen"
screen=$default_screen
timeout 10 eww open bar --screen "$screen" >/dev/null 9>&- || { restart_unit; exit 1; }
fi
printf '%s\n' "$spec" >"$screen_file"
printf '%s\n' "$screen" >"$current_file"
@@ -181,16 +230,20 @@ open_bar() {
close_bar() {
close_popups
timeout 3 eww close bar >/dev/null 2>&1 9>&- || true
ewwc close bar >/dev/null 2>&1 || true
rm -f "$current_file"
}
reload() {
ensure || return 1
if ! timeout 15 eww reload >/dev/null 9>&-; then
log "reload failed, restarting $unit"
# Slow: the client always hits its deadline, so ignore the status and
# verify that the daemon is still with us afterwards.
ewwc reload >/dev/null 2>&1 || true
sleep 1
if ! wait_for_daemon; then
log "daemon lost during reload, restarting $unit"
restart_unit
return
return 0
fi
open_bar --force
}
@@ -234,18 +287,28 @@ check() {
return 0
fi
[[ $(unit_state) == active ]] || return 0 # still starting; ExecStartPost handles it
if rival_daemon; then
log "a second eww daemon (pid $(rival_pids)) owns the socket, restarting $unit"
restart_unit
return 0
fi
if socket_missing && (($(daemon_age) > 15)); then
log "socket file is gone, restarting $unit"
restart_unit
return 0
fi
if ! ping_daemon; then
strike "daemon does not answer"
return 0
fi
if bar_open; then
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.
open_bar || true
if bar_open; then
if wait_for_window bar 3; then
rm -f "$fail_file"
log "bar was missing, reopened"
else
@@ -255,7 +318,8 @@ check() {
status() {
systemctl --user --no-pager status "$unit" 2>&1 | head -n 12 || true
printf '\nwindows:\n'
printf '\nsocket listeners: %s(service main pid %s)\n' "$(socket_owners)" "$(main_pid)"
printf 'windows:\n'
list_windows | sed 's/^/ /'
printf 'screen: %s (requested: %s)\n' "$(current_screen)" "$(saved_screen)"
printf 'strikes: %s\n' "$(cat "$fail_file" 2>/dev/null || echo 0)"