{ lib, config, pkgs, ... }: let ewwBar = "${pkgs.custom.eww-bar}/bin/eww-bar"; # 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 (baseInputs ++ inputs)}:$PATH" ${builtins.readFile ./bar/scripts/_lib.sh} ${builtins.readFile src} ''; 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/net/wg-toggle" = mkScript "wg-toggle" ./bar/scripts/net/wg-toggle [ ]; "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 pkgs.custom.eww-bar ]; # 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 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 = "${ewwBar} clean"; ExecStart = "${lib.getExe pkgs.eww} daemon --no-daemonize"; ExecStartPost = "${ewwBar} open"; ExecReload = "${ewwBar} reload"; Restart = "always"; 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" ]; }; xdg.configFile = lib.mkMerge [ { "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") && !(lib.hasPrefix "scripts/" relPath); 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) ]; }; }