improve eww
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
{ writeShellApplication
|
||||
, coreutils
|
||||
, eww
|
||||
, gnugrep
|
||||
, gnused
|
||||
, hyprland
|
||||
, jq
|
||||
, procps
|
||||
, systemd
|
||||
, util-linux
|
||||
}:
|
||||
|
||||
# Lifecycle manager for the eww daemon and the bar window. Used by
|
||||
# eww.service (ExecStartPre/Post, ExecReload), eww-watchdog.timer, the kanshi
|
||||
# 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 ];
|
||||
text = builtins.readFile ./eww-bar.sh;
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
# 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").
|
||||
#
|
||||
# SCREEN is a GDK monitor index (0), a connector name (DP-1) or
|
||||
# desc:<prefix of the description shown by `hyprctl monitors`>. Descriptions
|
||||
# survive re-plugging and profile changes; indices do not.
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
usage: eww-bar COMMAND [ARGS]
|
||||
|
||||
ensure make sure a healthy, service-owned daemon is running
|
||||
open [SCREEN] open the bar (no-op if already open there), remember SCREEN
|
||||
place [SCREEN] force re-open of the bar on SCREEN (used by kanshi)
|
||||
close close the bar and the popups
|
||||
reload reload the eww config and re-place the bar
|
||||
restart restart eww.service (the bar comes back with it)
|
||||
reset kill every eww process, wipe the sockets, start fresh
|
||||
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
|
||||
USAGE
|
||||
}
|
||||
|
||||
uid=$(id -u)
|
||||
runtime_dir=${XDG_RUNTIME_DIR:-/run/user/$uid}
|
||||
state_dir=$runtime_dir/eww-bar
|
||||
lock_file=$state_dir/lock
|
||||
screen_file=$state_dir/screen # requested screen spec, re-resolved on restart
|
||||
current_file=$state_dir/current # resolved screen the bar is currently on
|
||||
fail_file=$state_dir/strikes
|
||||
unit=eww.service
|
||||
default_screen=0
|
||||
max_strikes=2
|
||||
|
||||
mkdir -p "$state_dir"
|
||||
|
||||
log() { printf 'eww-bar: %s\n' "$*" >&2; }
|
||||
|
||||
# ---------------------------------------------------------------- daemon ---
|
||||
|
||||
# nixpkgs wraps eww (wrapGAppsHook), so the running process is `.eww-wrapped`;
|
||||
# `pkill -x eww` never matched it. Exact match, this user only.
|
||||
kill_stray_daemons() {
|
||||
pkill -TERM -x -u "$uid" '(\.eww-wrapped|eww)' 2>/dev/null || true
|
||||
sleep 0.3
|
||||
pkill -KILL -x -u "$uid" '(\.eww-wrapped|eww)' 2>/dev/null || true
|
||||
rm -f "$runtime_dir"/eww-server_*
|
||||
}
|
||||
|
||||
unit_state() { systemctl --user show -p ActiveState --value "$unit" 2>/dev/null || echo unknown; }
|
||||
unit_running() {
|
||||
case $(unit_state) in
|
||||
active | activating | reloading) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# `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>&-; }
|
||||
|
||||
# `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'; }
|
||||
|
||||
healthy() { unit_running && ping_daemon; }
|
||||
|
||||
wait_for_daemon() {
|
||||
local i
|
||||
for ((i = 0; i < 100; i++)); do
|
||||
ping_daemon && return 0
|
||||
sleep 0.1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
start_unit() {
|
||||
systemctl --user reset-failed "$unit" >/dev/null 2>&1 || true
|
||||
systemctl --user start "$unit"
|
||||
}
|
||||
|
||||
restart_unit() {
|
||||
log "restarting $unit"
|
||||
systemctl --user reset-failed "$unit" >/dev/null 2>&1 || true
|
||||
systemctl --user restart "$unit"
|
||||
}
|
||||
|
||||
ensure() {
|
||||
healthy && return 0
|
||||
(
|
||||
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"
|
||||
systemctl --user stop "$unit" >/dev/null 2>&1 || true
|
||||
kill_stray_daemons
|
||||
start_unit
|
||||
wait_for_daemon
|
||||
) 9>"$lock_file"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------- screens ---
|
||||
|
||||
focused_monitor() {
|
||||
hyprctl -j monitors 2>/dev/null | jq -r 'first(.[] | select(.focused) | .name) // empty'
|
||||
}
|
||||
|
||||
# desc:<prefix> → connector name via hyprctl; anything else is passed to eww.
|
||||
resolve_screen() {
|
||||
local spec=$1 name
|
||||
case $spec in
|
||||
desc:*)
|
||||
name=$(hyprctl -j monitors 2>/dev/null \
|
||||
| jq -r --arg p "${spec#desc:}" '[.[] | select(.description | startswith($p))][0].name // empty')
|
||||
if [[ -n $name ]]; then
|
||||
printf '%s\n' "$name"
|
||||
else
|
||||
log "no monitor matches '$spec', using screen $default_screen"
|
||||
printf '%s\n' "$default_screen"
|
||||
fi
|
||||
;;
|
||||
'') printf '%s\n' "$default_screen" ;;
|
||||
*) printf '%s\n' "$spec" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
saved_screen() { cat "$screen_file" 2>/dev/null || true; }
|
||||
current_screen() { cat "$current_file" 2>/dev/null || true; }
|
||||
|
||||
# ------------------------------------------------------------------- bar ---
|
||||
|
||||
close_popups() {
|
||||
local w
|
||||
for w in popup powermenu; do
|
||||
timeout 3 eww close "$w" >/dev/null 2>&1 9>&- || true
|
||||
done
|
||||
timeout 3 eww update active-panel= >/dev/null 2>&1 9>&- || true
|
||||
}
|
||||
|
||||
# open_bar [--force] [SPEC]
|
||||
open_bar() {
|
||||
local force=0 spec screen
|
||||
if [[ ${1:-} == --force ]]; then force=1; shift; fi
|
||||
spec=${1:-$(saved_screen)}
|
||||
spec=${spec:-$default_screen}
|
||||
ensure || { log "no daemon, cannot open the bar"; return 1; }
|
||||
screen=$(resolve_screen "$spec")
|
||||
(
|
||||
flock -x 9
|
||||
if ((force == 0)) && [[ $(current_screen) == "$screen" ]] && bar_open; then
|
||||
exit 0
|
||||
fi
|
||||
# Popups may sit on an output that just vanished; `eww open` itself
|
||||
# replaces an already-open bar.
|
||||
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"
|
||||
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"
|
||||
log "bar opened on screen $screen"
|
||||
) 9>"$lock_file"
|
||||
}
|
||||
|
||||
close_bar() {
|
||||
close_popups
|
||||
timeout 3 eww close bar >/dev/null 2>&1 9>&- || true
|
||||
rm -f "$current_file"
|
||||
}
|
||||
|
||||
reload() {
|
||||
ensure || return 1
|
||||
if ! timeout 15 eww reload >/dev/null 9>&-; then
|
||||
log "reload failed, restarting $unit"
|
||||
restart_unit
|
||||
return
|
||||
fi
|
||||
open_bar --force
|
||||
}
|
||||
|
||||
reset() {
|
||||
log "full reset"
|
||||
systemctl --user stop "$unit" >/dev/null 2>&1 || true
|
||||
kill_stray_daemons
|
||||
rm -f "$current_file" "$fail_file"
|
||||
start_unit # ExecStartPost re-opens the bar
|
||||
}
|
||||
|
||||
# ExecStartPre
|
||||
clean() {
|
||||
kill_stray_daemons
|
||||
rm -f "$current_file" "$fail_file"
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------- watchdog ---
|
||||
|
||||
# One bad check is tolerated (a busy daemon can miss eww's 100 ms IPC reply
|
||||
# deadline); the second consecutive one restarts the unit.
|
||||
strike() {
|
||||
local n
|
||||
n=$(($(cat "$fail_file" 2>/dev/null || echo 0) + 1))
|
||||
if ((n >= max_strikes)); then
|
||||
log "$1 ($n/$max_strikes) — restarting $unit"
|
||||
rm -f "$fail_file"
|
||||
restart_unit
|
||||
else
|
||||
log "$1 ($n/$max_strikes)"
|
||||
printf '%s\n' "$n" >"$fail_file"
|
||||
fi
|
||||
}
|
||||
|
||||
check() {
|
||||
systemctl --user is-active --quiet graphical-session.target || return 0
|
||||
if ! unit_running; then
|
||||
log "$unit is not running, starting it"
|
||||
start_unit || true
|
||||
return 0
|
||||
fi
|
||||
[[ $(unit_state) == active ]] || return 0 # still starting; ExecStartPost handles it
|
||||
if ! ping_daemon; then
|
||||
strike "daemon does not answer"
|
||||
return 0
|
||||
fi
|
||||
if bar_open; 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
|
||||
rm -f "$fail_file"
|
||||
log "bar was missing, reopened"
|
||||
else
|
||||
strike "UI unresponsive"
|
||||
fi
|
||||
}
|
||||
|
||||
status() {
|
||||
systemctl --user --no-pager status "$unit" 2>&1 | head -n 12 || true
|
||||
printf '\nwindows:\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)"
|
||||
}
|
||||
|
||||
case ${1:-} in
|
||||
ensure) ensure ;;
|
||||
open) shift; open_bar "$@" ;;
|
||||
place) shift; open_bar --force "$@" ;;
|
||||
close) close_bar ;;
|
||||
reload) reload ;;
|
||||
restart) restart_unit ;;
|
||||
reset) reset ;;
|
||||
check) check ;;
|
||||
clean) clean ;;
|
||||
focused) focused_monitor ;;
|
||||
status) status ;;
|
||||
-h | --help | help) usage ;;
|
||||
*) usage >&2; exit 64 ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user