Better eww

This commit is contained in:
soraefir
2026-07-19 23:11:43 +02:00
parent 887634421c
commit 58492cae22
6 changed files with 78 additions and 8 deletions
@@ -6,13 +6,15 @@ PANEL="$1"
exec 9>"/tmp/eww_panel_toggle.lock" exec 9>"/tmp/eww_panel_toggle.lock"
flock -n 9 || exit 0 flock -n 9 || exit 0
CURRENT=$(eww get active-panel 2>/dev/null | tr -d '"') # Every eww call must run with fd 9 closed (9>&-): `eww open` daemonizes and
# would inherit the fd, holding the flock forever and wedging all later toggles.
CURRENT=$(eww get active-panel 2>/dev/null 9>&- | tr -d '"')
if [ "$CURRENT" = "$PANEL" ]; then if [ "$CURRENT" = "$PANEL" ]; then
eww update active-panel="" eww update active-panel="" 9>&-
eww close popup 2>/dev/null eww close popup 2>/dev/null 9>&-
else else
eww update active-panel="$PANEL" eww update active-panel="$PANEL" 9>&-
eww close popup 2>/dev/null eww close popup 2>/dev/null 9>&-
eww-open-on-current-screen popup eww-open-on-current-screen popup 9>&-
fi fi
+39 -1
View File
@@ -1,9 +1,13 @@
{ lib, config, pkgs, ... }: { lib, config, pkgs, ... }:
let let
ensureDaemon = pkgs.custom.eww-ensure-daemon;
openOnCurrentScreen = pkgs.writeShellScriptBin "eww-open-on-current-screen" '' openOnCurrentScreen = pkgs.writeShellScriptBin "eww-open-on-current-screen" ''
window="$1" window="$1"
shift shift
${ensureDaemon}/bin/eww-ensure-daemon || exit 1
screen="$(hyprctl monitors -j | ${lib.getExe pkgs.jq} -r '.[] | select(.focused == true) | .name' | head -n1)" screen="$(hyprctl monitors -j | ${lib.getExe pkgs.jq} -r '.[] | select(.focused == true) | .name' | head -n1)"
if [ -n "$screen" ]; then if [ -n "$screen" ]; then
@@ -13,6 +17,21 @@ let
exec ${lib.getExe pkgs.eww} open "$window" "$@" exec ${lib.getExe pkgs.eww} open "$window" "$@"
''; '';
# Escape hatch: tears down every eww process (including orphaned daemons that
# own unreachable surfaces), clears the stale socket, and brings back a single
# daemon with the bar. `pkill -x` matches the process name exactly so it can
# never match a wrapper script or a shell that merely mentions eww.
ewwReset = pkgs.writeShellScriptBin "eww-reset" ''
${pkgs.systemd}/bin/systemctl --user stop eww.service >/dev/null 2>&1 || true
${pkgs.procps}/bin/pkill -x -u "$(id -u)" eww >/dev/null 2>&1 || true
sleep 1
${pkgs.procps}/bin/pkill -9 -x -u "$(id -u)" eww >/dev/null 2>&1 || true
rm -f "''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"/eww-server_*
${ensureDaemon}/bin/eww-ensure-daemon || exit 1
exec ${lib.getExe pkgs.eww} open bar --screen 0
'';
# Wraps a static script file with a bash launcher that prepends Nix store # Wraps a static script file with a bash launcher that prepends Nix store
# bin dirs to PATH — keeps the source files unchanged. # bin dirs to PATH — keeps the source files unchanged.
mkScript = name: src: inputs: pkgs.writeShellScriptBin name '' mkScript = name: src: inputs: pkgs.writeShellScriptBin name ''
@@ -43,7 +62,26 @@ let
in { in {
config = lib.mkIf (config.usercfg.wm == "Wayland") { config = lib.mkIf (config.usercfg.wm == "Wayland") {
home.packages = [ pkgs.eww openOnCurrentScreen ]; home.packages = [ pkgs.eww openOnCurrentScreen ensureDaemon ewwReset ];
# One explicitly-managed daemon, so no client ever races to spawn a rival.
# ExecStartPre clears a socket left behind by an unclean exit, which would
# otherwise make the fresh daemon look reachable to clients before it binds.
systemd.user.services.eww = {
Unit = {
Description = "eww daemon";
PartOf = [ "graphical-session.target" ];
After = [ "graphical-session.target" ];
};
Service = {
Type = "simple";
ExecStartPre = "${pkgs.bash}/bin/bash -c 'rm -f \"\${XDG_RUNTIME_DIR:-/run/user/$(id -u)}\"/eww-server_*'";
ExecStart = "${lib.getExe pkgs.eww} daemon --no-daemonize";
Restart = "on-failure";
RestartSec = 1;
};
Install.WantedBy = [ "graphical-session.target" ];
};
xdg.configFile = lib.mkMerge [ xdg.configFile = lib.mkMerge [
{ {
@@ -1,7 +1,12 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
# Close the bar if it's already open (wrong screen), then open on the target screen. # Close the bar if it's already open (wrong screen), then open on the target screen.
# Must go through eww-ensure-daemon: this runs on every monitor hotplug, and a
# bare `eww open` against a busy/dead socket spawns a second daemon, leaving the
# first one's bar and popups on screen with no way to close them.
moveOrOpenBar = screen: "${pkgs.writeShellScript "kanshi-eww-bar-${toString screen}" '' moveOrOpenBar = screen: "${pkgs.writeShellScript "kanshi-eww-bar-${toString screen}" ''
${pkgs.custom.eww-ensure-daemon}/bin/eww-ensure-daemon || exit 1
if ${pkgs.eww}/bin/eww active-windows 2>/dev/null | grep -qx "bar"; then if ${pkgs.eww}/bin/eww active-windows 2>/dev/null | grep -qx "bar"; then
${pkgs.eww}/bin/eww close bar ${pkgs.eww}/bin/eww close bar
fi fi
+5 -1
View File
@@ -24,7 +24,11 @@
startupScript = pkgs.writeShellScriptBin "hyprland-start" '' startupScript = pkgs.writeShellScriptBin "hyprland-start" ''
${pkgs.eww}/bin/eww open bar & # Wait for the managed daemon rather than letting `eww open` spawn its own:
# at login the socket may not be bound yet, and a self-spawned client daemon
# would orphan the service's one, leaving unclosable windows behind.
(${pkgs.custom.eww-ensure-daemon}/bin/eww-ensure-daemon \
&& ${pkgs.eww}/bin/eww open bar --screen 0) &
${pkgs.awww}/bin/awww-daemon & ${pkgs.awww}/bin/awww-daemon &
${pkgs.awww}/bin/awww restore & ${pkgs.awww}/bin/awww restore &
+2
View File
@@ -5,4 +5,6 @@
repalette = pkgs.callPackage ./repalette { }; repalette = pkgs.callPackage ./repalette { };
vosk = pkgs.callPackage ./vosk { }; vosk = pkgs.callPackage ./vosk { };
eww-ensure-daemon = pkgs.callPackage ./eww-ensure-daemon { };
} }
+19
View File
@@ -0,0 +1,19 @@
{ lib, writeShellScriptBin, eww, systemd }:
# `eww open` silently becomes a NEW daemon when it can't reach the socket,
# rebinding the same socket path and orphaning the previous daemon. The old
# daemon keeps rendering its layer-shell surfaces but no longer answers any
# `eww close` — that is the "zombie panel I can't close" / "bar opened twice".
# Every open path must go through here first, so the socket is always live
# before a client touches it and no client ever self-daemonizes.
writeShellScriptBin "eww-ensure-daemon" ''
${lib.getExe eww} ping >/dev/null 2>&1 && exit 0
${systemd}/bin/systemctl --user start eww.service >/dev/null 2>&1 || true
for _ in $(seq 1 100); do
${lib.getExe eww} ping >/dev/null 2>&1 && exit 0
sleep 0.1
done
exit 1
''