vpn eww and eww crash
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
, gnugrep
|
||||
, gnused
|
||||
, hyprland
|
||||
, iproute2
|
||||
, jq
|
||||
, procps
|
||||
, systemd
|
||||
@@ -15,6 +16,6 @@
|
||||
# profiles and the user (`eww-bar status`, `eww-bar reset`).
|
||||
writeShellApplication {
|
||||
name = "eww-bar";
|
||||
runtimeInputs = [ coreutils eww gnugrep gnused hyprland jq procps systemd util-linux ];
|
||||
runtimeInputs = [ coreutils eww gnugrep gnused hyprland iproute2 jq procps systemd util-linux ];
|
||||
text = builtins.readFile ./eww-bar.sh;
|
||||
}
|
||||
|
||||
+98
-34
@@ -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)"
|
||||
|
||||
Reference in New Issue
Block a user