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
@@ -34,3 +34,10 @@
.bt-device-btn:hover { @include background-active; } .bt-device-btn:hover { @include background-active; }
.net-toggle-on, .bt-btn-on { @include color-base; } .net-toggle-on, .bt-btn-on { @include color-base; }
.net-toggle-off, .bt-btn-off { @include color-inactive; } .net-toggle-off, .bt-btn-off { @include color-inactive; }
// WireGuard
.wg-accent { background-color: $base0E; }
.wg-row { margin-bottom: $popup-scale * 4pt; }
.wg-name { font-size: 0.78em; font-weight: bold; }
.wg-up { @include color-base; }
.wg-down { @include color-inactive; }
@@ -4,8 +4,8 @@
(defwidget clock-mod [] (defwidget clock-mod []
(module (module
(eventbox (eventbox
:onhover "${EWW_CMD} update date_rev=true" :onhover "${EWW_CMD} --no-daemonize update date_rev=true"
:onhoverlost "${EWW_CMD} update date_rev=false" :onhoverlost "${EWW_CMD} --no-daemonize update date_rev=false"
:onclick "scripts/panel-toggle clock" :onclick "scripts/panel-toggle clock"
:onrightclick "scripts/powermenu-toggle" :onrightclick "scripts/powermenu-toggle"
(box (box
@@ -1,5 +1,5 @@
(deflisten net (deflisten net
:initial '{"wifi":{"connected":false,"enabled":false,"icon":"󰤮","ssid":"","ip":"","freq":0,"band":"","gen":"","signal":0},"ethernet":{"connected":false,"interface":"","ip":"","speed":""},"usb":{"connected":false,"interface":"","ip":""}}' :initial '{"wifi":{"connected":false,"enabled":false,"icon":"󰤮","ssid":"","ip":"","freq":0,"band":"","gen":"","signal":0},"ethernet":{"connected":false,"interface":"","ip":"","speed":""},"usb":{"connected":false,"interface":"","ip":""},"wireguard":[],"vpn":{"up":false,"name":""}}'
"scripts/net/net") "scripts/net/net")
(deflisten bt (deflisten bt
@@ -20,6 +20,11 @@
:class "net-icon net-active" :class "net-icon net-active"
:tooltip "USB: Connected" :tooltip "USB: Connected"
:text "󰌷") :text "󰌷")
(label
:visible {net.vpn.up}
:class "net-icon net-active"
:tooltip "VPN: ${net.vpn.name}"
:text "󰖂")
(label (label
:class "net-icon ${net.wifi.connected ? 'net-active' : 'net-dim'}" :class "net-icon ${net.wifi.connected ? 'net-active' : 'net-dim'}"
:tooltip {net.wifi.connected ? "WiFi: ${net.wifi.ssid}" : "WiFi: Disconnected"} :tooltip {net.wifi.connected ? "WiFi: ${net.wifi.ssid}" : "WiFi: Disconnected"}
@@ -3,6 +3,13 @@
# top of every wrapped script, right after setting PATH. # top of every wrapped script, right after setting PATH.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Every eww client call goes through this wrapper. A client waits only 100 ms
# for the daemon's reply; a slower command (opening the popup) would otherwise
# make a plain `eww open` fork a second daemon that takes over the socket —
# the "two bars" bug. With --no-daemonize it is just an error, and the daemon
# still carries out the command.
eww() { command eww --no-daemonize "$@"; }
# Print the payload only when it differs from the previous one. # Print the payload only when it differs from the previous one.
emit() { emit() {
[[ $1 == "${EMIT_LAST-}" ]] && return 0 [[ $1 == "${EMIT_LAST-}" ]] && return 0
@@ -70,8 +70,60 @@ state() {
"$w_connected" "$w_enabled" "$w_icon" "$(jstr "$w_ssid")" "$w_ip" "${w_freq:-0}" "$(band "$w_freq")" "$w_gen" "${w_signal:-0}" "$w_connected" "$w_enabled" "$w_icon" "$(jstr "$w_ssid")" "$w_ip" "${w_freq:-0}" "$(band "$w_freq")" "$w_gen" "${w_signal:-0}"
printf '"ethernet":{"connected":%s,"interface":"%s","ip":"%s","speed":"%s"},' \ printf '"ethernet":{"connected":%s,"interface":"%s","ip":"%s","speed":"%s"},' \
"$e_connected" "$eth" "$e_ip" "$e_speed" "$e_connected" "$eth" "$e_ip" "$e_speed"
printf '"usb":{"connected":%s,"interface":"%s","ip":"%s"}}' \ printf '"usb":{"connected":%s,"interface":"%s","ip":"%s"},' \
"$u_connected" "$usb" "$u_ip" "$u_connected" "$usb" "$u_ip"
wg_json
printf '}'
}
# --- WireGuard: interfaces from the wireguard-<name>.service units (so a
# tunnel that is down still shows up) plus any live wireguard-type link.
# `wg show` needs root, so peer names and the endpoint come from the peer
# units instead.
wg_ifaces() {
{
systemctl list-units --all --plain --no-legend 'wireguard-*.service' 2>/dev/null \
| awk '$1 !~ /-peer-/ { sub(/^wireguard-/, "", $1); sub(/\.service$/, "", $1); print $1 }'
ip -d -o link 2>/dev/null | awk '/ wireguard / { sub(":", "", $2); print $2 }'
} | sort -u
}
human_bytes() {
awk -v b="${1:-0}" 'BEGIN {
split("B KiB MiB GiB TiB", u, " "); i = 1
while (b >= 1024 && i < 5) { b /= 1024; i++ }
printf (i == 1 ? "%d %s" : "%.1f %s"), b, u[i] }'
}
wg_json() { # prints the "wireguard" array and the "vpn" summary
local name up ip rx tx peers endpoint peer_unit peer_script sep="" out="[" vpn_up=false vpn_name=""
while read -r name; do
[[ -n $name ]] || continue
up=false ip="" rx=0 tx=0 endpoint=""
if [[ -d /sys/class/net/$name ]]; then
ip -o link show "$name" 2>/dev/null | grep -q '<[^>]*UP' && up=true
ip=$(ipv4 "$name")
rx=$(<"/sys/class/net/$name/statistics/rx_bytes")
tx=$(<"/sys/class/net/$name/statistics/tx_bytes")
fi
peers=$(systemctl list-units --all --plain --no-legend "wireguard-$name-peer-*.service" 2>/dev/null \
| awk '{ print $1 }' | sed "s/^wireguard-$name-peer-//; s/\.service\$//" | paste -sd, -)
peer_unit=$(systemctl list-units --all --plain --no-legend "wireguard-$name-peer-*.service" 2>/dev/null \
| awk 'NR == 1 { print $1 }')
# NixOS puts `wg set … endpoint "host:port"` in the peer unit's start script
if [[ -n $peer_unit ]]; then
peer_script=$(systemctl show -p ExecStart --value "$peer_unit" 2>/dev/null \
| sed -n 's/.*path=\([^ ;]*\).*/\1/p' | awk 'NR == 1')
[[ -r $peer_script ]] && endpoint=$(grep -o 'endpoint "[^"]*"' "$peer_script" 2>/dev/null \
| awk -F'"' 'NR == 1 { print $2 }')
fi
if [[ $up == true && $vpn_up == false ]]; then vpn_up=true vpn_name=$name; fi
[[ -z $vpn_name ]] && vpn_name=$name
out+="$sep{\"name\":\"$(jstr "$name")\",\"up\":$up,\"ip\":\"$ip\",\"rx\":\"$(human_bytes "$rx")\",\"tx\":\"$(human_bytes "$tx")\",\"peers\":\"$(jstr "$peers")\",\"endpoint\":\"$(jstr "$endpoint")\"}"
sep=,
done < <(wg_ifaces)
printf '"wireguard":%s],"vpn":{"up":%s,"name":"%s"}' "$out" "$vpn_up" "$(jstr "$vpn_name")"
} }
refresh() { emit "$(state)"; } refresh() { emit "$(state)"; }
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Bring the WireGuard tunnel $1 down (stop its service, which takes the peer
# units with it) or up (start its target, which pulls service and peers).
# Needs the polkit rule from modules/nixos/system/network/wireguard.
name=$1
[[ -n $name ]] || exit 64
if [[ -d /sys/class/net/$name ]]; then
systemctl stop "wireguard-$name.service"
else
systemctl start "wireguard-$name.target"
fi
@@ -4,6 +4,11 @@
# The popup hosts every panel behind revealers, so switching panels while it # The popup hosts every panel behind revealers, so switching panels while it
# is open is just a variable update (animated, no scripts restarted). It is # is open is just a variable update (animated, no scripts restarted). It is
# only closed and re-opened when it has to move to another monitor. # only closed and re-opened when it has to move to another monitor.
#
# `eww open popup` takes longer than the client's 100 ms reply deadline, so
# its exit status and error output are meaningless and ignored (the daemon
# carries the command out anyway; the lib's eww wrapper makes sure a slow
# reply can never spawn a second daemon).
panel=$1 panel=$1
[[ -n $panel ]] || exit 64 [[ -n $panel ]] || exit 64
@@ -12,7 +17,7 @@ panel=$1
exec 9>"${XDG_RUNTIME_DIR:-/tmp}/eww-panel-toggle.lock" exec 9>"${XDG_RUNTIME_DIR:-/tmp}/eww-panel-toggle.lock"
flock -n 9 || exit 0 flock -n 9 || exit 0
ew() { timeout 5 eww "$@" 9>&-; } ew() { eww "$@" 9>&-; }
current=$(ew get active-panel 2>/dev/null | tr -d '"') current=$(ew get active-panel 2>/dev/null | tr -d '"')
popup_screen=$(ew get popup-screen 2>/dev/null | tr -d '"') popup_screen=$(ew get popup-screen 2>/dev/null | tr -d '"')
@@ -21,15 +26,16 @@ if ew active-windows 2>/dev/null | grep -qx 'popup: popup'; then open=1; else op
if ((open)) && [[ $current == "$panel" ]]; then if ((open)) && [[ $current == "$panel" ]]; then
ew update active-panel= ew update active-panel=
ew close popup ew close popup 2>/dev/null
elif ((open)) && [[ -n $screen && $popup_screen == "$screen" ]]; then elif ((open)) && [[ -n $screen && $popup_screen == "$screen" ]]; then
ew update active-panel="$panel" ew update active-panel="$panel"
else else
((open)) && ew close popup ((open)) && ew close popup 2>/dev/null
ew update active-panel="$panel" popup-screen="$screen" ew update active-panel="$panel" popup-screen="$screen"
if [[ -n $screen ]]; then if [[ -n $screen ]]; then
ew open popup --screen "$screen" ew open popup --screen "$screen" >/dev/null 2>&1
else else
ew open popup ew open popup >/dev/null 2>&1
fi fi
fi fi
exit 0
@@ -1,11 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Toggle the full-screen power menu on the focused monitor. # Toggle the full-screen power menu on the focused monitor. The open's exit
ew() { timeout 5 eww "$@"; } # status is ignored: it exceeds the client's 100 ms reply deadline.
ew close popup 2>/dev/null eww close popup 2>/dev/null
ew update active-panel= eww update active-panel= 2>/dev/null
screen=$(eww-bar focused) screen=$(eww-bar focused)
if [[ -n $screen ]]; then if [[ -n $screen ]]; then
ew open powermenu --toggle --screen "$screen" eww open powermenu --toggle --screen "$screen" >/dev/null 2>&1
else else
ew open powermenu --toggle eww open powermenu --toggle >/dev/null 2>&1
fi fi
exit 0
@@ -83,6 +83,29 @@
:visible {!bt.powered} :visible {!bt.powered}
:text "Disabled"))) :text "Disabled")))
; --- WireGuard ---
(defwidget wg-row [wg]
(box :orientation "v" :space-evenly false :class "wg-row"
(box :orientation "h" :space-evenly false :valign "center"
(label :class "wg-name ${wg.up ? 'wg-up' : 'wg-down'}" :halign "start" :hexpand true :text {wg.name})
(button
:class "net-toggle-btn ${wg.up ? 'net-toggle-on' : 'net-toggle-off'}"
:onclick "scripts/net/wg-toggle ${wg.name}"
:tooltip {wg.up ? "Disconnect" : "Connect"}
(label :text {wg.up ? "󰖂" : "󰦞"})))
(box :orientation "v" :space-evenly false :visible {wg.up}
(netinfo-row :label "IP" :value {wg.ip})
(netinfo-row :label "Peer" :value {wg.endpoint != "" ? "${wg.peers} · ${wg.endpoint}" : wg.peers})
(netinfo-row :label "Traffic" :value "󰇚 ${wg.rx} 󰕒 ${wg.tx}"))
(label :class "netinfo-dim" :halign "start" :visible {!wg.up} :text "Down")))
(defwidget wireguard-net-section []
(box :orientation "v" :space-evenly false :class "sys-section"
(section-header :title "WireGuard" :accent "wg-accent")
(for wg in {net.wireguard}
(wg-row :wg {wg}))))
; --- Root --- ; --- Root ---
(defwidget net-win [] (defwidget net-win []
@@ -94,4 +117,7 @@
(box :class "section-sep") (box :class "section-sep")
(wifi-net-section) (wifi-net-section)
(box :class "section-sep") (box :class "section-sep")
(box :visible {arraylength(net.wireguard) > 0} :space-evenly false :orientation "v"
(wireguard-net-section)
(box :class "section-sep"))
(bluetooth-net-section))) (bluetooth-net-section)))
@@ -22,7 +22,7 @@
(pm-btn :icon "󰗼" :label "Sign out" (pm-btn :icon "󰗼" :label "Sign out"
:onclick "hyprctl eval \"hl.dispatch(hl.dsp.exit())\"") :onclick "hyprctl eval \"hl.dispatch(hl.dsp.exit())\"")
(pm-btn :icon "󰅖" :label "Cancel" (pm-btn :icon "󰅖" :label "Cancel"
:onclick "eww close powermenu"))))))) :onclick "${EWW_CMD} --no-daemonize close powermenu")))))))
(defwindow powermenu (defwindow powermenu
:monitor 0 :monitor 0
@@ -20,6 +20,7 @@ let
"scripts/net/net" = mkScript "net" ./bar/scripts/net/net [ iproute2 wpa_supplicant ]; "scripts/net/net" = mkScript "net" ./bar/scripts/net/net [ iproute2 wpa_supplicant ];
"scripts/net/bt" = mkScript "bt" ./bar/scripts/net/bt [ jq glib ]; "scripts/net/bt" = mkScript "bt" ./bar/scripts/net/bt [ jq glib ];
"scripts/net/bt-toggle" = mkScript "bt-toggle" ./bar/scripts/net/bt-toggle [ bluez ]; "scripts/net/bt-toggle" = mkScript "bt-toggle" ./bar/scripts/net/bt-toggle [ bluez ];
"scripts/net/wg-toggle" = mkScript "wg-toggle" ./bar/scripts/net/wg-toggle [ ];
"scripts/sys/gpu" = mkScript "gpu" ./bar/scripts/sys/gpu [ custom.amdgpu_top jq ]; "scripts/sys/gpu" = mkScript "gpu" ./bar/scripts/sys/gpu [ custom.amdgpu_top jq ];
"scripts/sys/memory" = mkScript "memory" ./bar/scripts/sys/memory [ ]; "scripts/sys/memory" = mkScript "memory" ./bar/scripts/sys/memory [ ];
"scripts/sys/battery" = mkScript "battery" ./bar/scripts/sys/battery [ ]; "scripts/sys/battery" = mkScript "battery" ./bar/scripts/sys/battery [ ];
@@ -36,6 +36,20 @@ in
}; };
}; };
# Let the desktop (eww network panel, scripts/net/wg-toggle) bring the
# tunnel up and down. eww runs as a user service outside the login
# session, where polkit cannot ask for a password, so wheel users may
# start/stop wireguard-* units without one.
security.polkit.extraConfig = ''
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.systemd1.manage-units" &&
subject.isInGroup("wheel") &&
/^wireguard-/.test(action.lookup("unit") || "")) {
return polkit.Result.YES;
}
});
'';
systemd.services."wireguard-wg0-peer-vpn-helcel" = { systemd.services."wireguard-wg0-peer-vpn-helcel" = {
after = [ "network-online.target" "nss-lookup.target" ]; after = [ "network-online.target" "nss-lookup.target" ];
bindsTo = [ "network-online.target" ]; bindsTo = [ "network-online.target" ];
+2 -1
View File
@@ -4,6 +4,7 @@
, gnugrep , gnugrep
, gnused , gnused
, hyprland , hyprland
, iproute2
, jq , jq
, procps , procps
, systemd , systemd
@@ -15,6 +16,6 @@
# profiles and the user (`eww-bar status`, `eww-bar reset`). # profiles and the user (`eww-bar status`, `eww-bar reset`).
writeShellApplication { writeShellApplication {
name = "eww-bar"; 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; text = builtins.readFile ./eww-bar.sh;
} }
+98 -34
View File
@@ -1,9 +1,17 @@
# eww-bar — lifecycle manager for the eww daemon and the bar window. # 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 # The daemon is owned by the user unit eww.service. Two eww facts shape this
# through `ensure` first: a plain `eww open` against a dead or stale socket # script:
# 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"). # * 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 # SCREEN is a GDK monitor index (0), a connector name (DP-1) or
# desc:<prefix of the description shown by `hyprctl monitors`>. Descriptions # 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 check health check run by eww-watchdog.timer
clean ExecStartPre: kill stray daemons, wipe stale sockets clean ExecStartPre: kill stray daemons, wipe stale sockets
focused print the name of the focused Hyprland monitor 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 USAGE
} }
@@ -42,6 +50,25 @@ mkdir -p "$state_dir"
log() { printf 'eww-bar: %s\n' "$*" >&2; } 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 --- # ---------------------------------------------------------------- daemon ---
# nixpkgs wraps eww (wrapGAppsHook), so the running process is `.eww-wrapped`; # nixpkgs wraps eww (wrapGAppsHook), so the running process is `.eww-wrapped`;
@@ -60,18 +87,33 @@ unit_running() {
*) return 1 ;; *) return 1 ;;
esac 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. # pids listening on an eww socket path. After a rival rebinds the path, ss
ping_daemon() { timeout 3 eww ping >/dev/null 2>&1 9>&-; } # 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 # The socket file is gone while the daemon lives on: a rival that crashed at
# frozen UI: the reply is then empty instead of listing the windows. # startup unlinked it, and no client can reach the daemon any more.
# fd 9 is closed for every client call so a daemonizing client (which can socket_missing() { ! ls "$runtime_dir"/eww-server_* >/dev/null 2>&1; }
# only happen when the socket is dead) can never inherit our lock. daemon_age() { ps -o etimes= -p "$(main_pid)" 2>/dev/null | tr -d " " || echo 0; }
list_windows() { timeout 5 eww active-windows 2>/dev/null 9>&- || true; }
bar_open() { list_windows | grep -qx 'bar: bar'; }
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() { wait_for_daemon() {
local i local i
@@ -98,10 +140,13 @@ ensure() {
( (
flock -x 9 flock -x 9
healthy && exit 0 healthy && exit 0
# The unit is (re)starting — possibly running us from ExecStartPost — if rival_daemon; then
# so give the daemon time to bind before declaring it dead. log "a second eww daemon (pid $(rival_pids)) owns the socket"
if unit_running && wait_for_daemon; then exit 0; fi elif unit_running && wait_for_daemon && ! rival_daemon; then
log "daemon unreachable, restarting $unit" # 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 systemctl --user stop "$unit" >/dev/null 2>&1 || true
kill_stray_daemons kill_stray_daemons
start_unit start_unit
@@ -142,9 +187,9 @@ current_screen() { cat "$current_file" 2>/dev/null || true; }
close_popups() { close_popups() {
local w local w
for w in popup powermenu; do 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 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] # open_bar [--force] [SPEC]
@@ -161,17 +206,21 @@ open_bar() {
exit 0 exit 0
fi fi
# Popups may sit on an output that just vanished; `eww open` itself # 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 close_popups
if ! timeout 10 eww open bar --screen "$screen" >/dev/null 9>&-; then ewwc open bar --screen "$screen" >/dev/null 2>&1 || true
if [[ $screen == "$default_screen" ]]; then if ! wait_for_window bar 5; then
log "eww open failed, restarting $unit" 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 restart_unit
exit 1 exit 1
fi 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 fi
printf '%s\n' "$spec" >"$screen_file" printf '%s\n' "$spec" >"$screen_file"
printf '%s\n' "$screen" >"$current_file" printf '%s\n' "$screen" >"$current_file"
@@ -181,16 +230,20 @@ open_bar() {
close_bar() { close_bar() {
close_popups 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" rm -f "$current_file"
} }
reload() { reload() {
ensure || return 1 ensure || return 1
if ! timeout 15 eww reload >/dev/null 9>&-; then # Slow: the client always hits its deadline, so ignore the status and
log "reload failed, restarting $unit" # 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 restart_unit
return return 0
fi fi
open_bar --force open_bar --force
} }
@@ -234,18 +287,28 @@ check() {
return 0 return 0
fi fi
[[ $(unit_state) == active ]] || return 0 # still starting; ExecStartPost handles it [[ $(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 if ! ping_daemon; then
strike "daemon does not answer" strike "daemon does not answer"
return 0 return 0
fi fi
if bar_open; then if wait_for_window bar 3; then
rm -f "$fail_file" rm -f "$fail_file"
return 0 return 0
fi fi
# Either nothing is open or the main loop is frozen (empty reply). Try to # 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 the bar: a frozen daemon still won't list it afterwards.
open_bar || true open_bar || true
if bar_open; then if wait_for_window bar 3; then
rm -f "$fail_file" rm -f "$fail_file"
log "bar was missing, reopened" log "bar was missing, reopened"
else else
@@ -255,7 +318,8 @@ check() {
status() { status() {
systemctl --user --no-pager status "$unit" 2>&1 | head -n 12 || true 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/^/ /' list_windows | sed 's/^/ /'
printf 'screen: %s (requested: %s)\n' "$(current_screen)" "$(saved_screen)" printf 'screen: %s (requested: %s)\n' "$(current_screen)" "$(saved_screen)"
printf 'strikes: %s\n' "$(cat "$fail_file" 2>/dev/null || echo 0)" printf 'strikes: %s\n' "$(cat "$fail_file" 2>/dev/null || echo 0)"