{ config, lib, pkgs, ... }: # Waybar: a top status bar reading system state at a glance. let vpnClient = config.modules.network.vpn-client; # A Nerd Font Private-Use-Area codepoint as a literal character, decoded # through JSON so the glyph survives as bytes rather than an editor paste. g = code: builtins.fromJSON ''"\u${code}"''; sleepOffIcon = builtins.fromJSON ''"\udb81\udcb3"''; # Renders and toggles mako's do-not-disturb mode. # With no mako daemon answering, the status reads "notifications on", keeping # the widget inert rather than broken. dndStatus = pkgs.writeShellScript "waybar-dnd-status" '' if ${lib.getExe' pkgs.mako "makoctl"} mode 2>/dev/null | ${lib.getExe pkgs.gnugrep} -qx dnd; then printf '{"text":"${g "f1f6"}","tooltip":"Do not disturb: on","class":"dnd"}\n' else printf '{"text":"${g "f0f3"}","tooltip":"Notifications on","class":"active"}\n' fi ''; dndToggle = pkgs.writeShellScript "waybar-dnd-toggle" '' ${lib.getExe' pkgs.mako "makoctl"} mode -t dnd >/dev/null 2>&1 ${lib.getExe' pkgs.procps "pkill"} -RTMIN+8 waybar ''; # Lit while a wf-recorder capture is running. # Empty text collapses the widget when idle, so it shows nothing until then. recStatus = pkgs.writeShellScript "waybar-recording-status" '' if ${lib.getExe' pkgs.procps "pgrep"} -x wf-recorder >/dev/null; then printf '{"text":"${g "f03d"}","tooltip":"Recording","class":"recording"}\n' else printf '{"text":"","class":"idle"}\n' fi ''; idleLockStatus = pkgs.writeShellScript "waybar-idle-lock-status" '' if ${lib.getExe' pkgs.systemd "systemctl"} --user is-active --quiet lockscreen-idle-inhibit.service; then printf '{"text":"${sleepOffIcon}","tooltip":"Idle locking inhibited","class":"inhibited"}\n' else printf '{"text":"","class":"idle"}\n' fi ''; vpnStatus = pkgs.writeShellScript "waybar-vpn-status" '' interface=${lib.escapeShellArg vpnClient.interface} if ! ${lib.getExe' pkgs.iproute2 "ip"} link show dev "$interface" >/dev/null 2>&1; then printf '{"text":"","class":"idle"}\n' exit 0 fi ipaddr=$(${lib.getExe' pkgs.iproute2 "ip"} -4 -o addr show dev "$interface" | ${lib.getExe' pkgs.gawk "awk"} '{ print $4; exit }') ${lib.getExe pkgs.jq} -cn \ --arg text "󰲝" \ --arg tooltip "VPN $interface: ''${ipaddr:-no IPv4}" \ --arg class "active" \ '{ text: $text, tooltip: $tooltip, class: $class }' ''; networkStatus = pkgs.writeShellScript "waybar-network-status" '' active=$( ${lib.getExe' pkgs.networkmanager "nmcli"} -t -f DEVICE,TYPE,STATE,CONNECTION device status \ | ${lib.getExe' pkgs.gawk "awk"} -F: '$3 == "connected" && $2 != "loopback" { print; exit }' ) if [ -z "$active" ]; then ${lib.getExe pkgs.jq} -cn \ --arg text "${g "f127"} off" \ --arg tooltip "Network disconnected" \ --arg class "disconnected" \ '{ text: $text, tooltip: $tooltip, class: $class }' exit 0 fi IFS=: read -r device type _state connection <<< "$active" case "$type" in wifi) icon="${g "f1eb"}" ;; ethernet) icon="LAN" ;; *) icon="NET" ;; esac ipaddr=$(${lib.getExe' pkgs.iproute2 "ip"} -4 -o addr show dev "$device" | ${lib.getExe' pkgs.gawk "awk"} '{ print $4; exit }') ${lib.getExe pkgs.jq} -cn \ --arg text "$icon $connection" \ --arg tooltip "$device: ''${ipaddr:-no IPv4}" \ --arg class "$type" \ '{ text: $text, tooltip: $tooltip, class: $class }' ''; in { options.modules.desktop.session.waybar.enable = lib.mkEnableOption "the Waybar status bar"; config = lib.mkIf config.modules.desktop.session.waybar.enable { # A symbols font backs the module glyphs, so pango falls back to it for the # codepoints Stylix's monospace font does not carry. fonts.packages = [ pkgs.nerd-fonts.symbols-only ]; home-manager.users.${config.user.name}.programs.waybar = { enable = true; # A user service bound to graphical-session.target, which uwsm activates, # so the bar comes up with the session and no compositor exec-once. systemd.enable = true; style = '' .modules-right { margin-right: 8px; } #custom-dnd { margin-right: 8px; } ''; settings.mainBar = { layer = "top"; position = "top"; height = 30; spacing = 8; modules-left = [ "hyprland/workspaces" ]; modules-center = [ "clock" ]; modules-right = [ "custom/recording" "custom/idle-lock" "mpris" "wireplumber" ] ++ lib.optional vpnClient.enable "custom/vpn" ++ [ "custom/network" "battery" "custom/dnd" ]; # Per-application icons for the windows on each workspace, mapped from # window class with a neutral glyph for anything unlisted. "hyprland/workspaces" = { on-click = "activate"; format = "{id} {windows}"; format-window-separator = " "; window-rewrite-default = g "f2d0"; window-rewrite = lib.mapAttrs' ( _name: windowIcon: lib.nameValuePair "class<${windowIcon.class}>" (g windowIcon.icon) ) (lib.attrByPath [ config.networking.hostName ] { } config.registry.window-icon); }; clock = { format = "{:%a %d %b %H:%M}"; tooltip-format = "{calendar}"; calendar = { mode = "month"; # Underline the current day so it stands out in the grid. format.today = "{}"; }; }; mpris = { format = "${g "f001"} {title}"; format-paused = "${g "f04c"} {title}"; max-length = 40; on-click = "play-pause"; on-scroll-up = "next"; on-scroll-down = "previous"; }; wireplumber = { format = "{icon} {volume}%"; format-muted = "${g "f026"} muted"; format-icons = [ (g "f026") (g "f027") (g "f028") ]; on-click = "${lib.getExe' pkgs.wireplumber "wpctl"} set-mute @DEFAULT_AUDIO_SINK@ toggle"; }; "custom/vpn" = { return-type = "json"; exec = "${vpnStatus}"; interval = 5; }; "custom/network" = { return-type = "json"; exec = "${networkStatus}"; interval = 5; }; battery = { format = "{icon} {capacity}%"; format-charging = "${g "f0e7"} {capacity}%"; format-icons = [ (g "f244") (g "f243") (g "f242") (g "f241") (g "f240") ]; states = { warning = 20; critical = 10; }; }; "custom/dnd" = { return-type = "json"; exec = "${dndStatus}"; on-click = "${dndToggle}"; interval = "once"; signal = 8; }; # Recording state has no event to hook, so the widget samples the # wf-recorder process once a second. "custom/recording" = { return-type = "json"; exec = "${recStatus}"; interval = 1; }; "custom/idle-lock" = { return-type = "json"; exec = "${idleLockStatus}"; interval = "once"; signal = 9; }; }; }; }; }