vpn eww and eww crash
This commit is contained in:
@@ -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" ];
|
||||||
|
|||||||
@@ -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
@@ -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)"
|
||||||
|
|||||||
Reference in New Issue
Block a user