{ 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 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. mkScript = name: src: inputs: pkgs.writeShellScriptBin name '' export PATH="${lib.makeBinPath inputs}:$PATH" ${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 ]; }; in { config = lib.mkIf (config.usercfg.wm == "Wayland") { 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 [ { "eww" = { source = lib.cleanSourceWith { 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)); src = lib.cleanSource ./bar/.; }; recursive = true; }; "eww/css/_colors.scss".text = '' $base00: #${config.colorScheme.palette.base00}; $base01: #${config.colorScheme.palette.base01}; $base02: #${config.colorScheme.palette.base02}; $base03: #${config.colorScheme.palette.base03}; $base04: #${config.colorScheme.palette.base04}; $base05: #${config.colorScheme.palette.base05}; $base06: #${config.colorScheme.palette.base06}; $base07: #${config.colorScheme.palette.base07}; $base08: #${config.colorScheme.palette.base08}; $base09: #${config.colorScheme.palette.base09}; $base0A: #${config.colorScheme.palette.base0A}; $base0B: #${config.colorScheme.palette.base0B}; $base0C: #${config.colorScheme.palette.base0C}; $base0D: #${config.colorScheme.palette.base0D}; $base0E: #${config.colorScheme.palette.base0E}; $base0F: #${config.colorScheme.palette.base0F}; $fg: $base07; $bg0: $base00; $bg1: $base01; $border-color: $base03; $border-color-focus: $base04; $border-radius: ${config.colorScheme.palette.border-radius}px; $border-width: ${config.colorScheme.palette.border-width}px; $gaps-screen: ${config.colorScheme.palette.gaps-screen}px; $gaps-window: ${config.colorScheme.palette.gaps-window}px; $panel-font-size: 10pt; $popup-scale: 1.25; ''; } (lib.mapAttrs' (rel: drv: { name = "eww/${rel}"; value.source = "${drv}/bin/${drv.name}"; }) scripts) ]; }; }