improve eww

This commit is contained in:
2026-09-10 19:54:50 +02:00
parent e551d36e1c
commit 7bb27f9373
46 changed files with 1296 additions and 1092 deletions
+82 -71
View File
@@ -1,94 +1,104 @@
{ lib, config, pkgs, ... }:
let
ensureDaemon = pkgs.custom.eww-ensure-daemon;
ewwBar = "${pkgs.custom.eww-bar}/bin/eww-bar";
ewwCleanStart = pkgs.writeShellScript "eww-clean-start" ''
uid="$(${pkgs.coreutils}/bin/id -u)"
runtime_dir="''${XDG_RUNTIME_DIR:-/run/user/$uid}"
${pkgs.procps}/bin/pkill -TERM -x -u "$uid" eww >/dev/null 2>&1 || true
${pkgs.coreutils}/bin/sleep 0.2
${pkgs.procps}/bin/pkill -KILL -x -u "$uid" eww >/dev/null 2>&1 || true
${pkgs.coreutils}/bin/rm -f "$runtime_dir"/eww-server_*
'';
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
exec ${lib.getExe pkgs.eww} open "$window" --screen "$screen" "$@"
fi
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.
# Every script ships as a bash wrapper that pins PATH to its dependencies
# and inlines the shared helpers, so the sources under bar/scripts stay
# plain shell files.
baseInputs = with pkgs; [ coreutils findutils gawk gnugrep gnused systemd util-linux ];
mkScript = name: src: inputs: pkgs.writeShellScriptBin name ''
export PATH="${lib.makeBinPath inputs}:$PATH"
export PATH="${lib.makeBinPath (baseInputs ++ inputs)}:$PATH"
${builtins.readFile ./bar/scripts/_lib.sh}
${builtins.readFile src}
'';
scripts = {
"scripts/brightness" = mkScript "brightness" ./bar/scripts/brightness [ pkgs.brightnessctl ];
"scripts/nightlight" = mkScript "nightlight" ./bar/scripts/nightlight [ pkgs.wlsunset ];
"scripts/read-mode" = mkScript "read-mode" ./bar/scripts/read-mode [ pkgs.hyprland ];
"scripts/airplane-mode" = mkScript "airplane-mode" ./bar/scripts/airplane-mode [ pkgs.util-linux ];
"scripts/do-not-disturb" = mkScript "do-not-disturb" ./bar/scripts/do-not-disturb [ pkgs.dunst ];
"scripts/panel-toggle" = mkScript "panel-toggle" ./bar/scripts/panel-toggle [ pkgs.eww pkgs.util-linux openOnCurrentScreen ];
"scripts/powermenu-toggle" = mkScript "powermenu-toggle" ./bar/scripts/powermenu-toggle [ pkgs.eww openOnCurrentScreen ];
"scripts/power-save" = mkScript "power-save" ./bar/scripts/power-save [ pkgs.eww ];
"scripts/radio" = mkScript "radio" ./bar/scripts/radio [ pkgs.eww pkgs.jq pkgs.curl pkgs.mpv pkgs.playerctl ];
"scripts/color-pick" = mkScript "color-pick" ./bar/scripts/color-pick [ pkgs.eww pkgs.hyprpicker ];
"scripts/media" = mkScript "media" ./bar/scripts/media [ pkgs.playerctl ];
"scripts/lock" = mkScript "lock" ./bar/scripts/lock [ pkgs.eww pkgs.hyprlock ];
"scripts/screenshot" = mkScript "screenshot" ./bar/scripts/screenshot [ pkgs.eww pkgs.hyprshot pkgs.satty pkgs.wl-clipboard ];
"scripts/sys/gpu" = mkScript "gpu" ./bar/scripts/sys/gpu [ pkgs.custom.amdgpu_top pkgs.jq ];
"scripts/sys/memory" = mkScript "memory" ./bar/scripts/sys/memory [ pkgs.ripgrep ];
"scripts/volume" = mkScript "volume" ./bar/scripts/volume [ pkgs.ripgrep ];
"scripts/weather" = mkScript "weather" ./bar/scripts/weather [ pkgs.jq pkgs.curl ];
"scripts/workspaces" = mkScript "workspaces" ./bar/scripts/workspaces [ pkgs.jaq pkgs.socat pkgs.ripgrep ];
scripts = with pkgs; {
# bar
"scripts/workspaces" = mkScript "workspaces" ./bar/scripts/workspaces [ hyprland jaq socat ripgrep ];
"scripts/panel-toggle" = mkScript "panel-toggle" ./bar/scripts/panel-toggle [ eww custom.eww-bar ];
"scripts/powermenu-toggle" = mkScript "powermenu-toggle" ./bar/scripts/powermenu-toggle [ eww custom.eww-bar ];
"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-toggle" = mkScript "bt-toggle" ./bar/scripts/net/bt-toggle [ bluez ];
"scripts/sys/gpu" = mkScript "gpu" ./bar/scripts/sys/gpu [ custom.amdgpu_top jq ];
"scripts/sys/memory" = mkScript "memory" ./bar/scripts/sys/memory [ ];
"scripts/sys/battery" = mkScript "battery" ./bar/scripts/sys/battery [ ];
"scripts/sys/cputemp" = mkScript "cputemp" ./bar/scripts/sys/cputemp [ ];
# popup panels
"scripts/volume" = mkScript "volume" ./bar/scripts/volume [ wireplumber pulseaudio ];
"scripts/brightness" = mkScript "brightness" ./bar/scripts/brightness [ brightnessctl ];
"scripts/weather" = mkScript "weather" ./bar/scripts/weather [ jq curl ];
"scripts/radio" = mkScript "radio" ./bar/scripts/radio [ eww jq curl mpv playerctl wireplumber ];
"scripts/media" = mkScript "media" ./bar/scripts/media [ playerctl ];
# quick actions (long-running ones are detached via systemd-run)
"scripts/nightlight" = mkScript "nightlight" ./bar/scripts/nightlight [ eww wlsunset procps ];
"scripts/read-mode" = mkScript "read-mode" ./bar/scripts/read-mode [ eww hyprland ];
"scripts/airplane-mode" = mkScript "airplane-mode" ./bar/scripts/airplane-mode [ eww ];
"scripts/do-not-disturb" = mkScript "do-not-disturb" ./bar/scripts/do-not-disturb [ eww dunst ];
"scripts/power-save" = mkScript "power-save" ./bar/scripts/power-save [ eww power-profiles-daemon ];
"scripts/lock" = mkScript "lock" ./bar/scripts/lock [ eww hyprlock ];
"scripts/screenshot" = mkScript "screenshot" ./bar/scripts/screenshot [ eww hyprshot satty wl-clipboard ];
"scripts/color-pick" = mkScript "color-pick" ./bar/scripts/color-pick [ eww hyprpicker ];
"scripts/wallpaper" = mkScript "wallpaper" ./bar/scripts/wallpaper [ eww ];
};
in {
config = lib.mkIf (config.usercfg.wm == "Wayland") {
home.packages = [ pkgs.eww openOnCurrentScreen ensureDaemon ewwReset ];
home.packages = [ pkgs.eww pkgs.custom.eww-bar ];
# 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.
# The daemon and the bar are one unit: it starts and stops with the
# graphical session, opens the bar once the daemon answers, and is
# restarted on crash. `eww-bar` (packages/eww-bar) does the heavy
# lifting; kanshi calls `eww-bar place` to move the bar between outputs.
systemd.user.services.eww = {
Unit = {
Description = "eww daemon";
Description = "eww daemon and bar";
Documentation = "https://elkowar.github.io/eww/";
PartOf = [ "graphical-session.target" ];
After = [ "graphical-session.target" ];
ConditionEnvironment = "WAYLAND_DISPLAY";
# Cap crash loops (e.g. a broken config) without giving up for good:
# the watchdog resets the counter and tries again later.
StartLimitIntervalSec = 60;
StartLimitBurst = 5;
};
Service = {
Type = "simple";
ExecStartPre = "${ewwCleanStart}";
ExecStartPre = "${ewwBar} clean";
ExecStart = "${lib.getExe pkgs.eww} daemon --no-daemonize";
ExecStartPost = "${ewwBar} open";
ExecReload = "${ewwBar} reload";
Restart = "always";
RestartSec = 1;
RestartSec = 2;
TimeoutStartSec = 30;
TimeoutStopSec = 5;
};
Install.WantedBy = [ "graphical-session.target" ];
};
# Liveness check: `eww ping` only proves the socket is up, so the check
# also lists the windows through the GTK main loop. Two consecutive
# failures (≈40 s) restart the daemon; a missing bar is simply reopened.
systemd.user.services.eww-watchdog = {
Unit = {
Description = "eww health check";
After = [ "eww.service" ];
};
Service = {
Type = "oneshot";
ExecStart = "${ewwBar} check";
};
};
systemd.user.timers.eww-watchdog = {
Unit = {
Description = "Periodic eww health check";
PartOf = [ "graphical-session.target" ];
After = [ "graphical-session.target" ];
};
Timer = {
OnActiveSec = "30s";
OnUnitActiveSec = "20s";
AccuracySec = "5s";
};
Install.WantedBy = [ "graphical-session.target" ];
};
@@ -97,13 +107,14 @@ in {
{
"eww" = {
source = lib.cleanSourceWith {
# scripts/ is shipped through the wrappers below, never verbatim
filter = name: _type:
let
baseName = baseNameOf (toString name);
relPath = lib.removePrefix (toString ./bar + "/") (toString name);
in !(lib.hasSuffix ".nix" baseName)
&& (baseName != "_colors.scss")
&& !(builtins.elem relPath (builtins.attrNames scripts));
&& !(lib.hasPrefix "scripts/" relPath);
src = lib.cleanSource ./bar/.;
};
recursive = true;