{ 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}"''; user = config.user.name; 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 ${pkgs.mako}/bin/makoctl mode 2>/dev/null | ${pkgs.gnugrep}/bin/grep -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" '' ${pkgs.mako}/bin/makoctl mode -t dnd >/dev/null 2>&1 ${pkgs.procps}/bin/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 ${pkgs.procps}/bin/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 ${pkgs.systemd}/bin/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 ! ${pkgs.iproute2}/bin/ip link show dev "$interface" >/dev/null 2>&1; then printf '{"text":"","class":"idle"}\n' exit 0 fi ipaddr=$(${pkgs.iproute2}/bin/ip -4 -o addr show dev "$interface" | ${pkgs.gawk}/bin/awk '{ print $4; exit }') ${pkgs.jq}/bin/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=$( ${pkgs.networkmanager}/bin/nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status \ | ${pkgs.gawk}/bin/awk -F: '$3 == "connected" && $2 != "loopback" { print; exit }' ) if [ -z "$active" ]; then ${pkgs.jq}/bin/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=$(${pkgs.iproute2}/bin/ip -4 -o addr show dev "$device" | ${pkgs.gawk}/bin/awk '{ print $4; exit }') ${pkgs.jq}/bin/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.${user}.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 = { "class" = g "f120"; "class<.*[Ff]irefox.*>" = g "f269"; "class" = g "f268"; "class<[Cc]ode>" = g "f121"; "class" = g "f02d"; "class<[Ss]ignal>" = g "f4ad"; "class<[Ss]team>" = g "f1b6"; "class<[Vv]esktop>" = g "f1ff"; }; }; 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 = "${pkgs.wireplumber}/bin/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; }; }; }; }; }