From 3c9d0ec88615d5a3ee6a643b2dc19761f2039c1a Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 11 Oct 2026 21:07:35 -0400 Subject: [PATCH 1/2] refactor(nix): share glyph decoding helper --- src/dotfiles/util.nix | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/dotfiles/util.nix b/src/dotfiles/util.nix index b461c7c..01c44ff 100644 --- a/src/dotfiles/util.nix +++ b/src/dotfiles/util.nix @@ -25,6 +25,14 @@ in removeNixSuffix = path: lib.removeSuffix ".nix" (builtins.baseNameOf path); + glyph = + code: + builtins.fromJSON ( + "\"" + + (if builtins.isList code then lib.concatMapStrings (part: "\\u${part}") code else "\\u${code}") + + "\"" + ); + registry.predicates.uniqueValue = uniqueValue; registry.predicates.uniquePerHost = -- 2.47.3 From ee29e53f7c0d958f11e8ca26e601a383cbf9edf0 Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 11 Oct 2026 21:07:35 -0400 Subject: [PATCH 2/2] refactor(waybar): register owned modules --- configs/nixos/AGENTS.md | 4 +- configs/nixos/hosts/neogaia/default.nix | 23 +- configs/nixos/hosts/zeus/default.nix | 2 +- .../nixos/modules/desktop/apps/firefox.nix | 3 +- .../nixos/modules/desktop/apps/ghostty.nix | 3 +- .../nixos/modules/desktop/apps/obsidian.nix | 3 +- configs/nixos/modules/desktop/apps/signal.nix | 3 +- configs/nixos/modules/desktop/apps/steam.nix | 3 +- .../nixos/modules/desktop/apps/vencord.nix | 3 +- .../desktop/session/hyprland/default.nix | 15 ++ .../desktop/session/hyprland/hyprlock.nix | 29 ++- .../nixos/modules/desktop/session/mako.nix | 26 ++ .../nixos/modules/desktop/session/waybar.nix | 231 +++--------------- .../nixos/modules/desktop/utilities/audio.nix | 35 ++- .../modules/desktop/utilities/recording.nix | 46 ++-- configs/nixos/modules/network/default.nix | 57 +++++ configs/nixos/modules/network/vpn-client.nix | 33 +++ configs/nixos/registry/waybar.nix | 29 +++ configs/nixos/registry/window-icon.nix | 4 +- 19 files changed, 315 insertions(+), 237 deletions(-) create mode 100644 configs/nixos/modules/network/default.nix create mode 100644 configs/nixos/registry/waybar.nix diff --git a/configs/nixos/AGENTS.md b/configs/nixos/AGENTS.md index 2f538cf..867195c 100644 --- a/configs/nixos/AGENTS.md +++ b/configs/nixos/AGENTS.md @@ -2,10 +2,10 @@ ## Desktop -- TODO: Verify this path after the NixOS configuration is migrated. - When adding a graphical application, add a `window-rewrite` icon mapping in `modules/desktop/session/waybar.nix`. +- When adding a graphical application, register its workspace icon in `registry.window-icon`. The workspace indicator uses this mapping to show an application icon instead of the generic default glyph. Match the mapping on the window class. + Store the icon as the rendered string. Run `hyprctl clients -j | jq -r '.[].class' | sort -u` to list classes in the running session. - TODO: Verify this path after the NixOS configuration is migrated. Set Hyprland session environment variables in `modules/desktop/session/hyprland/default.nix` with `env = KEY,VALUE`. diff --git a/configs/nixos/hosts/neogaia/default.nix b/configs/nixos/hosts/neogaia/default.nix index 45e7d36..d96111c 100644 --- a/configs/nixos/hosts/neogaia/default.nix +++ b/configs/nixos/hosts/neogaia/default.nix @@ -1,5 +1,6 @@ { inputs, + util, ... }: # neogaia — Dell XPS 13 9380 laptop. @@ -14,8 +15,7 @@ modules.host.cachyos.enable = true; - # So wifi can be joined from the console. - networking.networkmanager.enable = true; + modules.network.enable = true; registry.address.neogaia.self = "10.23.50.146"; @@ -40,4 +40,23 @@ modules.desktop.apps.obsidian.enable = true; modules.desktop.apps.steam.enable = true; + registry.waybar.neogaia.battery = { + order = 70; + settings = { + format = "{icon} {capacity}%"; + format-charging = "${util.glyph "f0e7"} {capacity}%"; + format-icons = [ + (util.glyph "f244") + (util.glyph "f243") + (util.glyph "f242") + (util.glyph "f241") + (util.glyph "f240") + ]; + states = { + warning = 20; + critical = 10; + }; + }; + }; + } diff --git a/configs/nixos/hosts/zeus/default.nix b/configs/nixos/hosts/zeus/default.nix index f30db78..14f8b19 100644 --- a/configs/nixos/hosts/zeus/default.nix +++ b/configs/nixos/hosts/zeus/default.nix @@ -21,7 +21,7 @@ enable32Bit = true; }; - networking.networkmanager.enable = true; + modules.network.enable = true; services.logind.settings.Login = { HandlePowerKey = "ignore"; diff --git a/configs/nixos/modules/desktop/apps/firefox.nix b/configs/nixos/modules/desktop/apps/firefox.nix index 5021d13..65140b7 100644 --- a/configs/nixos/modules/desktop/apps/firefox.nix +++ b/configs/nixos/modules/desktop/apps/firefox.nix @@ -3,6 +3,7 @@ inputs, lib, pkgs, + util, ... }: # Configures Firefox as the desktop browser. @@ -28,7 +29,7 @@ in config = lib.mkIf cfg.enable { registry.window-icon.${config.networking.hostName}.firefox = { class = ".*[Ff]irefox.*"; - icon = "f269"; + icon = util.glyph "f269"; }; home-manager.users.${config.user.name} = hm: { diff --git a/configs/nixos/modules/desktop/apps/ghostty.nix b/configs/nixos/modules/desktop/apps/ghostty.nix index 05d2ba6..d8e1e73 100644 --- a/configs/nixos/modules/desktop/apps/ghostty.nix +++ b/configs/nixos/modules/desktop/apps/ghostty.nix @@ -2,6 +2,7 @@ config, lib, pkgs, + util, ... }: # Ghostty as the desktop terminal. @@ -22,7 +23,7 @@ in registry.window-icon.${config.networking.hostName}.ghostty = { class = "com.mitchellh.ghostty"; - icon = "f120"; + icon = util.glyph "f120"; }; home-manager.users.${user}.home.packages = [ pkgs.ghostty ]; diff --git a/configs/nixos/modules/desktop/apps/obsidian.nix b/configs/nixos/modules/desktop/apps/obsidian.nix index fb018cd..49648aa 100644 --- a/configs/nixos/modules/desktop/apps/obsidian.nix +++ b/configs/nixos/modules/desktop/apps/obsidian.nix @@ -2,6 +2,7 @@ config, lib, pkgs, + util, ... }: # Obsidian as the desktop note-taking vault. @@ -17,7 +18,7 @@ in registry.window-icon.${config.networking.hostName}.obsidian = { class = "obsidian"; - icon = "f02d"; + icon = util.glyph "f02d"; }; }; } diff --git a/configs/nixos/modules/desktop/apps/signal.nix b/configs/nixos/modules/desktop/apps/signal.nix index a28387f..534d0f0 100644 --- a/configs/nixos/modules/desktop/apps/signal.nix +++ b/configs/nixos/modules/desktop/apps/signal.nix @@ -2,6 +2,7 @@ config, lib, pkgs, + util, ... }: # Signal desktop messenger. @@ -16,7 +17,7 @@ in registry.window-icon.${config.networking.hostName}.signal = { class = "[Ss]ignal"; - icon = "f4ad"; + icon = util.glyph "f4ad"; }; }; } diff --git a/configs/nixos/modules/desktop/apps/steam.nix b/configs/nixos/modules/desktop/apps/steam.nix index 6b111ed..eb252ce 100644 --- a/configs/nixos/modules/desktop/apps/steam.nix +++ b/configs/nixos/modules/desktop/apps/steam.nix @@ -2,6 +2,7 @@ config, lib, pkgs, + util, ... }: # Steam game launcher and runtime integration. @@ -26,7 +27,7 @@ registry.window-icon.${config.networking.hostName}.steam = { class = "[Ss]team"; - icon = "f1b6"; + icon = util.glyph "f1b6"; }; }; } diff --git a/configs/nixos/modules/desktop/apps/vencord.nix b/configs/nixos/modules/desktop/apps/vencord.nix index 79d9824..53a634c 100644 --- a/configs/nixos/modules/desktop/apps/vencord.nix +++ b/configs/nixos/modules/desktop/apps/vencord.nix @@ -2,6 +2,7 @@ config, lib, pkgs, + util, ... }: # Vesktop, a Discord client with Vencord built in. @@ -17,7 +18,7 @@ in registry.window-icon.${config.networking.hostName}.vesktop = { class = "[Vv]esktop"; - icon = "f1ff"; + icon = util.glyph "f1ff"; }; }; } diff --git a/configs/nixos/modules/desktop/session/hyprland/default.nix b/configs/nixos/modules/desktop/session/hyprland/default.nix index 2490799..0d39fd8 100644 --- a/configs/nixos/modules/desktop/session/hyprland/default.nix +++ b/configs/nixos/modules/desktop/session/hyprland/default.nix @@ -1,6 +1,7 @@ { config, lib, + util, ... }: # The Hyprland compositor. @@ -227,5 +228,19 @@ }; } ]; + + registry.waybar.${config.networking.hostName}."hyprland/workspaces" = { + position = "left"; + order = 10; + settings = { + on-click = "activate"; + format = "{id} {windows}"; + format-window-separator = " "; + window-rewrite-default = util.glyph "f2d0"; + window-rewrite = lib.mapAttrs' ( + _name: windowIcon: lib.nameValuePair "class<${windowIcon.class}>" windowIcon.icon + ) (lib.attrByPath [ config.networking.hostName ] { } config.registry.window-icon); + }; + }; }; } diff --git a/configs/nixos/modules/desktop/session/hyprland/hyprlock.nix b/configs/nixos/modules/desktop/session/hyprland/hyprlock.nix index 0fde487..ae8fbcb 100644 --- a/configs/nixos/modules/desktop/session/hyprland/hyprlock.nix +++ b/configs/nixos/modules/desktop/session/hyprland/hyprlock.nix @@ -2,12 +2,19 @@ config, lib, pkgs, + util, ... }: # The lock screen: a hyprlock session-lock surface the compositor owns. let - sleepIcon = builtins.fromJSON ''"\udb81\udcb2"''; - sleepOffIcon = builtins.fromJSON ''"\udb81\udcb3"''; + sleepIcon = util.glyph [ + "db81" + "dcb2" + ]; + sleepOffIcon = util.glyph [ + "db81" + "dcb3" + ]; idleLockToggle = pkgs.writeShellScript "lockscreen-idle-inhibit-toggle" '' unit=lockscreen-idle-inhibit.service @@ -37,6 +44,14 @@ let ${lib.getExe' pkgs.swayosd "swayosd-client"} --custom-message "${sleepOffIcon}" 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 + ''; in { options.modules.desktop.session.hyprland.hyprlock.enable = @@ -97,5 +112,15 @@ in }; }; }; + + registry.waybar.${config.networking.hostName}."custom/idle-lock" = { + order = 20; + settings = { + return-type = "json"; + exec = "${idleLockStatus}"; + interval = "once"; + signal = 9; + }; + }; }; } diff --git a/configs/nixos/modules/desktop/session/mako.nix b/configs/nixos/modules/desktop/session/mako.nix index 48b331d..d237b56 100644 --- a/configs/nixos/modules/desktop/session/mako.nix +++ b/configs/nixos/modules/desktop/session/mako.nix @@ -2,9 +2,24 @@ config, lib, pkgs, + util, ... }: # mako as the notification daemon: toasts, a do-not-disturb mode, and history recall. +let + 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":"${util.glyph "f1f6"}","tooltip":"Do not disturb: on","class":"dnd"}\n' + else + printf '{"text":"${util.glyph "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 + ''; +in { options.modules.desktop.session.mako.enable = lib.mkEnableOption "the mako notification daemon"; @@ -35,5 +50,16 @@ command = "${lib.getExe' pkgs.mako "makoctl"} restore"; }; }; + + registry.waybar.${config.networking.hostName}."custom/dnd" = { + order = 80; + settings = { + return-type = "json"; + exec = "${dndStatus}"; + on-click = "${dndToggle}"; + interval = "once"; + signal = 8; + }; + }; }; } diff --git a/configs/nixos/modules/desktop/session/waybar.nix b/configs/nixos/modules/desktop/session/waybar.nix index 76447d0..0e57a11 100644 --- a/configs/nixos/modules/desktop/session/waybar.nix +++ b/configs/nixos/modules/desktop/session/waybar.nix @@ -6,96 +6,19 @@ }: # Waybar: a top status bar reading system state at a glance. let - vpnClient = config.modules.network.vpn-client; + moduleNames = + position: + map (entry: entry.name) ( + lib.sort (left: right: left.module.order < right.module.order) ( + lib.filter (entry: entry.module.position == position) moduleEntries + ) + ); - # 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}"''; + moduleEntries = lib.mapAttrsToList (name: module: { + inherit name module; + }) modules; - 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 }' - ''; + modules = lib.attrByPath [ config.networking.hostName ] { } config.registry.waybar; in { options.modules.desktop.session.waybar.enable = lib.mkEnableOption "the Waybar status bar"; @@ -112,50 +35,27 @@ in # so the bar comes up with the session and no compositor exec-once. systemd.enable = true; - style = '' - .modules-right { - margin-right: 8px; + settings.mainBar = lib.mkMerge [ + { + layer = "top"; + position = "top"; + height = 30; + spacing = 8; + margin-right = 8; + + modules-left = moduleNames "left"; + modules-center = moduleNames "center"; + modules-right = moduleNames "right"; } + (lib.mapAttrs (_name: module: module.settings) modules) + ]; + }; - #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 = { + registry.waybar.${config.networking.hostName} = { + clock = { + position = "center"; + order = 10; + settings = { format = "{:%a %d %b %H:%M}"; tooltip-format = "{calendar}"; calendar = { @@ -164,77 +64,6 @@ in 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; - }; }; }; }; diff --git a/configs/nixos/modules/desktop/utilities/audio.nix b/configs/nixos/modules/desktop/utilities/audio.nix index bbbb140..d34ef91 100644 --- a/configs/nixos/modules/desktop/utilities/audio.nix +++ b/configs/nixos/modules/desktop/utilities/audio.nix @@ -2,6 +2,7 @@ config, lib, pkgs, + util, ... }: # PipeWire as the desktop audio server. @@ -15,7 +16,7 @@ locked = true; action = { kind = "exec"; - command = "${pkgs.playerctl}/bin/playerctl play-pause"; + command = "${lib.getExe pkgs.playerctl} play-pause"; }; }; @@ -24,7 +25,7 @@ locked = true; action = { kind = "exec"; - command = "${pkgs.playerctl}/bin/playerctl next"; + command = "${lib.getExe pkgs.playerctl} next"; }; }; @@ -33,7 +34,35 @@ locked = true; action = { kind = "exec"; - command = "${pkgs.playerctl}/bin/playerctl previous"; + command = "${lib.getExe pkgs.playerctl} previous"; + }; + }; + }; + + registry.waybar.${config.networking.hostName} = { + mpris = { + order = 30; + settings = { + format = "${util.glyph "f001"} {title}"; + format-paused = "${util.glyph "f04c"} {title}"; + max-length = 40; + on-click = "play-pause"; + on-scroll-up = "next"; + on-scroll-down = "previous"; + }; + }; + + wireplumber = { + order = 40; + settings = { + format = "{icon} {volume}%"; + format-muted = "${util.glyph "f026"} muted"; + format-icons = [ + (util.glyph "f026") + (util.glyph "f027") + (util.glyph "f028") + ]; + on-click = "${lib.getExe' pkgs.wireplumber "wpctl"} set-mute @DEFAULT_AUDIO_SINK@ toggle"; }; }; }; diff --git a/configs/nixos/modules/desktop/utilities/recording.nix b/configs/nixos/modules/desktop/utilities/recording.nix index 1fa9e63..11b5455 100644 --- a/configs/nixos/modules/desktop/utilities/recording.nix +++ b/configs/nixos/modules/desktop/utilities/recording.nix @@ -2,19 +2,11 @@ config, lib, pkgs, + util, ... }: # Keybound screen recording: pick a region, then toggle a video-only capture. let - slurp = "${pkgs.slurp}/bin/slurp"; - wf-recorder = "${pkgs.wf-recorder}/bin/wf-recorder"; - notify-send = "${pkgs.libnotify}/bin/notify-send"; - pgrep = "${pkgs.procps}/bin/pgrep"; - pkill = "${pkgs.procps}/bin/pkill"; - date = "${pkgs.coreutils}/bin/date"; - mkdir = "${pkgs.coreutils}/bin/mkdir"; - xdgUserDir = "${pkgs.xdg-user-dirs}/bin/xdg-user-dir"; - # A single key both starts and stops. # A running wf-recorder is stopped with SIGINT so it finalises the file. # Otherwise slurp picks a region and wf-recorder runs in the foreground until @@ -22,19 +14,29 @@ let # reports it saved. # No -a means no audio is captured. toggle = pkgs.writeShellScript "screen-record-toggle" '' - if ${pgrep} -x wf-recorder >/dev/null; then - ${pkill} -INT -x wf-recorder + if ${lib.getExe' pkgs.procps "pgrep"} -x wf-recorder >/dev/null; then + ${lib.getExe' pkgs.procps "pkill"} -INT -x wf-recorder exit 0 fi - region=$(${slurp}) || exit 0 - dir="$(${xdgUserDir} VIDEOS)/Recordings" - ${mkdir} -p "$dir" - file="$dir/recording-$(${date} +%Y%m%d-%H%M%S).mp4" + region=$(${lib.getExe pkgs.slurp}) || exit 0 + dir="$(${lib.getExe' pkgs.xdg-user-dirs "xdg-user-dir"} VIDEOS)/Recordings" + ${lib.getExe' pkgs.coreutils "mkdir"} -p "$dir" + file="$dir/recording-$(${lib.getExe' pkgs.coreutils "date"} +%Y%m%d-%H%M%S).mp4" - ${notify-send} -a "Screen recording" "Recording started" "Region capture, no audio." - ${wf-recorder} -g "$region" -f "$file" - ${notify-send} -a "Screen recording" "Recording saved" "$file" + ${lib.getExe' pkgs.libnotify "notify-send"} -a "Screen recording" "Recording started" "Region capture, no audio." + ${lib.getExe pkgs.wf-recorder} -g "$region" -f "$file" + ${lib.getExe' pkgs.libnotify "notify-send"} -a "Screen recording" "Recording saved" "$file" + ''; + + # Lit while a wf-recorder capture is running. + # Empty text collapses the widget when idle, so it shows nothing until then. + recordingStatus = pkgs.writeShellScript "waybar-recording-status" '' + if ${lib.getExe' pkgs.procps "pgrep"} -x wf-recorder >/dev/null; then + printf '{"text":"${util.glyph "f03d"}","tooltip":"Recording","class":"recording"}\n' + else + printf '{"text":"","class":"idle"}\n' + fi ''; in { @@ -50,5 +52,13 @@ in }; }; + registry.waybar.${config.networking.hostName}."custom/recording" = { + order = 10; + settings = { + return-type = "json"; + exec = "${recordingStatus}"; + interval = 1; + }; + }; }; } diff --git a/configs/nixos/modules/network/default.nix b/configs/nixos/modules/network/default.nix new file mode 100644 index 0000000..c524db1 --- /dev/null +++ b/configs/nixos/modules/network/default.nix @@ -0,0 +1,57 @@ +{ + config, + lib, + pkgs, + util, + ... +}: +# NetworkManager-managed networking. +let + 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 "${util.glyph "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="${util.glyph "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.network.enable = lib.mkEnableOption "NetworkManager-managed networking"; + + config = lib.mkIf config.modules.network.enable { + networking.networkmanager.enable = true; + + registry.waybar.${config.networking.hostName}."custom/network" = { + order = 60; + settings = { + return-type = "json"; + exec = "${networkStatus}"; + interval = 5; + }; + }; + }; +} diff --git a/configs/nixos/modules/network/vpn-client.nix b/configs/nixos/modules/network/vpn-client.nix index 76bc080..624f6df 100644 --- a/configs/nixos/modules/network/vpn-client.nix +++ b/configs/nixos/modules/network/vpn-client.nix @@ -1,10 +1,34 @@ { config, lib, + pkgs, + util, ... }: let cfg = config.modules.network.vpn-client; # NOLINT(let_order) + + vpnIcon = util.glyph [ + "db83" + "dc9d" + ]; + + vpnStatus = pkgs.writeShellScript "waybar-vpn-status" '' + interface=${lib.escapeShellArg cfg.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 "${vpnIcon}" \ + --arg tooltip "VPN $interface: ''${ipaddr:-no IPv4}" \ + --arg class "active" \ + '{ text: $text, tooltip: $tooltip, class: $class }' + ''; in { options.modules.network.vpn-client = { @@ -57,6 +81,15 @@ in vpn-off = "sudo systemctl stop 'wireguard-${cfg.interface}*.service' wireguard-${cfg.interface}.target"; }; + registry.waybar.${config.networking.hostName}."custom/vpn" = { + order = 50; + settings = { + return-type = "json"; + exec = "${vpnStatus}"; + interval = 5; + }; + }; + systemd.targets."wireguard-${cfg.interface}".wantedBy = lib.mkForce [ ]; networking.wireguard.interfaces.${cfg.interface} = { diff --git a/configs/nixos/registry/waybar.nix b/configs/nixos/registry/waybar.nix new file mode 100644 index 0000000..413f63b --- /dev/null +++ b/configs/nixos/registry/waybar.nix @@ -0,0 +1,29 @@ +{ lib, ... }: +{ + type = lib.types.submodule { + options = { + position = lib.mkOption { + type = lib.types.enum [ + "left" + "center" + "right" + ]; + default = "right"; + description = "Selects the Waybar section that shows the module."; + }; + + order = lib.mkOption { + type = lib.types.int; + default = 100; + example = 50; + description = "Sorts the module within its Waybar section."; + }; + + settings = lib.mkOption { + type = lib.types.attrsOf lib.types.anything; + default = { }; + description = "Sets the Waybar module configuration."; + }; + }; + }; +} diff --git a/configs/nixos/registry/window-icon.nix b/configs/nixos/registry/window-icon.nix index 76ffa09..574ae94 100644 --- a/configs/nixos/registry/window-icon.nix +++ b/configs/nixos/registry/window-icon.nix @@ -10,9 +10,9 @@ }; icon = lib.mkOption { - description = "Sets the Nerd Font codepoint shown for matching windows."; + description = "Sets the icon shown for matching windows."; type = lib.types.str; - example = "f269"; + example = ""; }; }; }; -- 2.47.3