From 58492cae22a3247c74ab2e6a12a396e702ac4923 Mon Sep 17 00:00:00 2001 From: soraefir Date: Sun, 19 Jul 2026 23:10:48 +0200 Subject: [PATCH] Better eww --- .../wayland/apps/eww/bar/scripts/panel-toggle | 14 ++++--- modules/home/wayland/apps/eww/default.nix | 40 ++++++++++++++++++- modules/home/wayland/apps/kanshi/default.nix | 5 +++ modules/home/wayland/hyprland/config.nix | 6 ++- packages/default.nix | 2 + packages/eww-ensure-daemon/default.nix | 19 +++++++++ 6 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 packages/eww-ensure-daemon/default.nix diff --git a/modules/home/wayland/apps/eww/bar/scripts/panel-toggle b/modules/home/wayland/apps/eww/bar/scripts/panel-toggle index e124fd5..b53cf68 100755 --- a/modules/home/wayland/apps/eww/bar/scripts/panel-toggle +++ b/modules/home/wayland/apps/eww/bar/scripts/panel-toggle @@ -6,13 +6,15 @@ PANEL="$1" exec 9>"/tmp/eww_panel_toggle.lock" 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 - eww update active-panel="" - eww close popup 2>/dev/null + eww update active-panel="" 9>&- + eww close popup 2>/dev/null 9>&- else - eww update active-panel="$PANEL" - eww close popup 2>/dev/null - eww-open-on-current-screen popup + eww update active-panel="$PANEL" 9>&- + eww close popup 2>/dev/null 9>&- + eww-open-on-current-screen popup 9>&- fi diff --git a/modules/home/wayland/apps/eww/default.nix b/modules/home/wayland/apps/eww/default.nix index f197e17..815ee87 100755 --- a/modules/home/wayland/apps/eww/default.nix +++ b/modules/home/wayland/apps/eww/default.nix @@ -1,9 +1,13 @@ { lib, config, pkgs, ... }: let + ensureDaemon = pkgs.custom.eww-ensure-daemon; + openOnCurrentScreen = pkgs.writeShellScriptBin "eww-open-on-current-screen" '' window="$1" shift + ${ensureDaemon}/bin/eww-ensure-daemon || exit 1 + screen="$(hyprctl monitors -j | ${lib.getExe pkgs.jq} -r '.[] | select(.focused == true) | .name' | head -n1)" if [ -n "$screen" ]; then @@ -13,6 +17,21 @@ let 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 # bin dirs to PATH — keeps the source files unchanged. mkScript = name: src: inputs: pkgs.writeShellScriptBin name '' @@ -43,7 +62,26 @@ let in { 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 [ { diff --git a/modules/home/wayland/apps/kanshi/default.nix b/modules/home/wayland/apps/kanshi/default.nix index 4574abb..19a853a 100644 --- a/modules/home/wayland/apps/kanshi/default.nix +++ b/modules/home/wayland/apps/kanshi/default.nix @@ -1,7 +1,12 @@ { config, lib, pkgs, ... }: let # 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}" '' + ${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 ${pkgs.eww}/bin/eww close bar fi diff --git a/modules/home/wayland/hyprland/config.nix b/modules/home/wayland/hyprland/config.nix index 00fd1a9..e95b395 100644 --- a/modules/home/wayland/hyprland/config.nix +++ b/modules/home/wayland/hyprland/config.nix @@ -24,7 +24,11 @@ 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 restore & diff --git a/packages/default.nix b/packages/default.nix index dc805f7..95c30e2 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -5,4 +5,6 @@ repalette = pkgs.callPackage ./repalette { }; vosk = pkgs.callPackage ./vosk { }; + + eww-ensure-daemon = pkgs.callPackage ./eww-ensure-daemon { }; } diff --git a/packages/eww-ensure-daemon/default.nix b/packages/eww-ensure-daemon/default.nix new file mode 100644 index 0000000..ee9af90 --- /dev/null +++ b/packages/eww-ensure-daemon/default.nix @@ -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 +''